在 Vue.js 框架中使用 contenteditable 元素

本文将按照如下要点展开

  • contenteditable 元素最基本的使用
  • 在 Vue.js 中使用 v-model+contenteditable
  • 中文输入法带来的问题及解决
  • 占位提示的处理
  • 可以直接使用已经分装好的组件

contenteditable 元素最基本的使用#

示例如下:

See the Pen simple-contenteditable by Marshal Wu (@marshalw) on CodePen.

示例中的 div 元素,默认为空,为了演示方便,在没有内容的情况下,通过 css 伪类增加了占位提示信息。

Vue.js v-model+contenteditable 实现的演进过程#

在实现最终解决方案过程中,是逐步发现问题逐步演进的,以下给出这个过程,既方便读者理解,也是为了验算和整理实现思路。

初步实现 v-model 和 contenteditable 子组件双向绑定(有不可用的错误)#

使用 Vue.js 框架,可通过 v-model 将父组件的 data 绑定到子组件。

下面示例演示一个子组件,封装 contenteditable 的元素,使用父组件 v-model 传递的属性,并实现双向绑定(这个示例请关注双向绑定,不要在意出现的错误):

See the Pen vue-simple-contenteditable_1 by Marshal Wu (@marshalw) on CodePen.

可以看到,使用 vue 官方 自定义组件的 v-model是可以实现初步功能的。

出现的错误是因为直接修改父组件 data 后,子组件重新渲染,光标回到了开始的地方。

解决问题,子组件使用自己的 data(还是有问题)#

解决上文的问题,可以让子组件有自己的 data 对象,父子间使用各自的 data 对象。

示例如下:

See the Pen vue-simple-contenteditable_2 by Marshal Wu (@marshalw) on CodePen.

解决了光标位置的问题,而且可以输入中文,但还是有问题,父组件的数据如果在其他地方修改,无法在子组件中同步修改。

记录光标位置的解决方案(基本解决问题,除了不能输入中文 && 不能回车)#

如果根据上面的问题,在子组件中增加 watch 父组件 data 的方式,就又会回到第一个示例的问题。

1
2
3
4
5
watch:{
content:function(val){
this.theContent=this.content
}
..

示例 2,似乎在很多场合下是可用的,只要不在其他地方修改父组件 data 就好了。

但为了让代码遵循规则,保持一致性,维护架构,必须考虑更好的解决办法。

准备调整如下内容:

  • 回退回第一个示例,子组件不再维护自己的 data
  • 为了保证光标位置在刷新子组件后在正确的位置,需要记录当前光标位置,重新渲染后回到之前的位置

See the Pen vue-simple-contenteditable_3 by Marshal Wu (@marshalw) on CodePen.

回避回车带来的问题#

回车问题比较复杂,我在实际应用中其实只需要单行 contenteditable 元素。

因此监听回车键后让 contenteditable 元素失去焦点,回避了这个问题的复杂性。

See the Pen vue-simple-contenteditable_4 by Marshal Wu (@marshalw) on CodePen.

即,通过增加 @keydown.enter.prevent="$event.target.blur()" 监听回车键按下事件,让元素失去焦点

解决中文输入法的问题#

解决中文输入法问题(也适用于其他比如日语)的基本思路是:

  • 当开始中文输入法时停止执行 input 事件处理
  • 当结束中文输入法时执行input 事件处理类似的逻辑

查到对应的事件处理:

实现如下:

See the Pen vue-simple-contenteditable_5 by Marshal Wu (@marshalw) on CodePen.

总结#

主要问题及解决思路#

使用 contenteditable 存在以下问题及解决办法

  • 希望能 v-model,这样调用友好,会带来在 contenteditable 元素中输入光标位置问题
    • 如果让 contenteditable 元素自己保留 data,再通过 $emit 触发改变父组件 data,虽然解决问题,但父组件 data 在其他地方改变不会同步出现在contenteditable 元素,因此不可取;
    • 想办法解决光标位置问题,键盘输入字符后记录光标位置,等重新渲染 contenteditable 元素后再回到光标位置,目前看可以解决问题而基本没有副作用(界面可以察觉到刷新光标闪一下,这是目前唯一的轻微问题)
  • 中文输入问题,现代浏览器有对间接输入法的开始和结束事件,借助这个可以很好的解决中文输入问题

发布了一个可直接使用的单行可编辑文本标签库#

根据上文的步骤,发布了一个可单独使用的 Vue 组件: