只要精神不滑坡,办法总比困难多
前言
当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开头插入元素。
至此,分享完毕。最终还是有个小瑕疵:失去焦点后,无法在末尾插入元素,如果有解决方案的开发者,欢迎评论区留言讨论。
写在最后
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于掘金,未经许可禁止转载💌
评论区