全手打原創,轉載請標明出處: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。
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>