整個界面的CSS樣式還是很好寫的,這里就不再詳述了,首先遇到的一個問題就是輸入@的時候彈出一個@列表,像圖一那樣的,雖然樣式丑了些,但是功能算是實現了。
彈出一個窗口還是很簡單的,無非就是一個div的顯示隱藏嗎,但最主要的是彈出div的位置,要緊挨着@字符,也就是如何計算@字符的位置,這是遇到的第一個難題。
通過萬能的Google,我發現了Caret.js這個庫,下面是作者的描述
Get caret position or offset from inputor
This is the core function that working in At.js.Now, It just become an simple jquery plugin so that everybody can use it.And, of course, At.js is using this plugin too.
support iframe context
描述中還提到了At.js這個玩意兒,一看名字就能夠想到這肯定就是專門為@功能做的一個插件,事實也確實是這樣的,這個庫很強大,基本上幾行代碼就實現了一個@功能,但是我並沒有直接用這個庫,而是只用了Caret.js這個庫來進行定位,為啥不直接用At.js呢,簡單粗暴,省時省力。
說實話,我確實試了試,結果不容樂觀,出現了幾個錯誤,在簡單嘗試解決未果之后,果斷決定自己做一個,然后就是,自己走的路,跪着也要走完。。。欲哭無淚
好了,不多說了,上代碼
<!-- 整個聊天界面的布局 --> <div class="chat-record-area"></div> <div class="chat-drag-area"></div> <div class="chat-function-area"> <a href="#" id="smile" data-placement="vertical">  </a> <a href="#" id="upload-file" data-placement="vertical">  </a>  <div id="smile-container" style="display:none; width: 480px;"></div> </div> <div id="at-container"><ul></ul></div> <div id="chat-input-area" contenteditable="true"></div>
其中,at-container就是@彈出框的div,整個輸入框采用的是div,然后設置contenteditable屬性為true,這樣就可以實現一個簡單的輸入框效果了,但是這還只是一個渣渣,啥都不能干,還得用現成的基於contenteditable的編輯器才行,我這里選擇了wysiwygjs這個編輯器,雖然坑也挺多,但是用起來還可以。
// 初始化輸入框 editor = wysiwyg({ element: 'chat-input-area', // or: document.getElementById('chat-input-area') onKeyPress: function (key, character, shiftKey, altKey, ctrlKey, metaKey) { // 只要有按鍵,就關閉@彈窗 $('#at-container').css('display', 'none'); // 這里省略一坨代碼 if (character === '@') { // 獲取當前光標的位置信息 let offset = $('#chat-input-area').caret('offset'); let position = $('#chat-input-area').caret('position'); // console.log(offset); // console.log(position); let editorWidth = $('#chat-input-area').width(); let atWidth = $('#at-container').width(); // console.log('editor width = ' + editorWidth); // console.log('at div width = ' + atWidth); // 當右側的空間不夠顯示@彈窗時,顯示在左側 // 所以需要對右側剩余空間的大小進行判斷 if (editorWidth - position.left < atWidth) { $('#at-container').css('left', offset.left - atWidth); } else { $('#at-container').css('left', offset.left + 20); } $('#at-container').css('top', offset.top - 185); $('#at-container').css('display', 'block'); $('#at-container').css("position", "absolute"); $('#at-container').scrollTop(0); } } });
這樣,就解決了彈窗的位置問題,其他問題也就出現了,比如說點擊非彈窗區域自動關閉彈窗,按刪除鍵刪除@時,自動關閉彈窗,wysiwyg編輯器的onKeyPress事件大部分按鍵都能夠監聽,但是Backspace(或delete)鍵監聽不到,原因沒有細查,只能額外的去監聽Backspace(或delete)鍵,然后關閉彈窗。
// 按下Backspace(或delete)鍵時 $('#chat-input-area').keyup(function (e) { if (process.platform === 'darwin') { if (e.keyCode == 8) { $('#at-container').css('display', 'none'); } } else { if (e.keyCode == 46) { $('#at-container').css('display', 'none'); } } }); // 點擊@窗體外的區域時,關閉@彈窗 $(document).mouseup(function(e) { var pop = $('#at-container'); if(!pop.is(e.target) && pop.has(e.target).length === 0) { $('#at-container').css('display', 'none'); } });
完成之后,整個彈窗的位置和顯示問題基本上已經解決了,當然,@彈窗里面的內容加載以及布局都比較容易,這里就不再詳述了。
點擊@彈窗里面的列表時,需要在下邊的輸入框中顯示@了某人,這里就遇到了一個問題,一般來說,你@了某人,那這個@+用戶名應該算作一個整體,一個塊,刪除的話需要整體刪除,所以輸入框中的內容不能簡單的是 @+用戶名 這種寫法,需要做某種處理才行。
可以參考下面這篇博文,講的很清楚,遇到的各種問題都有對應的解決辦法,就是有些復雜。
由於我自己所用的環境是Electron,只是基於Chromium引擎,所以需不要考慮Firefox以及IE和Edge瀏覽器,瞬間感覺輕松了很多。
當你輸入了@之后,彈窗才會顯示,這時@字符已經顯示在輸入框中了,而我們要做的就是當選擇了@對象之后,需要在輸入框中插入@的數據,這個數據作為一個整體,是可以整體被刪除的,看了上面的那篇博文之后,你會知道,需要在輸入框中插入<button contenteditable="false">@someone</button>這樣的代碼,contenteditable="false"可以保證button不會被編輯,即可以實現整體刪除的效果,但是隨之而來的是兩個問題:
- 由於@已經輸入了,需要被刪除掉,否則會出現@@someone這種情況
- document.execCommand('insertHTML', false, '<button contenteditable="false" onclick="return false;" class="at" >@${name}</button>')
這種寫法插入html之后,contenteditable="false"會被Chromium自動過濾掉,也就是最終插入的數據中是沒有該屬性的,這就會導致這個button中的內容可以被編輯,這可能是Chromium的一個bug吧
針對上面的兩個問題,有人會說,那我直接把@someone中的someone用button包裹起來不得了嗎,這樣就不會去刪除@了,這樣也是可以的,但是如果這樣做的話,就會出現其他體驗上的問題,比如說你刪掉someone之后,只剩下了@,那@彈窗應該會立即彈出,要不然你就沒辦法再次觸發彈窗了,只能將@手動刪除,然后再輸入,而且如果你@完之后,將輸入框焦點點到@字符后面,要不要重新彈出呢,如果重新彈出的話,那之前已經@成功的那個呢,要不要取消掉呢,隨之而來的是一系列的問題,倒不如直接將@someone作為一個整體,要刪一起刪,要留一起留,這樣整體簡單了很多,不用考慮太多的情況。
針對第一種情況的解決方案:
// @完之后自動刪除@字符 if (window.getSelection) { let range = window.getSelection(); if (range.rangeCount > 0) { // let sel = range.getRangeAt(0); // let startOffset = sel.startOffset; let startOffset = range.extentOffset; // if (range.extentNode) { range.extentNode.replaceData(startOffset - 1, 1, ''); // } else if(range.anchorNode) { // range.anchorNode.replaceData(startOffset - 1, 1, ''); // } } }
第二種情況的解決方案:
// 插入html之后,找到該節點,添加contenteditable屬性 let html = `<button contenteditable="false" onclick='return false;' class="at" data-id="${id}">@${name}</button>`; document.execCommand('insertHTML', false, html) $(`#chat-input-area button[data-id="${id}"]`).attr('contenteditable', false)
但是解決完這兩個問題后,最頭痛的問題又來了:@完之后,輸入框焦點不見了,這個問題如果你看了上面提到的博客之后應該會有了解,由於我比較懶,也沒有去下載他的demo來看,只是單純的看博文,所以代碼片段上有些斷片,在嘗試了他的方案之后,問題還是沒有解決,怎么辦,自己想辦法吧。
問題的原因就在於Chromium只會在文本中才會顯示焦點,如果你輸入框里面只輸入純文本,那沒問題,插入了button,而且button還不能被編輯,能顯示焦點就怪了,無論你怎么點,焦點都不會出現,除非你再輸入純文本,焦點才會出現。
知道原因了,問題也就好解決了,在插入html的時候給它后面添加一個文本內容就好了,順便在前面也添加一個,這樣無論后退還是前進都可以顯示焦點了,上完整代碼:
$('#at-container ul li a').unbind('click').click(function (e) { e.stopPropagation(); let peer = $(this).data('peer'); $('#at-container').css('display', 'none'); if (window.getSelection) { let range = window.getSelection(); if (range.rangeCount > 0) { let sel = range.getRangeAt(0); let startOffset = sel.startOffset; range.anchorNode.replaceData(startOffset - 1, 1, ''); } } let html = `<button contenteditable="false" onclick='return false;' class="at" data-peerid="${peer.peerid}">@${peer.name}</button>`; // 在插入的html前后分別插入一個 防止焦點丟失的問題 document.execCommand('insertHTML', false, '' + html + '') $(`#chat-input-area button[data-peerid="${peer.peerid}"]`).attr('contenteditable', false) $('#chat-input-area').focus(); });
作者:路過麥田
鏈接:https://www.jianshu.com/p/fff5079b1268
來源:簡書