在Vue项目开发中,我们经常会遇到需要自定义富文本输入场景的需求,此时使用带有contenteditable属性的div会比原生textarea更灵活,但Vue原生的v-model指令仅支持input、textarea等表单元素,无法直接作用于contenteditable div,需要我们手动实现双向绑定的逻辑。

核心实现思路
双向绑定的本质是数据变化时更新DOM内容,DOM内容变化时同步更新数据。对于contenteditable div来说,我们需要监听div的输入事件来同步数据,同时在数据变化时更新div的innerHTML,核心逻辑可以分为两个方向实现。
方案一:通过事件监听直接实现
我们可以在组件内部直接监听contenteditable div的input事件,在事件回调中获取最新内容并更新绑定的数据,同时监听数据变化更新div内容。以下是Vue 3的组合式API实现示例:
<template>
<div>
<div
ref="editableDiv"
contenteditable="true"
@input="handleInput"
style="border: 1px solid #ccc; min-height: 100px; padding: 8px;"
></div>
<p>当前内容:{{ content }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const content = ref('')
const editableDiv = ref(null)
// 监听输入事件同步数据
const handleInput = (e) => {
content.value = e.target.innerHTML
}
// 监听数据变化更新DOM
watch(content, (newVal) => {
if (editableDiv.value && editableDiv.value.innerHTML !== newVal) {
editableDiv.value.innerHTML = newVal
}
})
</script>
这种方式的优点是逻辑直观,不需要额外封装,适合单个组件内使用。但需要注意,直接设置innerHTML会导致光标位置重置,在用户连续输入时体验较差。
方案二:封装自定义指令实现v-model
如果多个组件都需要使用这个能力,封装一个自定义指令会更通用,让contenteditable div可以直接使用v-model指令。以下是Vue 3的自定义指令实现示例:
<template>
<div>
<div
v-model="content"
contenteditable="true"
style="border: 1px solid #ccc; min-height: 100px; padding: 8px;"
></div>
<p>当前内容:{{ content }}</p>
</div>
</template>
<script setup>
import { ref, directive } from 'vue'
const content = ref('')
// 注册自定义指令
const vModel = directive('model', {
// 绑定元素时初始化内容
mounted(el, binding) {
el.innerHTML = binding.value || ''
// 监听输入事件更新数据
const handler = (e) => {
binding.instance[binding.exp] = e.target.innerHTML
}
el.addEventListener('input', handler)
// 保存handler用于卸载时移除
el._vModelHandler = handler
},
// 数据更新时同步DOM
updated(el, binding) {
if (el.innerHTML !== binding.value) {
el.innerHTML = binding.value || ''
}
},
// 卸载时移除事件监听
unmounted(el) {
if (el._vModelHandler) {
el.removeEventListener('input', el._vModelHandler)
}
}
})
</script>
实现注意事项
- 输入法兼容:部分输入法输入过程中会触发多次input事件,需要做防抖处理避免频繁更新数据。
- 光标位置:直接设置innerHTML会导致光标跳转到内容开头,复杂场景下需要额外记录光标位置并恢复。
- 数据对比:更新DOM前需要对比当前内容和目标内容是否一致,避免不必要的DOM操作导致性能问题。
- XSS风险:如果内容来自用户输入,需要对innerHTML的内容做过滤,避免插入恶意脚本。
不同Vue版本适配
如果是Vue 2项目,自定义指令的钩子函数名称和参数会有差异,mounted对应bind,updated对应update,具体可以参考Vue 2的官方指令文档调整实现逻辑,核心的双向绑定思路是一致的。
Vuecontenteditablev_model自定义指令修改时间:2026-06-20 02:57:29