1.需求描述

根据项目需求,采用Antd组件库需要封装一个评论框,具有以下功能:

  • 支持文字输入
  • 支持常用表情包选择
  • 支持发布评论
  • 支持自定义表情包

2.封装代码

./InputComment.tsx

  1 import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
2 import { SmileOutlined } from '@ant-design/icons';
3 import { Row, Col, Button, Tooltip, message } from 'antd';
4
5 import styles from './index.less';
6
7 import {setCursorPostionEnd} from "./util";
8
9 const emojiPath = '/emojiImages/';
10 const emojiSuffix = '.png';
11 const emojiList = [...Array(15).keys()].map((_, index: number) => {
12 return { id: index + 1, path: emojiPath + (index + 1) + emojiSuffix };
13 });
14
15 type Props = {
16 uniqueId: string; // 唯一键
17 item?: object; // 携带参数
18 okClick: Function; // 发布
19 okText?: string;
20 };
21
22 const InputComment = forwardRef((props: Props, ref) => {
23 const { uniqueId: id, okClick, okText } = props;
24 const inputBoxRef = useRef<any>(null);
25 const [textCount, setTextCount] = useState(0);
26 let rangeOfInputBox: any;
27 const uniqueId = 'uniqueId_' + id;
28
29 const setCaretForEmoji = (target: any) => {
30 if (target?.tagName?.toLowerCase() === 'img') {
31 const range = new Range();
32 range.setStartBefore(target);
33 range.collapse(true);
34 // inputBoxRef?.current?.removeAllRanges();
35 // inputBoxRef?.current?.addRange(range);
36 const sel = window.getSelection();
37 sel?.removeAllRanges();
38 sel?.addRange(range);
39 }
40 };
41
42 /**
43 * 输入框点击
44 */
45 const inputBoxClick = (event: any) => {
46 const target = event.target;
47 setCaretForEmoji(target);
48 };
49
50 /**
51 * emoji点击
52 */
53 const emojiClick = (item: any) => {
54 const emojiEl = document.createElement('img');
55 emojiEl.src = item.path;
56 const dom = document.getElementById(uniqueId);
57 const html = dom?.innerHTML;
58
59 // rangeOfInputBox未定义并且存在内容时,将光标移动到末尾
60 if (!rangeOfInputBox && !!html) {
61 dom.innerHTML = html + `<img src="${item.path}"/>`;
62 setCursorPostionEnd(dom)
63 } else {
64 if (!rangeOfInputBox) {
65 rangeOfInputBox = new Range();
66 rangeOfInputBox.selectNodeContents(inputBoxRef.current);
67 }
68
69 if (rangeOfInputBox.collapsed) {
70 rangeOfInputBox.insertNode(emojiEl);
71 } else {
72 rangeOfInputBox.deleteContents();
73 rangeOfInputBox.insertNode(emojiEl);
74 }
75 rangeOfInputBox.collapse(false);
76
77 const sel = window.getSelection();
78 sel?.removeAllRanges();
79 sel?.addRange(rangeOfInputBox);
80 }
81 };
82
83 /**
84 * 选择变化事件
85 */
86 document.onselectionchange = (e) => {
87 if (inputBoxRef?.current) {
88 const element = inputBoxRef?.current;
89 const doc = element.ownerDocument || element.document;
90 const win = doc.defaultView || doc.parentWindow;
91 const selection = win.getSelection();
92
93 if (selection?.rangeCount > 0) {
94 const range = selection?.getRangeAt(0);
95 if (inputBoxRef?.current?.contains(range?.commonAncestorContainer)) {
96 rangeOfInputBox = range;
97 }
98 }
99 }
100 };
101
102 /**
103 * 获取内容长度
104 */
105 const getContentCount = (content: string) => {
106 return content
107 .replace(/&nbsp;/g, ' ')
108 .replace(/<br>/g, '')
109 .replace(/<\/?[^>]*>/g, '占位').length;
110 };
111
112 /**
113 * 发送
114 */
115 const okSubmit = () => {
116 const content = inputBoxRef.current.innerHTML;
117 if (!content) {
118 return message.warning('温馨提示:请填写评论内容!');
119 } else if (getContentCount(content) > 1000) {
120 return message.warning(`温馨提示:评论或回复内容小于1000字!`);
121 }
122
123 okClick(content);
124 };
125
126 /**
127 * 清空输入框内容
128 */
129 const clearInputBoxContent = () => {
130 inputBoxRef.current.innerHTML = '';
131 };
132
133 // 将子组件的方法 暴露给父组件
134 useImperativeHandle(ref, () => ({
135 clearInputBoxContent,
136 }));
137
138 // 监听变化
139 useEffect(() => {
140 const dom = document.getElementById(uniqueId);
141 const observer = new MutationObserver(() => {
142 const content = dom?.innerHTML ?? '';
143 // console.log('Content changed:', content);
144 setTextCount(getContentCount(content));
145 });
146
147 if (dom) {
148 observer.observe(dom, {
149 attributes: true,
150 childList: true,
151 characterData: true,
152 subtree: true,
153 });
154 }
155 }, []);
156
157 return (
158 <div style={{ marginTop: 10, marginBottom: 10 }} className={styles.inputComment}>
159 {textCount === 0 ? (
160 <div className="input-placeholder">
161 {okText === '确认' ? '回复' : '发布'}评论,内容小于1000字!
162 </div>
163 ) : null}
164
165 <div
166 ref={inputBoxRef}
167 id={uniqueId}
168 contentEditable={true}
169 placeholder="adsadadsa"
170 className="ant-input input-box"
171 onClick={inputBoxClick}
172 />
173 <div className="input-emojis">
174 <div className="input-count">{textCount}/1000</div>
175
176 <Row wrap={false}>
177 <Col flex="auto">
178 <Row wrap={true} gutter={[0, 10]} align="middle" style={{ userSelect: 'none' }}>
179 {emojiList.map((item, index: number) => {
180 return (
181 <Col
182 flex="none"
183 onClick={() => {
184 emojiClick(item);
185 }}
186
187 <Col flex="none" style={{ marginTop: 5 }}>
188 <Button
189 type="primary"
190 disabled={textCount === 0}
191 onClick={() => {
192 okSubmit();
193 }}
194 >
195 {okText || '发布'}
196 </Button>
197 </Col>
198 </Row>
199 </div>
200 </div>
201 );
202 });
203
204 export default InputComment;

./util.ts

 1 /**
2 * 光标放到文字末尾(获取焦点时)
3 * @param el
4 */
5 export function setCursorPostionEnd(el:any) {
6 if (window.getSelection) {
7 // ie11 10 9 ff safari
8 el.focus() // 解决ff不获取焦点无法定位问题
9 const range = window.getSelection() // 创建range
10 range?.selectAllChildren(el) // range 选择obj下所有子内容
11 range?.collapseToEnd() // 光标移至最后
12 } else if (document?.selection) {
13 // ie10 9 8 7 6 5
14 const range = document?.selection?.createRange() // 创建选择对象
15 // var range = document.body.createTextRange();
16 range.moveToElementText(el) // range定位到obj
17 range.collapse(false) // 光标移至最后
18 range.select()
19 }
20 }

 3.问题解决

  • 同一页面有多个评论框时,光标位置不准确?答:从组件外部传入唯一ID标识,进行区分。
  • 表情包存放位置?答:表情包存放在/public/emojiImages/**.png,命名规则1、2、3、4……

4.组件展示

React组件封装:文字、表情评论框的更多相关文章

  1. [RN] React Native 封装选择弹出框(ios&android)

    之前看到react-native-image-picker中自带了一个选择器,可以选择拍照还是图库,但我们的项目中有多处用到这个选择弹出框,所以就自己写了一下,最最重要的是ios和Android通用. ...

  2. React组件化开发

    环境搭建: 1.安装node.js 2.安装cnpm  # npm install -g cnpm --registry=https://registry.npm.taobao.org 3.全局安装c ...

  3. iOS开发之自定义表情键盘(组件封装与自动布局)

    下面的东西是编写自定义的表情键盘,话不多说,开门见山吧!下面主要用到的知识有MVC, iOS开发中的自动布局,自定义组件的封装与使用,Block回调,CoreData的使用.有的小伙伴可能会问写一个自 ...

  4. 带emoji表情弹出层的评论框,semantic+emoji picker,java.sql.SQLException: Incorrect string value: '\xF0\x9F..'

    在自己做一个项目玩时,在做评论的时候. 选中了semantic.js原型,这个在国内用的不是很多,但是在github上star数量很高,想当初我想找一个js框架是就在上面找的. semantic中文网 ...

  5. 封装react组件——三级联动

    思路: 数据设计:省份为一维数组,一级市为二维数组,二级市/区/县为三维数组.这样设计的好处在于根据数组索引实现数据的关联. UI组件: MUI的DropDownMenu组件或Select Field ...

  6. react第七单元(组件的高级用法-组件的组合(children的用法)-高阶组件-封装组件)

    第七单元(组件的高级用法-组件的组合(children的用法)-高阶组件-封装组件) #受控组件 简而言之,就是受到状态state控制的表单,表单的值改变则state值也改变,受控组件必须要搭配onc ...

  7. ReactNative之从HelloWorld中看环境搭建、组件封装、Props及State

    开篇呢,先给大家问个好,今天是中秋节,祝大家中秋节快乐!!虽然是中秋节,但是木有回家还是总结一下知识点写写博客吧,想着昨天总结一下的,但是昨天和几个同学小聚了一下,酒逢知己总是千杯少呢,喝的微醺不适合 ...

  8. React组件

    React组件 组件是React中的基本单位,在每个组件里面又封装了程序逻辑,通过reader标出界面片段或者回传一段描述,组件再通过React.renderComponent将组件展示在浏览器中.每 ...

  9. 编写React组件的最佳实践

    此文翻译自这里. 当我刚开始写React的时候,我看过很多写组件的方法.一百篇教程就有一百种写法.虽然React本身已经成熟了,但是如何使用它似乎还没有一个"正确"的方法.所以我( ...

  10. React组件之间通过Props传值的技巧(小案例,帮助体会理解props、state、受控组件和非受控组件等)

    本文重要是根据react小书上的一个很简单的例子改编的,加上自己的学习理解,希望可以通过实际案例让大家对概念有更清晰的理解,当然也希望能一块学习. import React,{Component} f ...

随机推荐

  1. Spring Boot学生信息管理系统项目实战-4.学生管理

    1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 2.1 导出导入模板 2.2 导入学生数据 3.项目源码 只挑重点讲,详细请看源码. 学生管理包含了学生信息的增删改查,这里我只 ...

  2. SecureCRT很好用的几个快捷键

    以下是我在使用SecureCRT这个SSH工具时用到的很实用的快捷键,与大家分享: [Alt]+[Enter]:全屏 [Alt]+[B]: 快速打开新的连接 [Alt]+[1/2/3/4/5.../9 ...

  3. 【libGDX】使用Mesh绘制三角形

    1 Mesh 和 ShaderProgram 简介 1.1 创建 Mesh ​ 1)Mesh 的构造方法 public Mesh(boolean isStatic, int maxVertices, ...

  4. win32 - 将文件的访问权限给特定的用户

    需要首先获取特定用户的SID. 这是一些步骤, 验证输入参数. 为可能足够大的SID和域名创建缓冲区. 在循环中,调用LookupAccountName以检索提供的帐户名的SID.如果SID的缓冲区或 ...

  5. 小红书 x Hugging Face 邀请你一起晒「创意新春照」

    不藏了,近期全网爆火的AI 写真项目 InstantID,正是来自小红书社区技术创作发布团队. 为了迎接龙年春节的到来,我们的InstantID全新推出「Spring Festival」新春风格!并与 ...

  6. Jenkins共享库使用

    简单使用 共享库(Shared libraries)是一种可以用来封装函数.变量甚至整个 Pipeline 的机制.通过共享库,可以将常用的功能和流程逻辑定义在单独的 Groovy 脚本中,然后在多个 ...

  7. HTML学习---day01

    1.head标签 <!DOCTYPE html> <!--文档声明H5 html--> <html lang="en"> <head> ...

  8. java+文件读写实现的图书管理系统

    一功能 管理员具有的功能 1.增加图书.删除图书.修改图书信息.查询图书.图书列表 2.借阅者管理,通过借阅的书号查询图书信息 3.个人信息修改 读者功能 1.图书借阅 2.图书归还 3.图书查询 4 ...

  9. NSSRound#17 Basic web

    NSSRound#17 Basic web 真签到 审题 一个登录界面 看到页面名字Robots? 转到robots.txt 看到加密 知识点: 加密解密. 解题 hint解密,使用Hex加密方式解出 ...

  10. 理解LLMOps: Large Language Model Operations

    理解LLMOps: Large Language Model Operations 对于像我一样的小白来说,本文是一篇非常不错的LLMs入门介绍文档.来自:Understanding LLMOps: ...