阅读 2676

Vue实现可编辑div获取焦点🎉

只要精神不滑坡,办法总比困难多

前言

当div设置contenteditable属性实现可编辑时,可以调用document.execCommand方法向可编辑div中插入内容。如果可编辑div没有点击或者失去了焦点,都无法实现将元素插入到可编辑div中,接下来就跟大家分享下,如何让div获取焦点,解决这一BUG。先看下我们最终实现的效果:

实现思路

上述gif图中表情插入div的具体实现,可阅读我的另一篇文章: Vue实现图片与文字混输

  • 定义一个函数实现div获取焦点
  • 给可编辑div元素绑定id属性
  • 通过document.querySelector方法获取可编辑div元素
  • 拿到可编辑div元素后调用focus方法实现div焦点的获取
  • 点击表情图标时绑定点击事件指向第一步声明的函数

实现过程

在实现之前我们先看看掘金的发沸点功能,入图所示:掘金用的也是可编辑div实现,点击表情图标时,他也用了和我同样的方法让div获取焦点,然后插入图片到可编辑div。

  • 可编辑div设置id
<div id="msgInputContainer" class="input-panel" contenteditable="true" spellcheck="false">

</div>
复制代码
  • 实现获取焦点函数
getEditableDivFocus: function () {
    document.querySelector('#msgInputContainer').focus();
}
复制代码
  • 工具栏绑定toolbarSwitch函数
    <div class="item-panel" v-for="item in toolbarList" :key="item.info">
        <img class="emoticon" :src="require(`../assets/img/${item.src}`)"
             @mouseenter="toolbarSwitch('hover',$event,item.src,item.hover,item.down,item.name)"
             @mouseleave="toolbarSwitch('leave',$event,item.src,item.hover,item.down,item.name)"
             @mousedown="toolbarSwitch('down',$event,item.src,item.hover,item.down,item.name)"
             @mouseup="toolbarSwitch('up',$event,item.src,item.hover,item.down,item.name)" :alt="item.info">
    </div> 
复制代码
  • toolbarSwitch函数实现:拦截表情图标,执行获取焦点函数。
    toolbarSwitch: function (status, event, path, hoverPath, downPath, toolItemName) {
      if (status === "hover" || status === "up") {
        event.target.src = require(`../assets/img/${hoverPath}`);
      } else if (status === "leave") {
        event.target.src = require(`../assets/img/${path}`);
      } else {
        // 可编辑div获取焦点
        this.getEditableDivFocus();
        event.target.src = require(`../assets/img/${downPath}`);
        // 表情框显示条件
        if (toolItemName === "emoticon") {
          if (this.emoticonShowStatus === "flex") {
            this.emoticonShowStatus = "none";
          } else {
            this.emoticonShowStatus = "flex";
          }
        } else {
          this.emoticonShowStatus = "none";
        }
      }
    },
复制代码

踩坑记录

使用tabindex="0"实现获取焦点

如图所示,百度搜到的解决方案,大都指向的tabindex="0,实现的

  • 阅读量挺高的一篇文章,设置tabindex="0",按tab来实现获取焦点
  • 给可编辑div添加tabindex="0"
<div id="msgInputContainer" class="input-panel" contenteditable="true" spellcheck="false" tabindex="0">

</div>
复制代码
  • 实现模拟按键函数
  fireKeyEvent: function (el, evtType, keyCode) {
    let doc = el.ownerDocument,
            win = doc.defaultView || doc.parentWindow,
            evtObj;
    if (doc.createEvent) {
      if (win.KeyEvent) {
        evtObj = doc.createEvent('KeyEvents');
        evtObj.initKeyEvent(evtType, true, true, win, false, false, false, false, keyCode, 0);
      } else {
        evtObj = doc.createEvent('UIEvents');
        Object.defineProperty(evtObj, 'keyCode', {
          get: function () {
            return this.keyCodeVal;
          }
        });
        Object.defineProperty(evtObj, 'which', {
          get: function () {
            return this.keyCodeVal;
          }
        });
        evtObj.initUIEvent(evtType, true, true, win, 1);
        evtObj.keyCodeVal = keyCode;
        if (evtObj.keyCode !== keyCode) {
          console.log("keyCode " + evtObj.keyCode + " 和 (" + evtObj.which + ") 不匹配");
        }
      }
      el.dispatchEvent(evtObj);
    } else if (doc.createEventObject) {
      evtObj = doc.createEventObject();
      evtObj.keyCode = keyCode;
      el.fireEvent('on' + evtType, evtObj);
    }
  },
复制代码
  • 触发tab按键
 // 可编辑div获取焦点
 getEditableDivFocus: function () {
    this.fireKeyEvent(this.$refs.msgInputContainer, 'tab', 9);
  }
复制代码
  • 执行结果:失败,无法执行tab事件。

使用HTML5的range对象来实现

  • 可编辑div获取焦点函数
  getEditableDivFocus: function () {
    // 获取可编辑div
    let srcObj = document.querySelector('#msgInputContainer');
    // 获取当前光标的位置
    let selection = window.getSelection();
    // 创建Range对象
    let range = document.createRange();
    // range对象选择可编辑div元素
    range.selectNodeContents(srcObj);
    // 移除所有的range对象
    selection.removeAllRanges();
    // 增加当前range对象到selection选区中
    selection.addRange(range);
    // 设置range对象开始位置
    range.setStart(srcObj, 1);
    // 设置range对象结束位置
    range.setEnd(srcObj, 1);
  }
复制代码
  • 可以实现获取焦点
  • 缺点(如图所示):可编辑div里必须要有内容,只能在text内容后面插入,无法在img元素内容后插入。

抓到元素后使用focus()获取焦点

  • 获取焦点函数
getEditableDivFocus: function () {
    document.querySelector('#msgInputContainer').focus();
}
复制代码
  • 可以实现获取焦点
  • 缺点(如图所示):失去焦点后,再次获取,只能在可编辑div开头插入元素,掘金也有这个问题。

我的项目:在线体验地址 | github项目地址

掘金的发沸点

每种实现方法的优点与缺点

  • 使用range对象实现:
可编辑div必须有内容,插入元素也只能在文字后面。

优点:
   可以实现在文字后面插入元素

缺点:
   可编辑div中必须要有内容,无法实现在其他元素后面插入
复制代码
  • 使用focus()实现
可编辑div必须有内容,插入元素也只能在文字后面。

优点:
   性能相比range对象好,不失去焦点的情况下可以在焦点位置插入元素

缺点:
   失去焦点后,只能在可编辑div开头插入元素。
复制代码

至此,分享完毕。最终还是有个小瑕疵:失去焦点后,无法在末尾插入元素,如果有解决方案的开发者,欢迎评论区留言讨论。

写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于掘金,未经许可禁止转载💌
文章分类
前端
文章标签