javascript用户选择突出显示
Posted
技术标签:
【中文标题】javascript用户选择突出显示【英文标题】:javascript user selection highlighting 【发布时间】:2010-09-23 05:17:57 【问题描述】:我正在尝试使用 javascript 来突出显示用户在单击某些奇怪的突出显示按钮时选择的文本(如 突出显示的文本)。它只需要与 WebKit 或 Firefox 一起工作,但它似乎几乎是不可能的,因为它必须在以下情况下工作:
<p>this is text</p>
<p>I eat food</p>
当用户在浏览器中从“是文本”到“我吃”进行选择时(不能只放一个跨度)。
这个案例:
<span><span>this is text</span>middle text<span>this is text</span></span>
当用户在浏览器中从“是文本”选择“这是”时(即使您可以将突出显示范围环绕选择中的每个元素,我希望看到您尝试突出显示中间文本)。
这个问题似乎在任何地方都没有得到解决,坦率地说我怀疑这是可能的。
如果您可以从选择中获得的 Range 作为一个完整的带有 html 的字符串,可以对其进行解析然后替换,但据我所知,您无法获得原始 html范围..可惜。
【问题讨论】:
【参考方案1】:这个答案对你来说可能已经晚了几年,但我遇到了类似的问题并想在这里记录它,因为它是谷歌上的第一个热门。
重申一下,问题是您只想从用户选择中捕获 Range 对象并用一个样式化的 div 包围它,如下所示:
function highlightSelection()
var userSelection = window.getSelection().getRangeAt(0);
highlightRange(userSelection);
function highlightRange(range)
var newNode = document.createElement("div");
newNode.setAttribute(
"style",
"background-color: yellow; display: inline;"
);
range.surroundContents(newNode);
但正如原始父母所说,这是不安全的。如果选择不跨越元素边界,它将起作用,但如果用户选择创建的范围是跨越 HTML 标记边界的不安全范围,它将引发 DOM 错误。
解决方案是生成一个由较小的 Range 对象组成的数组,其中没有一个单独跨越元素障碍,而是共同覆盖用户选择的 Range。这些安全范围中的每一个都可以如上所述突出显示。
function getSafeRanges(dangerous)
var a = dangerous.commonAncestorContainer;
// Starts -- Work inward from the start, selecting the largest safe range
var s = new Array(0), rs = new Array(0);
if (dangerous.startContainer != a)
for(var i = dangerous.startContainer; i != a; i = i.parentNode)
s.push(i)
;
if (0 < s.length) for(var i = 0; i < s.length; i++)
var xs = document.createRange();
if (i)
xs.setStartAfter(s[i-1]);
xs.setEndAfter(s[i].lastChild);
else
xs.setStart(s[i], dangerous.startOffset);
xs.setEndAfter(
(s[i].nodeType == Node.TEXT_NODE)
? s[i] : s[i].lastChild
);
rs.push(xs);
// Ends -- basically the same code reversed
var e = new Array(0), re = new Array(0);
if (dangerous.endContainer != a)
for(var i = dangerous.endContainer; i != a; i = i.parentNode)
e.push(i)
;
if (0 < e.length) for(var i = 0; i < e.length; i++)
var xe = document.createRange();
if (i)
xe.setStartBefore(e[i].firstChild);
xe.setEndBefore(e[i-1]);
else
xe.setStartBefore(
(e[i].nodeType == Node.TEXT_NODE)
? e[i] : e[i].firstChild
);
xe.setEnd(e[i], dangerous.endOffset);
re.unshift(xe);
// Middle -- the uncaptured middle
if ((0 < s.length) && (0 < e.length))
var xm = document.createRange();
xm.setStartAfter(s[s.length - 1]);
xm.setEndBefore(e[e.length - 1]);
else
return [dangerous];
// Concat
rs.push(xm);
response = rs.concat(re);
// Send to Console
return response;
然后可以(似乎)使用此修改后的代码突出显示用户选择:
function highlightSelection()
var userSelection = window.getSelection().getRangeAt(0);
var safeRanges = getSafeRanges(userSelection);
for (var i = 0; i < safeRanges.length; i++)
highlightRange(safeRanges[i]);
请注意,您可能需要一些更精美的 CSS 来使用户可以将许多不同的元素放在一起看起来不错。我希望这最终能帮助互联网上其他一些疲惫的灵魂!
【讨论】:
我希望我能为您在这方面的辛勤工作投两票。 干得好,但是.. 这个解决方案的局限性在于将一个整体分成几个部分,然后将这些部分分开。当按照用户的期望(已创建)使用它们时,您还需要一个通用的类或属性查找来重新统一它们。 非常感谢您为我们所有人解决了这个问题。这很好用。我想强调的一点是,这个函数返回了一些空范围,创建了一些不必要的跨度元素。我在 highlightRange 函数中添加了一个附加条件以避免该问题。此条件跳过任何空范围。谢谢,如果 (range.toString() !== "" && range.toString().match(/\w+/g) !== null) 这在大多数情况下都很好用,但是在突出显示直接子评论到 reddit.com/r/Tinder/comments/gyb7vc/… 父评论的父评论时失败了,“dm me...”仍然未突出显示。 感谢您的努力,这是一个很棒的工作,但是我有一个问题,我想取消突出显示突出显示的文本或其中的一些,可以这样做吗?请回答我,我在等你,谢谢。【参考方案2】:嗯,您可以使用 DOM 操作来实现。这适用于 Firefox:
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var newNode = document.createElement("span");
newNode.setAttribute("style", "background-color: pink;");
range.surroundContents(newNode);
似乎也可以在当前版本的 Safari 中使用。见https://developer.mozilla.org/en/DOM/range.surroundContents 和http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html
【讨论】:
如果选择跨越元素边界(例如,如果它跨越多个段落),这将不起作用。【参考方案3】:这是我第一次在这里发帖,但是看看你的答案,这样的事情不会有用吗?我这里有一个样本: http://henriquedonati.com/projects/Extension/extension.html
function highlightSelection()
var userSelection = window.getSelection();
for(var i = 0; i < userSelection.rangeCount; i++)
highlightRange(userSelection.getRangeAt(i));
function highlightRange(range)
var newNode = document.createElement("span");
newNode.setAttribute(
"style",
"background-color: yellow; display: inline;"
);
range.surroundContents(newNode);
【讨论】:
这在尝试跨多个元素突出显示时不起作用 @henrique 有没有机会将突出显示的内容存储到 Db 中?所以当你回来时它仍然突出显示?【参考方案4】:这是一个完整的代码来高亮和取消高亮文本
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.highlight
background-color: yellow;
#test-text::-moz-selection /* Code for Firefox */
background: yellow;
#test-text::selection
background: yellow;
</style>
</head>
<body>
<div id="div1" style="border: 1px solid #000;">
<div id="test-text">
<h1> Hello How are you </h1>
<p >
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
</div>
</div>
<br />
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript">
mouseXPosition = 0;
$(document).ready(function ()
$("#test-text").mousedown(function (e1)
mouseXPosition = e1.pageX;//register the mouse down position
);
$("#test-text").mouseup(function (e2)
var highlighted = false;
var selection = window.getSelection();
var selectedText = selection.toString();
var startPoint = window.getSelection().getRangeAt(0).startOffset;
var endPoint = window.getSelection().getRangeAt(0).endOffset;
var anchorTag = selection.anchorNode.parentNode;
var focusTag = selection.focusNode.parentNode;
if ((e2.pageX - mouseXPosition) < 0)
focusTag = selection.anchorNode.parentNode;
anchorTag = selection.focusNode.parentNode;
if (selectedText.length === (endPoint - startPoint))
highlighted = true;
if (anchorTag.className !== "highlight")
highlightSelection();
else
var afterText = selectedText + "<span class = 'highlight'>" + anchorTag.innerHTML.substr(endPoint) + "</span>";
anchorTag.innerHTML = anchorTag.innerHTML.substr(0, startPoint);
anchorTag.insertAdjacentHTML('afterend', afterText);
else
if(anchorTag.className !== "highlight" && focusTag.className !== "highlight")
highlightSelection();
highlighted = true;
if (anchorTag.className === "highlight" && focusTag.className === 'highlight' && !highlighted)
highlighted = true;
var afterHtml = anchorTag.innerHTML.substr(startPoint);
var outerHtml = selectedText.substr(afterHtml.length, selectedText.length - endPoint - afterHtml.length);
var anchorInnerhtml = anchorTag.innerHTML.substr(0, startPoint);
var focusInnerHtml = focusTag.innerHTML.substr(endPoint);
var focusBeforeHtml = focusTag.innerHTML.substr(0, endPoint);
selection.deleteFromDocument();
anchorTag.innerHTML = anchorInnerhtml;
focusTag.innerHTml = focusInnerHtml;
var anchorafterHtml = afterHtml + outerHtml + focusBeforeHtml;
anchorTag.insertAdjacentHTML('afterend', anchorafterHtml);
if (anchorTag.className === "highlight" && !highlighted)
highlighted = true;
var Innerhtml = anchorTag.innerHTML.substr(0, startPoint);
var afterHtml = anchorTag.innerHTML.substr(startPoint);
var outerHtml = selectedText.substr(afterHtml.length, selectedText.length);
selection.deleteFromDocument();
anchorTag.innerHTML = Innerhtml;
anchorTag.insertAdjacentHTML('afterend', afterHtml + outerHtml);
if (focusTag.className === 'highlight' && !highlighted)
highlighted = true;
var beforeHtml = focusTag.innerHTML.substr(0, endPoint);
var outerHtml = selectedText.substr(0, selectedText.length - beforeHtml.length);
selection.deleteFromDocument();
focusTag.innerHTml = focusTag.innerHTML.substr(endPoint);
outerHtml += beforeHtml;
focusTag.insertAdjacentHTML('beforebegin', outerHtml );
if (!highlighted)
highlightSelection();
$('.highlight').each(function()
if($(this).html() == '')
$(this).remove();
);
selection.removeAllRanges();
);
);
function highlightSelection()
var selection;
//Get the selected stuff
if (window.getSelection)
selection = window.getSelection();
else if (typeof document.selection != "undefined")
selection = document.selection;
//Get a the selected content, in a range object
var range = selection.getRangeAt(0);
//If the range spans some text, and inside a tag, set its css class.
if (range && !selection.isCollapsed)
if (selection.anchorNode.parentNode == selection.focusNode.parentNode)
var span = document.createElement('span');
span.className = 'highlight';
span.textContent = selection.toString();
selection.deleteFromDocument();
range.insertNode(span);
// range.surroundContents(span);
</script>
</html>
https://jsfiddle.net/Bilalchk123/1o4j0w2v/
【讨论】:
不适用于跨多个段落的文本选择。【参考方案5】: function load()
window.document.designMode = "On";
//run this in a button, will highlight selected text
window.document.execCommand("hiliteColor", false, "#768");
<html>
<head>
</head>
<body contentEditable="true" onload="load()">
this is text
</body>
</html>
【讨论】:
这无疑是最好的主意。很好的答案,但之后再次关闭designMode
确实是个好主意。
虽然帖子已经很老了,但我仍然认为这是我现在能找到的最佳解决方案
遗憾的是这个功能已经过时了。 developer.mozilla.org/en-US/docs/Web/API/Document/execCommand【参考方案6】:
我刚刚发布了一个包,它是 texthighlighter(一个已弃用的库)的 typescript 端口。只是将其转换为 typescript 就发现了一些错误,并且使将来的工作更容易。结帐https://www.npmjs.com/package/@funktechno/texthighlighter。这没有依赖关系,并允许突出显示用户选择、合并突出显示、删除突出显示、序列化和反序列化(从数据应用)突出显示。
请注意,您需要使用 javascript mouseup 事件来正确触发它。
import doHighlight, deserializeHighlights, serializeHighlights, removeHighlights, optionsImpl from "@/../node_modules/@funktechno/texthighlighter/lib/index";
const domEle = document.getElementById("sandbox");
const options: optionsImpl = ;
if (this.color) options.color = this.color;
if (domEle) doHighlight(domEle, true, options);
这就是我在 vue ts 项目中触发它的方式
<div
id="sandbox"
@mouseup="runHighlight($event)"
>text to highlight</div>
【讨论】:
【参考方案7】:我今天遇到了同样的问题,突出显示了多个标签上的选定标签。 解决办法:
-
找到一种方法来提取所选部分以及 HTML 标记。
用 span 元素包装提取的部分并将其放回 DOM 。
请参阅下面的代码,以获得进一步的说明。
function getRangeObject(selectionObject)
try
if(selectionObject.getRangeAt)
return selectionObject.getRangeAt(0);
catch(ex)
console.log(ex);
document.onmousedown = function(e)
var text;
if (window.getSelection)
/* get the Selection object */
userSelection = window.getSelection()
/* get the innerText (without the tags) */
text = userSelection.toString();
/* Creating Range object based on the userSelection object */
var rangeObject = getRangeObject(userSelection);
/*
This extracts the contents from the DOM literally, inclusive of the tags.
The content extracted also disappears from the DOM
*/
contents = rangeObject.extractContents();
var span = document.createElement("span");
span.className = "highlight";
span.appendChild(contents);
/* Insert your new span element in the same position from where the selected text was extracted */
rangeObject.insertNode(span);
else if (document.selection && document.selection.type != "Control")
text = document.selection.createRange().text;
;
【讨论】:
【参考方案8】:由于 HTML 使用 <mark>
元素作为突出显示的文本,也许使用这个节点很容易,而不是使用你自己的 css,更简洁的代码:
function highlightRange(range)
var newNode = document.createElement('mark');
range.surroundContents(newNode);
// original select range function
function highlight()
var userSelection = window.getSelection();
for(var i = 0; i < userSelection.rangeCount; i++)
highlightRange(userSelection.getRangeAt(i));
【讨论】:
以上是关于javascript用户选择突出显示的主要内容,如果未能解决你的问题,请参考以下文章
使用 Javascript/JQuery 为移动网络(Android、iOS、Windows Phone)突出显示/选择元素上的文本
如何使用 Javascript 鼠标事件和 CSS 重新创建“突出显示”选择?