自定义富文本的 v-model

自定义富文本的 v-model

Flying
2019-03-06 / 0 评论 / 164 阅读 / 正在检测是否收录...

最近在做一个富文本编辑器的项目,是以前彩信编辑器的升级,加入了一些拖拽,音视频图片编辑功能,该项目要求三个类似的编辑器同事开发,代码复用和管理都比较有挑战。其中富文本要支持插入动态参数,它的编辑是一个难点。为此我特意花了时间做了技术调研。

基础功能

为什么要自定义富文本?因为常用的文本域不支持自适应文本高度,看了几个解决方案,界面会闪烁,用户体验不好。最为关键的是,文本域不能插入 html 标签不支持 html 渲染。能满足这两个需求的开源编辑器倒是不少,像 ueditor、CKeditor,但一般比较重,有些杀鸡焉用牛刀的感觉。下面就用 vue 来写一个轻量级的富文本组件,并让它支持 v-model 双向数据绑定。

  • 模板
<template>
  <div
    v-html="html"
    contenteditable="true"
    spellcheck="false"
    class="rich-text"
  >
  </div>
</template>
  • 脚本
export default {
  name: 'RichText',
  data() {
    return {
      html: '<Strong>Hello</Strong>'
    }
  }
}
  • 样式
.rich-text {
  white-space: pre-wrap;
  min-height: 48px;
  padding: 4px 8px;
  box-sizing: border-box;
  outline: none;
  word-break: break-all;
  vertical-align: middle;
  border: 1px solid #ddd;
  border-radius: 0;
  user-select: auto;
}
.rich-text:empty::before{
  content: attr(placeholder);
  color: #CCC;
}

封装组件

通过设置 contenteditable="true",指定 div 元素是可编辑的,使用 v-html 指令,也说明该元素支持 html 的。使用 CSS 伪元素为其添加了占位,比 JavaScript 实现简洁不少。So far so good!为了复用代码,我们接下面将其封装成组件。

要自定义输入框、文本域、下拉框组件的 v-model,通常的做法是组件内部定义 value 属性,派发 input 事件。做下面的修改:

  • 模板
<template>
  <div
    v-html="value"
    contenteditable="true"
    spellcheck="false"
    class="rich-text"
    @keyup="handleKeyup"
  >
  </div>
</template>
  • 脚本
export default {
  name: 'RichText',
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  methods: {
    handleKeyup(event) {
      this.$emit('input', event.target.innerHTML)
    }
  }
}

调用组件

这是调用的代码;

  • 模板
<template>
  <div id="app">
    <rich-text v-model="text"/>
    <pre>{{ text }}</pre>
  </div>
</template>
  • 脚本
import RichText from "./components/RichText";
export default {
  name: 'App',
  components: { RichText },
  data() {
    return {
      text: ''
    }
  }
}

解决问题

测试一下,咦,怎么光标位置不对,都是在文本开头插入。那是因为处理按键事件有一定延迟,UI 重绘之前光标不知道应该定位到何处,所以出了问题。可以在可编辑的元素取得和失去焦点时加个开关处理一下。作如下修改:

  • 模板
<template>
  <div
    v-html="html"
    contenteditable="true"
    spellcheck="false"
    placeholder="请输入文本"
    class="rich-text"
    @focus="locked = true"
    @blur="locked = false"
    @keyup="handleKeyup"
  >
  </div>
</template>
  • 脚本
export default {
  name: 'RichText',
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      html: this.value,
      locked: false
    }
  },
  watch: {
    value(newValue) {
      if (!this.locked) {
        this.html = newValue
      }
    }
  },
  methods: {
    handleKeyup(event) {
      this.$emit('input', event.target.innerHTML)
    }
  }
}

测试一下,搞定!这只是万里长征的第一步,后面还有很多需求。

参考链接

4

评论 (0)

取消