已知一个元素: <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方法用了三层循环,文本内容较短的元素还行,而且元素里不能再嵌套元素。以后再尝试改进算法。