摘要:最近為實(shí)現(xiàn)一個(gè)新功能弄的焦頭爛額的實(shí)現(xiàn),在實(shí)現(xiàn)后寫下些心得,供以后會跳入這坑的同志們參考。選人實(shí)現(xiàn)主要涉及步驟為。需要修改的代碼,保存選區(qū)以及光標(biāo)信息,用于獲取在光標(biāo)焦點(diǎn)離開前,光標(biāo)的位置刪除符號。這樣就完成這一功能了。
最近為實(shí)現(xiàn)一個(gè)新功能弄的焦頭爛額 @xxx 的實(shí)現(xiàn),在實(shí)現(xiàn)后寫下些心得,供以后會跳入這坑的同志們參考。
首先,當(dāng)讓是考慮使用范圍,由于項(xiàng)目僅僅需要考慮在 WEBKIT 環(huán)境下使用,所以可以不用考慮 IE 這也使得代碼少了很多的 if(){}else{} 判斷。在Mozilla 開發(fā)者網(wǎng)絡(luò)上發(fā)現(xiàn) selection 和 range 這兩個(gè)關(guān)于選區(qū)對象和光標(biāo)對象,結(jié)合 Caret(一個(gè)用于判斷當(dāng)前光標(biāo)位置的JS插件)后,一個(gè)大致的雛形就浮現(xiàn)出來。
大概就長這樣:
先整理思路,捋一捋實(shí)現(xiàn)步驟。
大致思路如下:
鍵入 @ 后將選擇框顯示出來
將焦點(diǎn)定位在彈出框中的搜索框中
點(diǎn)擊選擇框中的選項(xiàng)時(shí),返回輸入框
輸入框中顯示 @xxx
將光標(biāo)定位在 @xxx 之后
刪除 @xxx 時(shí)需要整個(gè) @xxx 一起刪除
由于項(xiàng)目使用了 angular 來構(gòu)建,所以給的 demo 也是用 angular 來搭建的,但是不論用什么框架,想法有了,那么一切就好辦了。
selection 和 range 對象的具體使用請參考 MDN 上的相關(guān)文章:
selection
range
DEMO頁
主要涉及的幾個(gè)方法:
getSelection(window.getSelectio):獲取光標(biāo)所在的區(qū)域(一個(gè)div或是一個(gè)textarea);
selection.getRangeAt:獲取光標(biāo)所在區(qū)域中光標(biāo)選區(qū)的信息;
range.setStart:設(shè)置光標(biāo)選區(qū)的起始位置;
range.setEnd:設(shè)置光標(biāo)選區(qū)的結(jié)束位置;
range.deleteContents:將光標(biāo)選區(qū)選中的內(nèi)容刪除;
range.insertNode:在光標(biāo)選區(qū)中添加內(nèi)容;
selection.extend:將選區(qū)的焦點(diǎn)移動(dòng)到一個(gè)特定的位置;
selection.collapseToEnd:將當(dāng)前的選區(qū)折疊到最末尾的一個(gè)點(diǎn)。
html 結(jié)構(gòu)
所有人
樣式相關(guān)的CSS代碼就不放上來了,簡要分析下頁面結(jié)構(gòu),一個(gè) contenteditable="true" 的輸入框和一個(gè) id="selectPerson" 的選人框。
輸入框使用 contenteditable="true" 主要是因?yàn)橄朐谳斎肟蛑胁迦霕?biāo)簽,將 @xxx 內(nèi)容顯示出不同的顏色(這就需要將 @xxx 放在一個(gè)標(biāo)簽中),綁定 keyIn 的鍵盤輸入事件,用于檢索用戶輸入 @ 和 backspace ,并做出相應(yīng)的動(dòng)作;
選人框使用 showSelect 來控制是否顯示,遍歷顯示需要顯示的選人,以及使用 input 中的內(nèi)容來過濾選人。
實(shí)現(xiàn) @ 選擇相關(guān)代碼如下:
$scope.keyIn = function(e) { var selection = getSelection(); var ele = $("#demo"); if (e.code == "Digit2" && e.shiftKey) { $scope.showSelect = true; var offset = ele.caret("offset"); $scope.sPersonPosi = { left: offset.left - 10 + "px", top: offset.top + 20 + "px" }; // 讓選人框中的搜索框獲取焦點(diǎn) $timeout(function(){ $("#searchPersonInput")[0].focus(); }) } }
實(shí)現(xiàn)起來挺簡單,代碼也不復(fù)雜,利用 caret 插件獲取到光標(biāo)位置,將選人框在 @ 符號的下方顯示出來,并同時(shí)實(shí)現(xiàn)了步驟中的第二步:將焦點(diǎn)放在搜索框中。
選人實(shí)現(xiàn)主要涉及步驟為:3、4、5。
當(dāng)鼠標(biāo)點(diǎn)擊備選項(xiàng)時(shí)需要按順序進(jìn)行 3、4、5 步驟,所以需將 3、4、5 這 3 個(gè)步驟放在一起。
相關(guān)代碼如下:
$scope.sPersonDone = function(person) { // 成功選人后,關(guān)閉選擇框,讓輸入框獲取焦點(diǎn)。 $scope.showSelect = false; var ele = $("#demo")[0]; ele.focus(); // 獲取之前保留先來的信息。 // 需要修改 keyIn 的代碼,保存選區(qū)以及光標(biāo)信息,用于獲取在光標(biāo)焦點(diǎn)離開前,光標(biāo)的位置 var selection = lastSelection.selection; var range = lastSelection.range; var textNode = range.startContainer; // 刪除 @ 符號。 range.setStart(textNode, range.endOffset); range.setEnd(textNode, range.endOffset + 1); range.deleteContents(); // 生成需要顯示的內(nèi)容,包括一個(gè) span 和一個(gè)空格。 var spanNode1 = document.createElement("span"); var spanNode2 = document.createElement("span"); spanNode1.className = "at-text"; spanNode1.innerHTML = "@" + person.fullName; spanNode2.innerHTML = " "; // 將生成內(nèi)容打包放在 Fragment 中,并獲取生成內(nèi)容的最后一個(gè)節(jié)點(diǎn),也就是空格。 var frag = document.createDocumentFragment(), node, lastNode; frag.appendChild(spanNode1); while ((node = spanNode2.firstChild)) { lastNode = frag.appendChild(node); } // 將 Fragment 中的內(nèi)容放入 range 中,并將光標(biāo)放在空格之后。 range.insertNode(frag); selection.extend(lastNode, 1); selection.collapseToEnd(); };
我們需要的效果是在 @ 選人后,將整理好的 @xxx 包裝成一個(gè)標(biāo)簽,放在原先 @ 的位置,所以我們需要對原先的 $scope.keyIn 方法進(jìn)行改造,保留原先的光標(biāo)信息,方便在上面的方法中使用。
改造后的 $scope.keyIn 方法如下:
$scope.keyIn = function(e) { var selection = getSelection(); var ele = $("#demo"); if (e.code == "Digit2" && e.shiftKey) { $scope.showSelect = true; // 保存光標(biāo)信息 lastSelection = { range: selection.getRangeAt(0), offset: selection.focusOffset, selection: selection }; $scope.showSelect = true; // 設(shè)置彈出框位置 var offset = ele.caret("offset"); $scope.sPersonPosi = { left: offset.left - 10 + "px", top: offset.top + 20 + "px" }; $timeout(function(){ $("#searchPersonInput")[0].focus(); }) } }
這里估計(jì)挺多人會有疑問,為啥要在生成的標(biāo)簽后面加一個(gè)空格,而且這個(gè)空格要通過 這樣的方式實(shí)現(xiàn)。
首先,先解釋第一個(gè)問題:為啥要在標(biāo)簽后加一個(gè)空格?
如果不加空格的話,之后在輸入文字會添加在我們生成的標(biāo)簽中,也就是說如果不加空格來隔斷我們生成的標(biāo)簽,我們在文本框里所做的操作就是在我們生成的標(biāo)簽中進(jìn)行。而加了個(gè)空格就為了避免該問題的發(fā)生,使得文本編輯在正確的編輯框中進(jìn)行。
第二個(gè)問題:為啥不能直接加空格 " " ,而是通過 ,不得不說這是個(gè)過個(gè)悲傷的事實(shí),還是碰到了兼容性的問題,在 chrome 下運(yùn)行好好的代碼,在 node-webkit 中就會各種報(bào)錯(cuò)。原因在不斷的 defug 后發(fā)現(xiàn)了: node-webkit 中,將一個(gè) " " 添加到 contenteditable="true" 的 div 中會沒有啊,坑爹啊有木有?。?!呈上之前的代碼來祭奠下。
var spanNode1 = document.createElement("span"); var node = document.createTextNode(" "); spanNode1.className = "at-text"; spanNode1.innerHTML = "@" + person.fullName; var frag = document.createDocumentFragment(); frag.appendChild(spanNode1); frag.appendChild(node); range.insertNode(frag); selection.extend(node, 1);
結(jié)果一上 node-webkit 環(huán)境各種報(bào)錯(cuò)。真是坑了個(gè)大爹。原因是光標(biāo)定位不準(zhǔn),指定位置超出實(shí)際位置,但是 node-webkit 環(huán)境確實(shí)是可以輸入空格的,一看原來是 而 不能通過 createTextNode 來創(chuàng)建,所以就有了之前的哪個(gè)曲線救國的策略了。
刪除實(shí)現(xiàn)終于捋到最后一個(gè)步驟了,刪除時(shí),需要將一整個(gè)標(biāo)簽一起刪除。由于需要監(jiān)聽鍵盤的輸入,所以就可與之前 keyIn 的代碼寫在一起。
最終的 keyIn 代碼為:
$scope.keyIn = function(e) { var selection = getSelection(); var ele = document.getElementById("demo"); if (e.code == "Digit2" && e.shiftKey) { // 保存光標(biāo)信息 lastSelection = { range: selection.getRangeAt(0), offset: selection.focusOffset, selection: selection }; $scope.showSelect = true; // 設(shè)置彈出框位置 var offset = $(ele).caret("offset"); $scope.sPersonPosi = { left: offset.left + "px", top: offset.top + 30 + "px" }; $timeout(function(){ $("#searchPersonInput")[0].focus(); }) } else if (e.code == "Backspace") { // 刪除邏輯 // 1 :由于在創(chuàng)建時(shí)默認(rèn)會在 @xxx 后添加一個(gè)空格, // 所以當(dāng)?shù)弥鈽?biāo)位于 @xxx 之后的一個(gè)第一個(gè)字符后并按下刪除按鈕時(shí), // 應(yīng)該將光標(biāo)前的 @xxx 給刪除 // 2 :當(dāng)光標(biāo)位于 @xxx 中間時(shí),按下刪除按鈕時(shí)應(yīng)該將整個(gè) @xxx 給刪除。 var range = selection.getRangeAt(0); var removeNode = null; if (range.startOffset <= 1 && range.startContainer.parentElement.className != "at-text") removeNode = range.startContainer.previousElementSibling; if (range.startContainer.parentElement.className == "at-text") removeNode = range.startContainer.parentElement; if (removeNode) ele.removeChild(removeNode); } };
代碼的邏輯都寫在注釋里了,這里就不多說了。
這樣就完成 @ 這一功能了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/86537.html
摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡的前端持續(xù)集成的開發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...
摘要:因?yàn)楣ぷ髦幸恢痹谑褂?,也一直以來想總結(jié)一下自己關(guān)于的一些知識經(jīng)驗(yàn)。于是把一些想法慢慢整理書寫下來,做成一本開源免費(fèi)專業(yè)簡單的入門級別的小書,提供給社區(qū)。本書的后續(xù)可能會做成視頻版本,敬請期待。本作品采用署名禁止演繹國際許可協(xié)議進(jìn)行許可 React.js 小書 本文作者:胡子大哈本文原文:React.js 小書 轉(zhuǎn)載請注明出處,保留原文鏈接以及作者信息 在線閱讀:http://huzi...
摘要:騰訊空間超分辨率技術(shù)為用戶節(jié)省流量,處理效果和速度超谷歌技術(shù)在的標(biāo)準(zhǔn)下,處理速度在提升了,處理效果也有明顯提升。此外,也是業(yè)界首次實(shí)現(xiàn)移動(dòng)端使用深度神經(jīng)網(wǎng)絡(luò)進(jìn)行超分辨率,并保證圖片能夠?qū)崟r(shí)進(jìn)行處理。值得一提的是的對應(yīng)指標(biāo)也在名單里。 團(tuán)隊(duì)分享 魔幻語言 JavaScript 系列之 call、bind 以及上下文 從一行代碼來看看 JavaScript 是一門多么魔幻的語言,順便談?wù)?...
摘要:的語言的動(dòng)態(tài)性意味著我們可以使用以上種數(shù)據(jù)類型表示變換過渡動(dòng)畫實(shí)現(xiàn)案例前端掘金以下所有效果的實(shí)現(xiàn)方式均為個(gè)人見解,如有不對的地方還請一一指出。 讀 zepto 源碼之工具函數(shù) - 掘金Zepto 提供了豐富的工具函數(shù),下面來一一解讀。 源碼版本 本文閱讀的源碼為 zepto1.2.0 $.extend $.extend 方法可以用來擴(kuò)展目標(biāo)對象的屬性。目標(biāo)對象的同名屬性會被源對象的屬性...
閱讀 2885·2023-04-26 01:47
閱讀 3658·2023-04-25 23:45
閱讀 2563·2021-10-13 09:39
閱讀 663·2021-10-09 09:44
閱讀 1880·2021-09-22 15:59
閱讀 2896·2021-09-13 10:33
閱讀 1846·2021-09-03 10:30
閱讀 704·2019-08-30 15:53