模擬可編輯div輸入域同時輸入文字表情
近期遇到了模擬可編輯div輸入域同時輸入文字表情的需求,本來還覺得很好做,但是在具體實施的過程中遇到了一點問題。
第一個比較簡單的問題是表情和對應字符的映射關系,這部分比較好做,沒有用富文本框也沒有用編輯器,做了表情和相應字符的對應關系只有就可以實現這個需求。
第二個問題在解決過程中就比較棘手了,因為是自己模擬輸入域,所以對於在文字中加入表情後光標的定位及後續的輸入是有要求的,就是得符合正常的輸入習慣。這個問題的核心就是對於node節點的操作和光標對象的熟悉程度及其內部屬性和方法的使用。對於前端開發者來說其實是很基礎的一個知識點(此處羞愧臉)。不熟悉的同學其實也不要有心裏負擔,其實本來這寫知識點及其使用網上一大推,但是不一定能夠滿足自己要的需求。對於這第二個問題來說,我的需求就是,在任何地方都可以做到想插入文字一樣插入表情,而且光標像插入文字一樣定位到表情後面。這麽說淺顯易懂吧。還不懂的話上圖來理解一下
就是這樣,將光標定位到“の”後面,插入表情,然後光標位置定位在表情後面。會的人會覺得很簡單(膜拜大佬),不會的也有思路:就是監聽光標位置,首先記錄光標的初始位置,然後插入表情,然後計算長度,接著在定位光標。剛開始我就是這麽想的,但是做了你就會發現一個問題,對於range對象來說對於非文本節點的計算光標是一個大問題。那麽接下來我們來普及一下range這個對象(好開心,又認識了一個對象??)。
這是對range的定義:
Range 對象
Range 對象表示文檔的連續範圍區域,如用戶在瀏覽器窗口中用鼠標拖動選中的區域。
Selection對象
所對應的是用戶所選擇的 ranges
大致上來說range和selection都是區域
range裏的屬性等等還可以操作光標,做一些索引,位置的定位,輸出等,這樣你就可以在特定位置插入你想插入的節點了。
我們假設你已經做好了表情和對應字符的轉換,列如對應的是[微笑]。而且我們假設你可以選取一段文字中光標的位置。接下來你講在這個位置插入表情符號,成功了。這時候有人就說了,你這不廢話麽,肯定成功啊,如此清晰明確的邏輯和操作。但是如果看效果的話,你接下來插入的表情一般都不會成功。因為光標只會讀取文本節點,當你插入了非文本節點。在次定位光標時,問題就在於,你插入的非文本節點會對你光標位置的輸出造成幹擾。也就是說不能指哪打哪。我們來看以下幾種情況:
這是光標定位在1111111的第四個1之後的selection。我們可以通過focusOffset等諸多屬性來在確切位置插入。完全沒問題。但是註意看data屬性,值是1111111,只是部分文本節點,這很符合邏輯,因為1111111和222222本來就是兩個文本節點,沒有normalize在一起。
接下來這種情況
輸出的data,嗯,沒毛病,是222222, anchorOffset是3????思考一下,你會發現,是根據當前定位節點來計算的,沒毛病。那插入位置只能你自己計算嘍,加上第一個文本節點,在加上第二個元素的長度,嗯,然後定位光標。居然發現報錯了,這是因為把第二個元素的長度完全解析成了img標簽的長度,所以,沒一個文本節點的光標都是從零開始,並且非文本節點還不計算在內。經過我的努力,用這種數字的定位是解決不了的。
找了一些可以規避這個問題的方法,發現最優的可以將表情插入,並且光標的位置定位在表情後面,但是不能做到在任何位置插入表情。找了半天,找到了不同點。但是參考別人做的,和我的不一樣應該是我用了一種取巧的辦法。下面上代碼:
function insertHtmlAtCaret(html) {
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
}
$(".abc").click(function (){
// $(".abc").trigger(‘click‘);
document.getElementsByClassName(‘test‘)[0].focus(); insertHtmlAtCaret(‘<img src="wdwe.png">‘);
});
insertHtmlAtCaret方法是個黑箱,想了解內容的具體邏輯可言去學習一下。但是剛開始的時候我用這個方法也遇到了一點問題。點擊的表情總是會加在內容的最前面。這裏有一個註意點,那就是類名為abc的元素必須是input type=‘button‘的元素。
希望遇到和我一樣問題的人,看到了這篇文章會對你的編碼有幫助,如果有不明白的歡迎一起探討。
模擬可編輯div輸入域同時輸入文字表情