1. 程式人生 > >寫一個可插入自定義標籤的 Textarea 元件

寫一個可插入自定義標籤的 Textarea 元件

- “插入自定義標籤是什麼鬼?”

- “比如你要插入一個<wise></wise>的標籤...”

- “什麼情況下會有這種需求?”

- “得罪了產品的情況下...”

 

一、需求背景

某天,產品找到我,發生瞭如下對話

PM:“哇,研發小哥哥你今天好帥啊~”

我:“說人話。”

PM:“我有一個需求。”

我:“我不聽。”

PM:“我們要給使用者傳送模版訊息。”

我:“找後端小姐姐啊。”

PM:“後端小姐姐讓你在編輯這個欄位的時候,標記對應的模版片語。

我:“怎麼標記?”

PM:“然後我希望在刪除這個模版片語的時候,可以整個刪除。

我:“怎麼刪除?!”

PM:“整個刪除。”

我:(開啟抽屜,亮出我的四十米長刀)

PM:(楚楚可憐的看著我)

我:“做不了。”

PM:(伸出一根手指)

我:“這需求我接了。”

 

十年後...

倉庫地址:https://github.com/wisewrong/vue-tag-textarea

 

二、輸入功能實現

為了實現這個功能,我最先想的是改造一個 <textarea>

然後我想到了 contenteditable (連結指向 mozilla.org) 這一屬性

這是一個 html5 的屬性,可以讓元素內容可編輯

<p contenteditable="true">這是一個可編輯的段落。</p>

但這樣改造之後的輸入框,在貼上的時候會帶上文字格式,即富文字

所以最後我放棄了該屬性,採用 CSS 的解決方案:-webkit-user-modify

這個屬性有四個可選值:

read-only:預設值,元素只讀,不可編輯;

read-write:可以編輯,支援富文字;

read-write-plaintext-only:可以編輯,不支援富文字;

write-only:使元素僅用於編輯(幾乎沒有瀏覽器支援)

 

所以最後我給輸入框添加了這樣一行 CSS 屬性:

-webkit-user-modify: read-write-plaintext-only !important;

 

三、實現單向繫結

一個常規的輸入框元件,父元件可以通過 v-model 指令雙向繫結資料

v-model 其實是一個語法糖,它向子元件傳遞一個 value 屬性,並接收一個 input 事件

所以對於父元件來說:

等價於:

 

而子元件為了支援 v-model,需要在 props 裡定一個 value 屬性

 

並且在合適的時候觸發 this.$emit('input', value)

 

在這個 tag-textarea 元件中,我在輸入框上監聽 input 事件,獲取元素內的 innerHTML,並暴露給父元件

 

到這裡,還僅僅實現了單項繫結——子元件的值改變時,父元件的值隨之改變

要實現真正意義上的雙向繫結,還有一段路要走

 

四、完成雙向繫結

首先需要在 data 定義一個 currentText

如果是普通的輸入框,直接在輸入框元素上使用 v-text="currentText

然後在 watch 中監聽 value 的變化,實時更新 currentText,就能實現雙向繫結

 

但這個 textarea 返回的是 html,v-text 是不能用了

如果用 v-html 的話,在輸入的時候,游標會一直跳到最前方,最後我採用了以下方案:

在 data 新增一個 isLocked 用於記錄鎖定狀態 

在 mounted() 的時候,通過 dom 操作初始化資料 

在聚焦和失焦的時候修改鎖定狀態

看,很簡單吧,沒有人會受傷的世界 一個基本的 textarea,完成了

 

五、插入標籤

建立標籤並不難,可如何讓這個標籤插入到游標所在的位置?

於是 Selection 物件閃亮登場,它記錄了拖藍的文字範圍,或插入符號的當前位置

通過 Selection 物件可以獲取到 Range 物件,然後使用 Range.insertNode() 方法,在目標位置插入標籤

在 mounted() 中監聽 selectionchange 事件,新增對應的處理函式,並在 beforeDestroy() 的時候解除安裝

在處理函式中,記錄當前的 range

 

新增標籤的時候,首先通過 document.createElement() 建立標籤,然後插入節點

 

六、刪除標籤

刪除分為游標位於標籤外和標籤內兩種情況

首先是當游標在標籤外的時候,有一個取巧的辦法

給模版標籤新增樣式,將 -webkit-user-modify 設定為 read-only

這樣在刪除的時候,因為無法編輯,就會直接刪除整個 dom 節點

 

當游標位於標籤內的時候,會稍微複雜一點

首先需要監聽 click 事件,當點選模版標籤的時候,記錄其 id

然後監聽 keydown.delete 事件,如果選中了標籤,就使用 removeChild() 刪除標籤

 

以上,就已經滿足了產品的基本需求

不過既然是開發元件,就需要做一些適合元件開發的優化

比如 props 、slot 、 css樣式等,這裡就不多贅述了

 

最後,元旦快樂~