使用 JavaScript 突出显示文本范围
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6240139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Highlight text range using JavaScript
提问by Vincent
I would like to highlight (apply css to) a certain text range, denoted by its start and end position. This is more diffucult than it seems, since there may be other tags within the text, that need to be ignored.
我想突出显示(将 css 应用于)某个文本范围,由其开始和结束位置表示。这比看起来更困难,因为文本中可能还有其他需要忽略的标签。
Example:
例子:
<div>abcd<em>efg</em>hij</div>
highlight(2, 6)
needs to highlight "cdef
" without removing the tag.
highlight(2, 6)
需要突出显示"cdef
“而不删除标签。
I have tried already using a TextRange object, but without success.
我已经尝试过使用 TextRange 对象,但没有成功。
Thanks in advance!
提前致谢!
回答by Tim Down
Below is a function to set the selection to a pair of character offsets within a particular element. This is naive implementation: it does not take into account any text that may be made invisible (either by CSS or by being inside a <script>
or <style>
element, for example) and may have browser discrepancies (IE versus everything else) with line breaks, and takes no account of collapsed whitespace (such as 2 or more consecutive space characters collapsing to one visible space on the page). However, it does work for your example in all major browsers.
下面是将选择设置为特定元素内的一对字符偏移量的函数。这是一种幼稚的实现:它没有考虑任何可能变得不可见的文本(例如,通过 CSS 或通过在<script>
或<style>
元素内部)并且可能具有带有换行符的浏览器差异(IE 与其他所有内容),并且需要不考虑折叠的空白(例如 2 个或更多连续的空格字符折叠到页面上的一个可见空间)。但是,它在所有主要浏览器中都适用于您的示例。
For the other part, the highlighting, I'd suggest using document.execCommand()
for that. You can use my function below to set the selection and then call document.execCommand()
. You'll need to make the document temporarily editable in non-IE browsers for the command to work. See my answer here for code: getSelection & surroundContents across multiple tags
对于另一部分,突出显示,我建议使用document.execCommand()
它。您可以使用我下面的函数来设置选择,然后调用document.execCommand()
. 您需要使该文档在非 IE 浏览器中暂时可编辑,以便该命令起作用。请在此处查看我的答案以获取代码:跨多个标签的 getSelection & aroundContents
Here's a jsFiddle example showing the whole thing, working in all major browsers: http://jsfiddle.net/8mdX4/1211/
这是一个 jsFiddle 示例,展示了整个过程,适用于所有主要浏览器:http: //jsfiddle.net/8mdX4/1211/
And the selection setting code:
和选择设置代码:
function getTextNodesIn(node) {
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
} else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function setSelectionRange(el, start, end) {
if (document.createRange && window.getSelection) {
var range = document.createRange();
range.selectNodeContents(el);
var textNodes = getTextNodesIn(el);
var foundStart = false;
var charCount = 0, endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
endCharCount = charCount + textNode.length;
if (!foundStart && start >= charCount
&& (start < endCharCount ||
(start == endCharCount && i <= textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
textRange.select();
}
}
回答by Nico Pernice
You could take a look at how works this powerful JavaScript utility which support selection over multiple DOM elements:
您可以看看这个强大的 JavaScript 实用程序是如何工作的,它支持对多个 DOM 元素进行选择:
MASHA(short for Mark & Share) allow you to mark interesting parts of web page content and share it
MASHA(Mark & Share 的缩写)允许您标记网页内容的有趣部分并分享
http://mashajs.com/index_eng.html
http://mashajs.com/index_eng.html
It's also on GitHub https://github.com/SmartTeleMax/MaSha
它也在 GitHub 上https://github.com/SmartTeleMax/MaSha
Works even on Mobile Safari and IE!
甚至适用于移动 Safari 和 IE!
回答by Bill
Based on the ideas of the jQuery.highlightplugin.
基于jQuery.highlight插件的想法。
private highlightRange(selector: JQuery, start: number, end: number): void {
let cur = 0;
let replacements: { node: Text; pos: number; len: number }[] = [];
let dig = function (node: Node): void {
if (node.nodeType === 3) {
let nodeLen = (node as Text).data.length;
let next = cur + nodeLen;
if (next > start && cur < end) {
let pos = cur >= start ? cur : start;
let len = (next < end ? next : end) - pos;
if (len > 0) {
if (!(pos === cur && len === nodeLen && node.parentNode &&
node.parentNode.childNodes && node.parentNode.childNodes.length === 1 &&
(node.parentNode as Element).tagName === 'SPAN' && (node.parentNode as Element).className === 'highlight1')) {
replacements.push({
node: node as Text,
pos: pos - cur,
len: len,
});
}
}
}
cur = next;
}
else if (node.nodeType === 1) {
let childNodes = node.childNodes;
if (childNodes && childNodes.length) {
for (let i = 0; i < childNodes.length; i++) {
dig(childNodes[i]);
if (cur >= end) {
break;
}
}
}
}
};
selector.each(function (index, element): void {
dig(element);
});
for (let i = 0; i < replacements.length; i++) {
let replacement = replacements[i];
let highlight = document.createElement('span');
highlight.className = 'highlight1';
let wordNode = replacement.node.splitText(replacement.pos);
wordNode.splitText(replacement.len);
let wordClone = wordNode.cloneNode(true);
highlight.appendChild(wordClone);
wordNode.parentNode.replaceChild(highlight, wordNode);
}
}
回答by Niklas
Following solution doesn't work for IE, you'll need to apply TextRange objects etc. for that. As this uses selections to perform this, it shouldn't break the HTML in normal cases, for example:
以下解决方案不适用于 IE,您需要为此应用 TextRange 对象等。由于这使用选择来执行此操作,因此在正常情况下不应破坏 HTML,例如:
<div>abcd<span>efg</span>hij</div>
With highlight(3,6);
和 highlight(3,6);
outputs:
输出:
<div>abc<em>d<span>ef</span></em><span>g</span>hij</div>
Take note how it wraps the first character outside of the span into an em
, and then the rest within the span
into a new one. Where as if it would just open it at character 3 and end at character 6, it would give invalid markup like:
请注意它如何将跨度外的第一个字符包装成一个em
,然后将其余的字符包装span
成一个新的。好像它只是在字符 3 处打开它并在字符 6 处结束,它会给出无效标记,例如:
<div>abc<em>d<span>ef</em>g</span>hij</div>
The code:
编码:
var r = document.createRange();
var s = window.getSelection()
r.selectNode($('div')[0]);
s.removeAllRanges();
s.addRange(r);
// not quite sure why firefox has problems with this
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
function highlight(start,end){
for(var st=0;st<start;st++){
s.modify("move", "forward", "character");
}
for(var st=0;st<(end-start);st++){
s.modify("extend", "forward", "character");
}
}
highlight(2,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
Example: http://jsfiddle.net/niklasvh/4NDb9/
editLooks like at least my FF4 had some issues with
s.modify("move", "backward", "documentboundary");
but at the same time, it seems to work without it, so I just changed it to
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
var r = document.createRange();
var s = window.getSelection()
r.selectNode($('div')[0]);
s.removeAllRanges();
s.addRange(r);
// not quite sure why firefox has problems with this
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
function highlight(start,end){
for(var st=0;st<start;st++){
s.modify("move", "forward", "character");
}
for(var st=0;st<(end-start);st++){
s.modify("extend", "forward", "character");
}
}
highlight(2,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
示例:http: //jsfiddle.net/niklasvh/4NDb9/
编辑看起来至少我的 FF4 有一些问题
s.modify("move", "backward", "documentboundary");
但与此同时,没有它似乎也能工作,所以我只是把它改成了
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
editas Tim Pointed out, modify is only available from FF4 onwards, so I took a different approach to getting the selection, which doesn't need the modify method, in hopes in making it a bit more browser compatible (IE still needs its own solution).
正如 Tim 指出的那样编辑,修改仅从 FF4 开始可用,所以我采用了不同的方法来获取选择,不需要修改方法,希望使其更兼容浏览器(IE 仍然需要自己的解决方案)。
The code:
编码:
var r = document.createRange();
var s = window.getSelection()
var pos = 0;
function dig(el){
$(el).contents().each(function(i,e){
if (e.nodeType==1){
// not a textnode
dig(e);
}else{
if (pos<start){
if (pos+e.length>=start){
range.setStart(e, start-pos);
}
}
if (pos<end){
if (pos+e.length>=end){
range.setEnd(e, end-pos);
}
}
pos = pos+e.length;
}
});
}
var start,end, range;
function highlight(element,st,en){
range = document.createRange();
start = st;
end = en;
dig(element);
s.addRange(range);
}
highlight($('div'),3,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
example: http://jsfiddle.net/niklasvh/4NDb9/