与えられた問題はとても興味深いものでした。これが私が思いついたものです:
- 何らかのプラグインを使用する (または自分で作成する) ことで、要素が表示されたときに通知を受け取ることができます
- その要素 text-nodes を解析し、単語自体から派生した一意の css-class 名を使用して、各単語を span 要素にラップします
- これらのユニークなクラス名に css-rules を追加する機能を追加
サンプル: http://jsbin.com/welcome/44285/
コードは非常にハックで、最新の Chrome でしかテストできませんが、私にとってはうまく機能し、確実に構築できます。
/**
* Highlighter factory
*
* @return Object
*/
function highlighter() {
var
me = {},
cssClassNames = {},
cssClassNamesCount = 0,
lastAddedRuleIndex,
cbCount = 0,
sheet;
// add a stylesheet if none present
if (document.styleSheets.length === 0) {
sheet = document.createElement('style');
$('head').append(sheet);
}
// get reference to the last stylesheet
sheet = document.styleSheets.item(document.styleSheets.length - 1);
/**
* Returns a constant but unique css class name for the given word
*
* @param String word
* @return String
*/
function getClassNameForWord(word) {
var word = word.toLowerCase();
return cssClassNames[word] = cssClassNames[word] || 'highlight-' + (cssClassNamesCount += 1);
}
/**
* Highlights the given list of words by adding a new css rule to the list of active
* css rules
*
* @param Array words
* @param String cssText
* @return void
*/
function highlight(words, cssText) {
var
i = 0,
lim = words.length,
classNames = [];
// get the needed class names
for (; i < lim; i += 1) {
classNames.push('.' + getClassNameForWord(words[i]));
}
// remove the previous added rule
if (lastAddedRuleIndex !== undefined) {
sheet.deleteRule(lastAddedRuleIndex);
}
lastAddedRuleIndex = sheet.insertRule(classNames.join(', ') + ' { ' + cssText + ' }', sheet.cssRules.length);
}
/**
* Calls the given function for each text node under the given parent element
*
* @param DomElement parentElement
* @param Function onLoad
* @param Function cb
* @return void
*/
function forEachTextNode(parentElement, onLoad, cb) {
var i = parentElement.childNodes.length - 1, childNode;
for (; i > -1; i -= 1) {
childNode = parentElement.childNodes[i];
if (childNode.nodeType === 3) {
cbCount += 1;
setTimeout(function (node) {
return function () {
cb(node);
cbCount -= 1;
if (cbCount === 0 && typeof onLoad === 'Function') {
onLoad(me);
}
};
}(childNode), 0);
} else if (childNode.nodeType === 1) {
forEachTextNode(childNode, cb);
}
}
}
/**
* replace each text node by span elements wrapping each word
*
* @param DomElement contextNode
* @param onLoad the parent element
* @return void
*/
function add(contextNode, onLoad) {
forEachTextNode(contextNode, onLoad, function (textNode) {
var
doc = textNode.ownerDocument,
frag = doc.createDocumentFragment(),
words = textNode.nodeValue.split(/(\W)/g),
i = 0,
lim = words.length,
span;
for (; i < lim; i += 1) {
if (/^\s*$/m.test(words[i])) {
frag.appendChild(doc.createTextNode(words[i]));
} else {
span = doc.createElement('span');
span.setAttribute('class', getClassNameForWord(words[i]));
span.appendChild(doc.createTextNode(words[i]));
frag.appendChild(span);
}
}
textNode.parentNode.replaceChild(frag, textNode);
});
}
// set public api and return created object
me.highlight = highlight;
me.add = add;
return me
}
var h = highlighter();
h.highlight(['Lorem', 'magna', 'gubergren'], 'background: yellow;');
// on ready
$(function ($) {
// using the in-view plugin (see the full code in the link above) here, to only
// parse elements that are actual visible
$('#content > *').one('inview', function (evt, visible) {
if (visible) {
h.add(this);
}
});
$(window).scroll();
});