成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

web 前端 @ 功能 JS 實現(xiàn)分析及其原理

jsyzchen / 839人閱讀

摘要:最近為實現(xiàn)一個新功能弄的焦頭爛額的實現(xiàn),在實現(xiàn)后寫下些心得,供以后會跳入這坑的同志們參考。選人實現(xiàn)主要涉及步驟為。需要修改的代碼,保存選區(qū)以及光標信息,用于獲取在光標焦點離開前,光標的位置刪除符號。這樣就完成這一功能了。

最近為實現(xiàn)一個新功能弄的焦頭爛額 @xxx 的實現(xiàn),在實現(xiàn)后寫下些心得,供以后會跳入這坑的同志們參考。

首先,當讓是考慮使用范圍,由于項目僅僅需要考慮在 WEBKIT 環(huán)境下使用,所以可以不用考慮 IE 這也使得代碼少了很多的 if(){}else{} 判斷。在Mozilla 開發(fā)者網(wǎng)絡上發(fā)現(xiàn) selectionrange 這兩個關(guān)于選區(qū)對象和光標對象,結(jié)合 Caret(一個用于判斷當前光標位置的JS插件)后,一個大致的雛形就浮現(xiàn)出來。

大概就長這樣:

先整理思路,捋一捋實現(xiàn)步驟。

大致思路如下:

鍵入 @ 后將選擇框顯示出來

將焦點定位在彈出框中的搜索框中

點擊選擇框中的選項時,返回輸入框

輸入框中顯示 @xxx

將光標定位在 @xxx 之后

刪除 @xxx 時需要整個 @xxx 一起刪除

由于項目使用了 angular 來構(gòu)建,所以給的 demo 也是用 angular 來搭建的,但是不論用什么框架,想法有了,那么一切就好辦了。

selectionrange 對象的具體使用請參考 MDN 上的相關(guān)文章:

selection

range

DEMO頁

主要涉及的幾個方法:

getSelection(window.getSelectio):獲取光標所在的區(qū)域(一個div或是一個textarea);

selection.getRangeAt:獲取光標所在區(qū)域中光標選區(qū)的信息;

range.setStart:設置光標選區(qū)的起始位置;

range.setEnd:設置光標選區(qū)的結(jié)束位置;

range.deleteContents:將光標選區(qū)選中的內(nèi)容刪除;

range.insertNode:在光標選區(qū)中添加內(nèi)容;

selection.extend:將選區(qū)的焦點移動到一個特定的位置;

selection.collapseToEnd:將當前的選區(qū)折疊到最末尾的一個點。

html 結(jié)構(gòu)
  • 所有人

樣式相關(guān)的CSS代碼就不放上來了,簡要分析下頁面結(jié)構(gòu),一個 contenteditable="true" 的輸入框和一個 id="selectPerson" 的選人框。

輸入框使用 contenteditable="true" 主要是因為想在輸入框中插入標簽,將 @xxx 內(nèi)容顯示出不同的顏色(這就需要將 @xxx 放在一個標簽中),綁定 keyIn 的鍵盤輸入事件,用于檢索用戶輸入 @backspace ,并做出相應的動作;

選人框使用 showSelect 來控制是否顯示,遍歷顯示需要顯示的選人,以及使用 input 中的內(nèi)容來過濾選人。

實現(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"
        };
        // 讓選人框中的搜索框獲取焦點
        $timeout(function(){
            $("#searchPersonInput")[0].focus();
        })
    }
}

實現(xiàn)起來挺簡單,代碼也不復雜,利用 caret 插件獲取到光標位置,將選人框在 @ 符號的下方顯示出來,并同時實現(xiàn)了步驟中的第二步:將焦點放在搜索框中。

選人實現(xiàn)

主要涉及步驟為:3、4、5。

當鼠標點擊備選項時需要按順序進行 3、4、5 步驟,所以需將 3、4、53 個步驟放在一起。
相關(guān)代碼如下:

$scope.sPersonDone = function(person) {

    // 成功選人后,關(guān)閉選擇框,讓輸入框獲取焦點。
    $scope.showSelect = false;
    var ele = $("#demo")[0];
    ele.focus();

    // 獲取之前保留先來的信息。
    // 需要修改 keyIn 的代碼,保存選區(qū)以及光標信息,用于獲取在光標焦點離開前,光標的位置
    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)容,包括一個 span 和一個空格。
    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)容的最后一個節(jié)點,也就是空格。
    var frag = document.createDocumentFragment(),
        node, lastNode;
    frag.appendChild(spanNode1);
    while ((node = spanNode2.firstChild)) {
        lastNode = frag.appendChild(node);
    }

    // 將 Fragment 中的內(nèi)容放入 range 中,并將光標放在空格之后。
    range.insertNode(frag);
    selection.extend(lastNode, 1);
    selection.collapseToEnd();
};

我們需要的效果是在 @ 選人后,將整理好的 @xxx 包裝成一個標簽,放在原先 @ 的位置,所以我們需要對原先的 $scope.keyIn 方法進行改造,保留原先的光標信息,方便在上面的方法中使用。

改造后的 $scope.keyIn 方法如下:

$scope.keyIn = function(e) {
    var selection = getSelection();
    var ele = $("#demo");
    if (e.code == "Digit2" && e.shiftKey) {
        $scope.showSelect = true;
        
        // 保存光標信息
        lastSelection = {
            range: selection.getRangeAt(0),
            offset: selection.focusOffset,
            selection: selection
        };
        $scope.showSelect = true;

        // 設置彈出框位置
        var offset = ele.caret("offset");
        $scope.sPersonPosi = {
            left: offset.left - 10 + "px",
            top: offset.top + 20 + "px"
        };
        $timeout(function(){
            $("#searchPersonInput")[0].focus();
        })
    }
}

這里估計挺多人會有疑問,為啥要在生成的標簽后面加一個空格,而且這個空格要通過   這樣的方式實現(xiàn)。

首先,先解釋第一個問題:為啥要在標簽后加一個空格?

如果不加空格的話,之后在輸入文字會添加在我們生成的標簽中,也就是說如果不加空格來隔斷我們生成的標簽,我們在文本框里所做的操作就是在我們生成的標簽中進行。而加了個空格就為了避免該問題的發(fā)生,使得文本編輯在正確的編輯框中進行。

第二個問題:為啥不能直接加空格 " " ,而是通過   ,不得不說這是個過個悲傷的事實,還是碰到了兼容性的問題,在 chrome 下運行好好的代碼,在 node-webkit 中就會各種報錯。原因在不斷的 defug 后發(fā)現(xiàn)了: node-webkit 中,將一個 " " 添加到 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)境各種報錯。真是坑了個大爹。原因是光標定位不準,指定位置超出實際位置,但是 node-webkit 環(huán)境確實是可以輸入空格的,一看原來是    不能通過 createTextNode 來創(chuàng)建,所以就有了之前的哪個曲線救國的策略了。

刪除實現(xiàn)

終于捋到最后一個步驟了,刪除時,需要將一整個標簽一起刪除。由于需要監(jiān)聽鍵盤的輸入,所以就可與之前 keyIn 的代碼寫在一起。

最終的 keyIn 代碼為:

$scope.keyIn = function(e) {
    var selection = getSelection();
    var ele = document.getElementById("demo");
    if (e.code == "Digit2" && e.shiftKey) {

        // 保存光標信息
        lastSelection = {
            range: selection.getRangeAt(0),
            offset: selection.focusOffset,
            selection: selection
        };
        $scope.showSelect = true;

        // 設置彈出框位置
        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)建時默認會在 @xxx 后添加一個空格,
        // 所以當?shù)弥鈽宋挥?@xxx 之后的一個第一個字符后并按下刪除按鈕時,
        // 應該將光標前的 @xxx 給刪除
        // 2 :當光標位于 @xxx 中間時,按下刪除按鈕時應該將整個 @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://systransis.cn/yun/86537.html

相關(guān)文章

  • 前端基礎

    摘要:談起閉包,它可是兩個核心技術(shù)之一異步基于打造前端持續(xù)集成開發(fā)環(huán)境本文將以一個標準的項目為例,完全拋棄傳統(tǒng)的前端項目開發(fā)部署方式,基于容器技術(shù)打造一個精簡的前端持續(xù)集成的開發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日...

    graf 評論0 收藏0
  • 寫一本關(guān)于 React.js 的小書

    摘要:因為工作中一直在使用,也一直以來想總結(jié)一下自己關(guān)于的一些知識經(jīng)驗。于是把一些想法慢慢整理書寫下來,做成一本開源免費專業(yè)簡單的入門級別的小書,提供給社區(qū)。本書的后續(xù)可能會做成視頻版本,敬請期待。本作品采用署名禁止演繹國際許可協(xié)議進行許可 React.js 小書 本文作者:胡子大哈本文原文:React.js 小書 轉(zhuǎn)載請注明出處,保留原文鏈接以及作者信息 在線閱讀:http://huzi...

    Scorpion 評論0 收藏0
  • ELSE 技術(shù)周刊(2017.11.13期)

    摘要:騰訊空間超分辨率技術(shù)為用戶節(jié)省流量,處理效果和速度超谷歌技術(shù)在的標準下,處理速度在提升了,處理效果也有明顯提升。此外,也是業(yè)界首次實現(xiàn)移動端使用深度神經(jīng)網(wǎng)絡進行超分辨率,并保證圖片能夠?qū)崟r進行處理。值得一提的是的對應指標也在名單里。 團隊分享 魔幻語言 JavaScript 系列之 call、bind 以及上下文 從一行代碼來看看 JavaScript 是一門多么魔幻的語言,順便談談 ...

    caohaoyu 評論0 收藏0
  • 基礎 - 收藏集 - 掘金

    摘要:的語言的動態(tài)性意味著我們可以使用以上種數(shù)據(jù)類型表示變換過渡動畫實現(xiàn)案例前端掘金以下所有效果的實現(xiàn)方式均為個人見解,如有不對的地方還請一一指出。 讀 zepto 源碼之工具函數(shù) - 掘金Zepto 提供了豐富的工具函數(shù),下面來一一解讀。 源碼版本 本文閱讀的源碼為 zepto1.2.0 $.extend $.extend 方法可以用來擴展目標對象的屬性。目標對象的同名屬性會被源對象的屬性...

    wuaiqiu 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<