先说前提。
这是极其小几率出现的bug,并不影响一般的网页开发。只是谷歌内核开发的时候才会出现, 基本和前端关系不大。
我们公司有一个pc端的客户端,内嵌了一个谷歌的浏览器。 为了处理某些奇奇怪怪的需求、会按照某些规则屏蔽键盘事件。只有在当前focus可编辑的时候, 才可以使用backspace键这样子, 客户端研发那边使用谷歌的FocusOnEditableField 的API来识别当前的编辑状态
我在使用element重构项目之后,使用了el-select的远程查找、筛选功能,在这里 发现el-select组件全部无法触发FocusOnEditableField了。。。于是开始了dubug之旅。这个api提到 当前focus的元素如果是可编辑状态,就返回true。这让我想起了dom里面很冷门的一个api
contenteditable 属性规定是否可编辑元素的内容。
复制代码
OK, 来试一下。 这里有几个api可以帮我测试一下
document.activeElement 获取当前文档中获得焦点的元素
dom.contentEditable 获取当前元素的contentEditable值
复制代码
mounted () {
document.onkeydown = function (e) {
console.log(document.activeElement.contentEditable)
}
},
复制代码
但是实际打印出来的都是inherit, 看来contentEditable方法也和dom.style
一样只能获得行内的值。 翻翻MDN 找到了这个apiisContentEditable
他可以获取计算之后的contentEditable的值。
OK。再来试试
mounted () {
document.onkeydown = function (e) {
console.log(document.activeElement.isContentEditable)
}
},
复制代码
在select组建中测试返回false。OK。 我们直接通过修改dom属性的方式来看看是不是这个属性的问题。
mounted() {
if (this.$refs.elSelect.$children[0].$options.name === 'ElInput') {
this.$refs.elSelect.$children[0].$refs.input.contentEditable = true
}
},
复制代码
组件加载出来之后,暴力的修改contentEditable属性。。 测试一下, 功能OK, 打完收工~。
当然不可能, 虽然实现了功能,但是有很多疑点啊。 原生的input和elemen库的input都可以正常使用, 应该还有更深层次的问题。。 没办法了, 看源码吧
从package里得知,element通过USE引入的组件都是从\node_modules\element-ui\lib\element-ui.common.js
这里引入的。 package里的是未编译过的组件。 开始翻阅源码
然后一无所获。。
编译过的这个文件虽然可以打断点, 但是没有办法去看具体dom的绑定属性。那就用更简单暴力的方式, 直接从页面上导入element未编译的组建。 node_modules/element-ui/packages/select/src/select.vue
就是他了。 然后项目就挂了。这个组件还引入了其他组件, 其中有一部分使用了jsx。 再该环境太麻烦了。直接把和bug无关的代码全部干掉, 项目总算又跑起来了。
来看看element的select组建, 涨姿势了。
compositionstart、 compositionupdate、compositionend
这三个事件, 新姿势get~。
回归业务,继续找bug。
嗯~~~disabled、readonly
这两个属性比较可疑啊。
删掉readonly果然就OK了。 看来问题出现在这里了。
再看看相关的代码
// 点击父元素的时候
toggleMenu() {
if (!this.selectDisabled) {
if (this.menuVisibleOnFocus) {
this.menuVisibleOnFocus = false;
} else {
this.visible = !this.visible;
}
if (this.visible) {
(this.$refs.input || this.$refs.reference).focus();
}
}
},
复制代码
computed: {
readonly() {
// trade-off for IE input readonly problem: https://github.com/ElemeFE/element/issues/10403
const isIE = !this.$isServer && !isNaN(Number(document.documentMode));
return !this.filterable || this.multiple || !isIE && !this.visible;
},
}
复制代码
逻辑就应该是, 点击父元素的时候, 更改visible的值, 然后通过计算属性计算出readonly, 然后通过v-bind来修改dom的属性。
看来找到原因了,看起来这个操作是同步事件, 实际上dom的更新是异步事件。
而谷歌的FocusOnEditableField逻辑应该是, 有元素进入focus->检查该元素的readonly、contentEditable等属性。
而实际上呢。修改了 readonly -> 调用focus事件 这里并不是同步的, input在只读状态进入了focus。 所以无法触发FocusOnEditableField事件了。
具体的可以参考Vue.nextTick方法
写个demoe来试试
<div style="width: 300px;height: 300px;background-color: pink" @click.stop="toggleMenu">
<input ref="input" type="text" :readonly="readonly">
</div>
复制代码
data () {
return {
readonly: true
}
},
methods: {
toggleMenu() {
this.readonly = false
console.log(this.$refs.input.readOnly )
},
}
复制代码
嗯~果然。第一次点击的时候打印的是true。在改一下
toggleMenu() {
this.readonly = false
this.$nextTick(() => {
console.log(this.$refs.input.readOnly)
this.$refs.input.focus()
})
},
复制代码
总算找到了问题的原因。
望着桌子上的头发, 这波不亏。
ps: 最近在翻阅element的源码, 收获颇丰。element封装的dispatch方法、broadcast方法也给我以后处理vue组建提供了灵感。感谢大佬们的无私奉献。