天天看點

為元素文字高亮關鍵字

已知一個元素: <div>123abc&amp;de</div> ,我們想高亮文字"a",也就是說,把元素内容變為

<div>123<span class='highlight'>a</span>bc&amp;de</div>。要如何做?

這還不簡單,把div的innerHTML做個replace,把 a替換成 <span class='highlight'>a</span> 不就成了?注意,字元實體&amp;裡面也有個a,replace會破壞它的,如果用正則控制不替換&和;之間的字元似乎又比較麻煩。我這裡有個方法:

1. 把 div 的 innerText 用關鍵字拆分開,得到["123", "bc&de"]

2. 把這個數組中的字元串轉換成HTML格式,即得到["123", "bc&amp;de"]

3. 用<span class='highlight'>a</span> 這個字元串join上面那個數組,即得到 123<spanclass='highlight'>a</span>bc&amp;de

這樣我們就得到了div高亮後的innerText。

不過别高興太早,需求總是會變化的:如果要高亮3a和abc這兩個關鍵字怎麼辦?上面那種做法完全沒用了。今天想到一個比較笨的辦法,構造一個數組markArray,數組長度和div的innerText的長度一樣,記錄innerText中的每一個字是否被高亮,這個用indexOf可以做到。然後把單個的字進行合并,得到若幹被高亮和不被高亮的字元串,最後把這些字元串數組進行拼接。仍以上述為例:

1. 3a和abc高亮,markArray為[0, 0, 1, 1, 1, 1, 0, 0, 0]

2. 合并後,得到3個字元串,12不高亮,3abc高亮,&de不高亮

3. 這3個字元串轉換為HTML格式,高亮的串用<span>包裹,最後得到12<span style='highlight'>3abc</span>&amp;de

代碼

var highlightUtil = {
    _sourceText: "",
    _keys: [],
    _blocks: [],
    _matchMarkArray: [],
    
    highlight: function(elt, keys) {
        this._sourceText = elt.innerText;
        this._keys = keys;
        
        this._mark();
        this._combine();
        var innerHTML = this._processBlocks();
        
        elt.innerHTML = innerHTML;
    },
    
    _getMatchPos: function(matchStr) {
        var posArray = [];
        var start = 0;
        
        while (true) {
            var index = this._sourceText.indexOf(matchStr, start);
            if (index == -1) {
                break;
            } else {
                posArray.push(index);
                start = index + matchStr.length;
            }
        }
        
        return posArray;
    },
   
    _mark: function() {
        this._matchMarkArray = [];
        for (var i = 0; i < this._sourceText.length; i++) {
            this._matchMarkArray.push(false);
        }
        
        for (var i = 0; i < this._keys.length; i++) {
            var key = this._keys[i];
            var posArray = this._getMatchPos(key);
            for (var p = 0; p < posArray.length; p++) {
                for (var c = 0; c < key.length; c++) {
                    this._matchMarkArray[posArray[p] + c] = true;
                }
            }
        }
    },
    
    _combine: function() {
        if (this._matchMarkArray.length == 0) {
            return;
        }
        
        this._blocks = [];
        
        var start = 0;
        var len = 0;
        var lastMark = this._matchMarkArray[0];
        
        for (var i = 0; i < this._matchMarkArray.length; i++) {
            var mark = this._matchMarkArray[i];
            if (mark == lastMark) {
                len++;
            } else {
                var block = [start, len, lastMark];
                this._blocks.push(block);
                
                start = i;
                len = 1;
                lastMark = mark;
            }
        }
        
        var lastBlock = [start, len, lastMark];
        this._blocks.push(lastBlock);
    },
    
    _processBlocks: function() {
        var htmlStr = "";
        for (var i = 0; i < this._blocks.length; i++) {
            var block = this._blocks[i];
            var start = block[0];
            var end = block[0] + block[1];
            var highlight = block[2];
            var sourceText = new String(this._sourceText);
            
            var textBlock = sourceText.slice(start, end);
            console.log(textBlock);
            
            if (highlight) {
                htmlStr += "<span class='highlight'>" + this._textToHtml(textBlock) + "</span>";
            } else {
                htmlStr += this._textToHtml(textBlock);
            }
        }
        
        return htmlStr;
    },

    _textToHtml: function(text) {
        var tmpElt = document.createElement("div");
        var tmpText = document.createTextNode(text);
        tmpElt.appendChild(tmpText);
        return tmpElt.innerHTML;
    }
};           

使用 highlightUtil.highlight(div, ["3a", "abc"]) 高亮字元串。這個算法比較低效,可以看到_mark方法用了三層循環,文本内容較短的元素還行,而且元素裡不能再嵌套元素。以後再嘗試改進算法。