contenteditable联合v-html实现数据双向绑定的vue组件

全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/11466197.html
css

 

先看最终实现的demo效果图html

(1)上面看似文本域的大框是经过给div添加contenteditable=true属性实现的Vue组件DivEditable.vue;vue

(2)下面的输入框是父组件中与DivEditable绑定相同变量的输入框,用于展现数据的双向绑定效果;浏览器

(3)按钮实现绑定变量的赋值操做;ide

(4)DivEditable的blur事件可触发文本过滤或样式的变动等操做(专门留的组件接口);函数

能够看到,DivEditable中值的改变会影响输入框中的值,一样的,输入框中值改变也会影响DivEditable中的值,经过按钮给绑定变量赋值同时触发了输入框及DivEditable中值的改变。学习

 

一、contenteditable属性flex

用于设置或返回元素的内容是否可编辑。:ui

疑问:这时你能够能会想,这么麻烦,怎么不直接使用可编辑元素?好比咱们最多见的有input、textarea。this

解答:但若是你想要在输入的内容中加入html代码,而且还要正常渲染,就要与v-html结合使用,因此咱们只能采用不可编辑元素并为其添加contenteditable为true的属性。

 

二、怎么实现DivEditable数据的双向绑定

犯傻1:一开始我天真的觉得v-html与v-model同样,变量赋值后自带双向绑定,=.=事实证实仍是太嫩;

犯傻2:因而我想那我再加一个v-model不就完事儿了,结果证实仍是太嫩,浏览器直接报错'v-model' directives aren't supported on <div> elements;

最终只能本身上了:

(1)首先能够经过@input事件监听到输入值的变化,此时就能够获取到变化后的值并将其传递给父组件

(2)虽然div不能添加v-model,可是在父组件中我调用DivEditable时却能够为其添加v-model

(3)v-model中传入的值能够在子组件prop中获取的到;

(4)这时你再监听获取到的prop值,并将该值赋值给子组件中的v-html参数,双向绑定就搞定啦。

 

这里引入Vue官方描述:

自定义组件的v-model:一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,v-model的值将会传入子组件中的prop。

具体的可自行学习 → https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model

 

DivEditable.vue组件源码(能够结合我上面的步骤描述看会更容易理解):

<!-- Created by dreamsqin on 2019/9/5 -->
<template>
  <div class="div-editable" contenteditable="true" v-html="innerText" @input="changeText" @focus="isChange = false" @blur="blurFunc"></div>
</template>

<script> export default { name: 'DivEditable', props: { value: { type: String, default: '' } }, data() { return { innerText: this.value, isChange: true } }, watch: { value() { if (this.isChange) { this.innerText = this.value } } }, methods: { changeText() { this.$emit('input', this.$el.innerHTML) }, blurFunc() { this.isChange = true
        this.$emit('blurFunc') } } } </script>

<style lang="scss"> .div-editable{ width: 100%; height: 100%; overflow-y: auto; word-break: break-all; outline: none; user-select: text; white-space: pre-wrap; text-align: left; &[contenteditable=true]{ user-modify: read-write-plaintext-only; &:empty:before { content: attr(placeholder); display: block; color: #ccc;
      } } } </style>

重点说明一下isChange参数的做用:

你能够先尝试拿掉它以及相关逻辑,看看最终会出现什么效果(输入一个字母光标就跑到前面去了,而且输入不了中文);

分析一下缘由:

(1)经过打断点能够看到,当你输入的时候触发input事件,提交值给父组件中的v-model;

(2)但由于在子组件中又监听了v-model的值,因此总体造成了闭环;

(3)还须要重点说明的是光标问题,contenteditable与v-html所在的元素值的改变若是不是经过输入而是经过赋值实现,光标就会跑到最前面;

因此以输入中文为例,你刚打了一个字母,立马就触发了监听与变更,光标移到最前面,天然没法完成整个正常的输入。

解决办法:

只有当blur的时候再作赋值操做(isChange为true),focus状态下不作赋值(isChange为false);

至于初始为true的缘由是在父组件中直接给绑定的变量赋值时子组件中仍是须要触发赋值的(isChange为true);

 

除此以外,我还为组件提供了一个blur事件的函数接口,你能够作一些数据的过滤或者样式的变动,例如我demo中要高亮标签。

 

三、父组件调用

直接上源码:

<template>
  <div class="test-page">
    <div class="contain">
      <div-editable v-model="testContent" @blurFunc="blurHighLight"></div-editable>
      <el-input class="input-style" v-model="testContent"></el-input>
      <el-button class="button-style" @click="changeText">改变值</el-button>
    </div>
  </div>
</template>

<script> import DivEditable from '@/components/DivEditable' export default { name: 'TestPage', data() { return { testContent: 'dreamsqin' } }, components: { DivEditable }, methods: { blurHighLight() { // 这里作数据过滤或样式变动操做 }, changeText() { this.testContent = '【标签1】dreamsqin'
        this.blurHighLight() } } } </script>

<style lang="scss"> .test-page{ height: 100%; display: flex; align-items: center; justify-content: center; .contain{ width: 600px; height: 250px; border: 2px solid #000; .input-style,.button-style{ margin-top: 10px;
      } .text-blue{ color: #2080F7;
      } } } </style>
相关文章
相关标签/搜索