美文网首页web前端技术分享
div可编辑属性contenteditable实现富文本编辑器

div可编辑属性contenteditable实现富文本编辑器

作者: 廊桥梦醉 | 来源:发表于2023-07-24 08:47 被阅读0次

    使用div标签的可编辑属性contenteditable实现可插入链接、表情包、其他变量的富文本编辑器,因为在我使用这个功能是在19年项目中需求中有涉及,最近被问到一些关于该功能的问题,就做一下总结.

    说明:
    1、技术栈: vue@^2.7.14, element-ui@^2.13.1, emoji@^0.3.2,  js, html, css
    2、div可编辑属性,change事件失效,可通过监听input事件来时时得到输入内容的变化
    3、开发此功能是为了实现用微信公众号向用户推送客服消息时,创建文本消息内容开发的,微信开放平台对于文本消息("msgtype":"text",)内容的格式有限制:文本中只支持a标签
    去微信开放平台

    完成后的页面效果

    可插入emoji表情,可插入a链接,可插入小程序链接,还可以插入一些自定义的变量

    公众号发送到用户看到的效果

    一、富文本实现

    1、输入功能
    div标签可编辑
    这一步比较简单,只需要给div标签添加contenteditable为true即可;

    <div contenteditable="true" style="height:100px; border: 1px solid red; padding:2px;" id="editor" ref="editor">

    </div>

    通过监听input事件,时时关注内容的变化并获取输入内容

    //let editor = document.getElementById('editor')
     //editor.addEventListener('input', (item) => { console.log(item) })

    this.$refs.editor.addEventListener('input', this.changeContentValue);

    自动获取焦点

    // let editor = document.getElementById('editor')
     // editor.focus();

    this.$refs.editor.focus();

    往光标处插入html片段

    // 往光标位置插入HTML片段
    function insertHtmlAtCaret(html) {
         if (window.getSelection) {
             // IE9 and non-IE
              if (this.sel.getRangeAt && this.sel.rangeCount) {
                       var el = document.createElement('div');
                        el.innerHTML = html;
                        var frag = document.createDocumentFragment();
                        var node;
                        var lastNode;
                        while ((node = el.firstChild)) {
                             lastNode = frag.appendChild(node);
                         }
                       this.range.insertNode(frag);
                      if (lastNode) {
                             this.range = this.range.cloneRange();
                              this.range.setStartAfter(lastNode);
                              this.range.collapse(true);
                              this.sel.removeAllRanges();
                               this.sel.addRange(this.range);
                          }
                     }
                }
                else if (document.selection && document.selection.type !== 'Control') {
                         // IE < 9 document.selection.createRange().pasteHTML(html);
                  }
         },

    2、插入a链接功能
    点击插入链接按钮可出现弹窗插入或者修改内容

    在点击插入链接按钮(也就是输入框失去焦点)的时候获取光标所在的位置
       this.sel = window.getSelection();
       this.range = this.sel.getRangeAt(0);
       this.taget = this.sel.focusNode.parentElement;
       const { sel, taget } = this;

    选中一部分内容,或者点解已插入链接的内容
    第一次添加链接或者多次修改链接内容

       this.selectContents = sel.toString(); // 当选中未添加链接的内容时,选中内容复制给链接的文字字段

    显示弹窗,对弹窗的文本与链接进行修改
    const { selectContents, selectUrl} = this;
    this.$set(this.textForm, 'text', selectContents);
    this.$set(this.textForm, 'url', selectUrl);

    完成后点击确定,以新内容替换旧内容
    const { text, url } = this.textForm;
     if (text && url) {
     this.range && this.range.deleteContents(); // 删除输入框原有的文本内容
     const { selectContents, selectUrl, taget } = this;
     if (selectContents && selectUrl && taget) {
         Array.from(this.$refs.editor.childNodes).forEach((item) => {
             if (item === taget) {
                    this.$refs.editor.removeChild(taget); // 删除输入框原有的文本链接内容 }
              else if (taget.parentNode === item) {
                     item.removeChild(taget); // 当村子a链接内有插入了一次a标签的情况处理
                   }
              });
         }

    插入到输入框
     this.insertHtmlAtCaret(`<a href='${url}' style="color:#5392ff">${text}</a>`); } 
     this.textForm = { url: '', text: '' }; // 重置

    效果图

    3、插入小程序链接同上

    4、插入表情包功能
    封装emoji 组件

     
    引入emoji组件

    import Emoji from './emoji';
    const emoji = require('emoji');
    components: {
     Emoji
     }
    html部分
    <el-popover
         ref="popover-click"
         placement="bottom-start"
          width="390"
          trigger="click"
            @show="mountedEmoji = true"
      >
             <Emoji
                @emoji = "selectEmoji">
            </Emoji>
     </el-popover>

     插入表情
     function selectEmoji(emoji) {
         this.insertHtmlAtCaret(emoji);
     },

    5、输入字数统计功能
         div的可编辑属性,获取到的内容格式如下,如果统计输入字数需要对其进行处理

    从获取到的输入内容可得出的结论是
      (1) shift+回车换行会在当前操作的这一行后生成<br/>标签,用来与下一行内容分开
      (2) 回车直接换行会生成<div><br/></div>形式, 输入内容后,输入的内容替换div标签中的br
      (3)当使用了直接回车换行,再使用shift+回车换行,则shift+回车换行这行内容会被直接回车换行生成的div包裹
      (4) 光标处于0位置的时候禁止换行

    针对以上需求处理方法是,对div中输入的内容进行过滤

    function getDomValue(elem) {
        var res = '';
        let arr = Array.from(elem.childNodes);
         arr.forEach((child) => {
             if (child.nodeName === '#text') {
                   res += child.nodeValue;
              } else if (child.nodeName === 'BR') {
                    res += '\n';
               } else if (child.nodeName === 'P') {
                  res += '\n' + getDomValue(child);
               } else if (child.nodeName === 'SPAN') {
               res += getDomValue(child);
               } else if (child.nodeName === 'BUTTON') {
              res += getDomValue(child);
              } else if (child.nodeName === 'IMG') {
                 res += child.alt;
               } else if (child.nodeName === 'DIV') {
                     const s = Array.from(child.childNodes);
                  if (s.length === 1 && s[0].nodeName === 'BR' || child.previousSibling && child.previousSibling.nodeName === 'BR') {
     // 处理shift+回车与直接回车混用导致多处来换行的情况
        res += getDomValue(child); }
               else {
                    res += '\n' + getDomValue(child); 
                }
               )else if (child.nodeName === 'A') {
                    if (child.href !== null) {
                         const innerHTML = child.innerHTML.replace(/<br>/g, '')
                                            .replace(/<span (.*?)>/gi, '').replace(/<\/span>/gi, '');
                         res += `<a href='${child.href}'>${innerHTML}</a>`;
                     }
            }
    }

    统计字数
    function getDomValuelength(elem) {
         var reg = /<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi;
         var data = elem.toLowerCase().replace(reg, function ($1, $2, $3) {
                             return $3;
                   });
          return data.length;
    }

    6、我的源码git地址:https://github.com/wangAlisa/div-follow-input

    相关文章

      网友评论

        本文标题:div可编辑属性contenteditable实现富文本编辑器

        本文链接:https://www.haomeiwen.com/subject/rovqpdtx.html