HTML 跨平台使用同一套 emoji (twemoji) + 实现 emoji 选择
创始人
2024-11-19 07:08:47
0

背景:

网页需要显示和发送带 emoji 表情的文本消息(为方便理解, 以 whatsapp 为例, 实际开发中待定)
同时, 要求不同系统打开网页时, 看到的都是同一套 emoji , 避免同一个 emoji 在不同电脑上显示不同

概述:

  1. 引入 twemoji 库文件
  2. 把网页版 wa 的 emoji 全部复制下来
  3. 新增 emoji 组件, 点击表情图标弹出表情框, 框内显示与 wa 一致
  4. 点选框中表情, 根据点击前光标在输入框(contentEditable 的 div)的位置, 插入 twemoji.parse 转换过的表情(图片)
  5. 给各处可能显示 twemoji 的 div 加上特定 class(比如 twemoji-convert), 在程序主界面(Main.vue)新增 MutationObserver , 在 DOM 变化时选取此类 class 元素, 使用 twemoji.parse 转换元素, 使显示 emoji

实现过程:

  1. 引入 twemoji

       
  2. 复制 wa emoji

    在网页版 whatsapp 上聊天, 一栏栏地点选表情, 发送, 复制下来, 此时接收到的内容已经是字符了, 把这些字符按顺序提取为数组;

    这个需要耐心, 这些个字符千奇百怪, 有的字符电脑系统不支持不能渲染出来, 有的字符后面需要接一个空格, 有的字符看上去只有一位但实际占了多位, 最多的还是由多种字符组合显示成一个表情的(字符人, 可加修饰字符: 性别, 发型, 职业, with another one …), 千万别弄错了

  3. 新增 emoji 组件

    渲染表情部分由全局的 MutationObserver 负责(twemoji.parse)

    选中表情部分如下:

    // 点击选中 emoji handleClickEmoji(e) {     // 取选中的 emoji DOM 标签     let emojiImg;     if (e.target.classList.contains('emoji-item')) {         emojiImg = e.target.querySelector('img.emoji');     } else if (e.target.classList.contains('emoji')) {         emojiImg = e.target;     }      // 取标签上的 alt (实体字符, twemoji 转换后自带)传给外部     if (emojiImg) {         this.$emit('checkEmoji', emojiImg.getAttribute('alt'));     } } 
  4. 输入框接收选中表情, 加入到输入框中

    输入框 div

     

    输入框 div 相关事件

    // inputOnKeyDown 处理回车, ctrl 等事件, 与表情主逻辑无关, 略过  // 离开焦点时先保存状态(光标等信息) save_range() {     let range = null;     if (window.getSelection) {         const sel = window.getSelection();         if (sel.getRangeAt && sel.rangeCount) {             range = sel.getRangeAt(0);         }     } else if (document.selection && document.selection.createRange) {         range = document.selection.createRange();     }     this.lastEditRange = range; }  // 粘贴内容到可编辑 div (参考 https://www.zhangxinxu.com/wordpress/2016/01/contenteditable-plaintext-only/) handlePaste(e) {     e.preventDefault();     let text;      if (window.clipboardData && window.clipboardData.setData) {         // IE         text = window.clipboardData.getData('text');     } else {         text = (e.originalEvent || e).clipboardData.getData('text/plain');     }     if (document.body.createTextRange) {         let textRange;         if (document.selection) {             textRange = document.selection.createRange();         } else if (window.getSelection) {             const sel = window.getSelection();             const range = sel.getRangeAt(0);              // 创建临时元素,使得TextRange可以移动到正确的位置             const tempEl = document.createElement('span');             tempEl.innerHTML = '&#FEFF;';             range.deleteContents();             range.insertNode(tempEl);             textRange = document.body.createTextRange();             textRange.moveToElementText(tempEl);             tempEl.parentNode.removeChild(tempEl);         }         textRange.text = text;         textRange.collapse(false);         textRange.select();     } else {         // Chrome之类浏览器         document.execCommand('insertText', false, text);     } } 

    选中表情相关事件

    // 接收"选中 emoji 表情"事件 handleCheckEmoji(val) {     // 获取待插入表情 Node     let dom_insert = document.createElement('span');     dom_insert.innerHTML = twemoji.parse(val);     dom_insert = dom_insert.childNodes[0];      // 插入 Node 到输入框     this.insertInputMsg(dom_insert); }  // 插入 emoji 表情到输入框 insertInputMsg(val) {     // 获取待插入结点     let dom_insert;     if (val instanceof Node) {         // 是 Node 结点, 不用做处理          dom_insert = val;     } else {         // 否则当做文本结点处理          dom_insert = document.createTextNode(String(val || ''));     }      // 获取编辑框对象     const dom_input = this.$refs.sendMsg;      // 编辑框设置焦点     dom_input.focus();      // 获取选定对象     let selection = null;     if (window.getSelection) {         selection = window.getSelection();     } else if (window.document.getSelection) {         selection = window.document.getSelection();     } else if (window.document.selection) {         selection = window.document.selection.createRange().text;     }     // 如果获取不到, 退出流程     if (!selection) {         this.$Message.error(this.$t('whatsapp_manage.browserError'));         return false;     }      // 判断是否有最后光标对象存在     if (this.lastEditRange) {         // 存在最后光标对象,选定对象清除所有光标并添加最后光标还原之前的状态         selection.removeAllRanges();         selection.addRange(this.lastEditRange);     }      // 根据所在位置的不同以不同的方式插入结点     if (this.lastEditRange) {         // 有光标对象, 直接插入         this.lastEditRange.insertNode(dom_insert);     } else if (selection.anchorNode == dom_input) {         // 焦点就在文本框, 则直接 append node 到最后         dom_input.appendChild(dom_insert);     } else if (selection.anchorNode.nodeName != '#text') {         // 焦点在非文本结点, 则插入到焦点节点后面         dom_input.insertBefore(dom_insert, selection.anchorNode.nextSibling);     }      // 创建新的光标对象     const range = document.createRange();     // 光标对象的范围界定为新建的内容节点     range.setStartAfter(dom_insert);     // 插入空格, 否则光标可能不显示     // dom_input.insertBefore(document.createTextNode(' '), dom_insert.nextSibling);     // range.setStart(dom_insert.nextSibling, 1);     // 使光标开始和光标结束重叠     range.collapse(true);     // 清除选定对象的所有光标对象     selection.removeAllRanges();     // 插入新的光标对象     selection.addRange(range);     // 无论如何都要记录最后光标对象     this.lastEditRange = selection.getRangeAt(0); } 
  5. 主界面监听 DOM 变动, twemoji.parse 转化指定 class 元素内部的实体字符为表情

    mounted() {     // 监听 DOM 变化, 变化时使用 twemoji 库转化 emoji 实体字符为 twemoji emoji     this.observer = new MutationObserver(function(mutations, observe) {         const domList = document.querySelectorAll('.twemoji-convert');         for (let i = 0; i < domList.length; i++) {             twemoji.parse(domList[i]);         }     });     this.observer.observe(document.body, {         'childList': true,         'characterData': true,         'subtree': true     }); } 

补充:

配合 Vue 使用时, 表情和 emoji 混杂的文本, 使用 twemoji.parse 后会破坏 vue 的响应式监听, 导致视图不随数据的更新而更新; 解决方法 — 给需要更新的地方加上 key , key 上绑定原数据, 这样, 当原数据变化时, 组件会重新渲染
另外, 如果使用虚拟滚动表格, 表格内有 emoji 文字, 仍然有问题, 滚动后其他纯文字单元格也显示成了带 emoji 的那个单元格内容, 暂未解决

相关内容

热门资讯

必备攻略!wpk插件靠谱的(有... 必备攻略!wpk插件靠谱的(有辅助)中至小程序插件(有挂策略);wpk是一款益智类棋牌手游,可以说是...
黑科技辅助挂!wepoke透明... 黑科技辅助挂!wepoke透明真的的(有科技)青龙大厅游戏辅助(有挂指示)准备好在wepoke ia...
科技揭秘!wpk俱乐部有外挂的... 1、不需要AI权限,帮助你快速的进行wpk计算辅助教程,沉浸在游戏的游玩之中。2、里面整个wpk黑科...
第三方内容!微扑克辅助挂(软件... 第三方内容!微扑克辅助挂(软件透明挂)旺旺福建麻将麻将神器下载(有挂手段)科技教程也叫必备教程,这是...
重大推荐!wepoke外挂(透... 1、让任何用户在无需AI插件第三方神器的情况下就能够完成在wepoke下的调试。2、直接的在wepo...
来一盘!wepoke智能ai(... 1、不需要AI权限,帮助你快速的进行wepoke计算辅助教程,沉浸在游戏的游玩之中。2、里面整个we...
安装程序教程!微扑克教你提高中... 安装程序教程!微扑克教你提高中牌率(透明挂软件)皮皮跑胡子输赢规律(有挂向导);皮皮跑胡子最新软件透...
WPK辅助挂!微扑克真的有挂存... 1、很好的工具软件,可以解锁游戏的ai质量和中牌率,深受大多数游戏玩家的喜爱。2、非常简单,易于操作...
新2024版推荐!wpkai辅... 新2024版推荐!wpkai辅助(有挂的)369互娱麻将插件(有挂引导);WPK必备黑科技是一款具有...
普及知识!wpk德州透视辅助(... 大家肯定在之前WePoKer或者WPK中玩过普及知识!wpk德州透视辅助(有辅助)中至麻将小程序bu...