已知一個元素: <div>123abc&de</div> ,我們想高亮文字"a",也就是說,把元素内容變為
<div>123<span class='highlight'>a</span>bc&de</div>。要如何做?
這還不簡單,把div的innerHTML做個replace,把 a替換成 <span class='highlight'>a</span> 不就成了?注意,字元實體&裡面也有個a,replace會破壞它的,如果用正則控制不替換&和;之間的字元似乎又比較麻煩。我這裡有個方法:
1. 把 div 的 innerText 用關鍵字拆分開,得到["123", "bc&de"]
2. 把這個數組中的字元串轉換成HTML格式,即得到["123", "bc&de"]
3. 用<span class='highlight'>a</span> 這個字元串join上面那個數組,即得到 123<spanclass='highlight'>a</span>bc&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>&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方法用了三層循環,文本内容較短的元素還行,而且元素裡不能再嵌套元素。以後再嘗試改進算法。