最近在做一个富文本编辑器的项目,是以前彩信编辑器的升级,加入了一些拖拽,音视频图片编辑功能,该项目要求三个类似的编辑器同事开发,代码复用和管理都比较有挑战。其中富文本要支持插入动态参数,它的编辑是一个难点。为此我特意花了时间做了技术调研。
基础功能
为什么要自定义富文本?因为常用的文本域不支持自适应文本高度,看了几个解决方案,界面会闪烁,用户体验不好。最为关键的是,文本域不能插入 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)
}
}
}
测试一下,搞定!这只是万里长征的第一步,后面还有很多需求。
评论 (0)