How To Wrap / Surround Highlighted Text With An Element
I want to wrap a selected text in a div container with span, is it possible?
A user will select a text and will click a button, on button click event I want to wrap that selected text with span element. I can get the selected text using window.getSelection()
but how to know its exact position in DOM st开发者_C百科ructure?
If the selection is completely contained within a single text node, you can do this using the surroundContents()
method of the range you obtain from the selection. However, this is very brittle: it does not work if the selection cannot logically be surrounded in a single element (generally, if the range crosses node boundaries, although this is not the precise definition). To do this in the general case, you need a more complicated approach.
Also, DOM Range
and window.getSelection()
are not supported in IE < 9. You'll need another approach again for those browsers. You can use a library such as my own Rangy to normalize browser behaviour and you may find the class applier module useful for this question.
Simple surroundContents()
example jsFiddle: http://jsfiddle.net/VRcvn/
Code:
function surroundSelection(element) {
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(element);
sel.removeAllRanges();
sel.addRange(range);
}
}
}
function wrapSelectedText() {
var selection= window.getSelection().getRangeAt(0);
var selectedText = selection.extractContents();
var span= document.createElement("span");
span.style.backgroundColor = "yellow";
span.appendChild(selectedText);
selection.insertNode(span);
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus gravida magna, quis interdum magna mattis quis. Fusce tempor sagittis varius. Nunc at augue at erat suscipit bibendum id nec enim. Sed eu odio quis turpis hendrerit sagittis id sit amet justo. Cras ac urna purus, non rutrum nunc. Aenean nec vulputate ante. Morbi scelerisque sagittis hendrerit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla tristique ligula fermentum tortor semper at consectetur erat aliquam. Sed gravida consectetur sollicitudin.
<input type="button" onclick="wrapSelectedText();" value="Highlight" />
JS Fiddle.
Here's an attempt at a general solution that allows crossing element boundaries. Probably doesn't mix well with images, etc., nor non left-to-right text, but should be OK with simple cases.
First, here's a very general function for walking text nodes in the subtree defined by a Range object, from left to right. This will give us all the text we need as pieces:
function walkRange(range) {
let ranges = [];
let el = range.startContainer;
let elsToVisit = true;
while (elsToVisit) {
let startOffset = el == range.startContainer ? range.startOffset : 0;
let endOffset = el == range.endContainer ? range.endOffset : el.textContent.length;
let r = document.createRange();
r.setStart(el, startOffset);
r.setEnd(el, endOffset);
ranges.push(r);
/// Move to the next text container in the tree order
elsToVisit = false;
while (!elsToVisit && el != range.endContainer) {
let nextEl = getFirstTextNode(el.nextSibling);
if (nextEl) {
el = nextEl;
elsToVisit = true;
}
else {
if (el.nextSibling) el = el.nextSibling;
else if (el.parentNode) el = el.parentNode;
else break;
}
}
}
return ranges;
}
That makes use of this utility function for getting the first (leftmost) text node in a subtree:
function getFirstTextNode(el) {
/// Degenerate cases: either el is null, or el is already a text node
if (!el) return null;
if (el.nodeType == 3) return el;
for (let child of el.childNodes) {
if (child.nodeType == 3) {
return child;
}
else {
let textNode = getFirstTextNode(child);
if (textNode !== null) return textNode;
}
}
return null;
}
Once you've called walkRanges
, you can just use surroundContents
on what it returns to actually do the highlighting/marking. Here it is in a function:
function highlight(range, className) {
range = range.getRangeAt ? range.getRangeAt(0) : range;
for (let r of walkRange(range)) {
let mark = document.createElement('mark');
mark.className = className;
r.surroundContents(mark);
}
}
and to unhighlight (assuming you used a unique class name for the highlight):
function unhighlight(sel) {
document.querySelectorAll(sel).forEach(el => el.replaceWith(...el.childNodes));
}
Example usage:
highlight(document.getSelection(), 'mySelectionClassName');
unhighlight('.mySelectionClassName')
it is possible. You need to use the range API and the Range.surroundContents() method. It places the node the content is wrapped in at the start of the specified range. see https://developer.mozilla.org/en/DOM/range.surroundContents
surroundContents only works if your selection contains only text and no HTML. Here is a more flexible, as well as cross-browser solution. This will insert a span like this:
<span id="new_selection_span"><!--MARK--></span>
The span is inserted before the selection, in front of the nearest opening HTML tag.
var span = document.createElement("span");
span.id = "new_selection_span";
span.innerHTML = '<!--MARK-->';
if (window.getSelection) { //compliant browsers
//obtain the selection
sel = window.getSelection();
if (sel.rangeCount) {
//clone the Range object
var range = sel.getRangeAt(0).cloneRange();
//get the node at the start of the range
var node = range.startContainer;
//find the first parent that is a real HTML tag and not a text node
while (node.nodeType != 1) node = node.parentNode;
//place the marker before the node
node.parentNode.insertBefore(span, node);
//restore the selection
sel.removeAllRanges();
sel.addRange(range);
}
} else { //IE8 and lower
sel = document.selection.createRange();
//place the marker before the node
var node = sel.parentElement();
node.parentNode.insertBefore(span, node);
//restore the selection
sel.select();
}
Following works across multiple dom elements
function highlightSelection() {
let selection= window.getSelection().getRangeAt(0);
let selectedContent = selection.extractContents();
var span= document.createElement("span");
span.style.backgroundColor = "lightpink";
span.appendChild(selectedContent);
selection.insertNode(span);
}
Make your <b>selection across multiple</b> elements <strike>and then click highlight</strike> button.
<button onclick="highlightSelection();">Highlight</button>
Please find the below code will be helpfull for wrapping the span tag for all kind of tags. Please go through the code and use the logic for your implementation.
getSelectedText(this);
addAnnotationElement(this, this.parent);
function getSelectedText(this) {
this.range = window.getSelection().getRangeAt(0);
this.parent = this.range.commonAncestorContainer;
this.frag = this.range.cloneContents();
this.clRange = this.range.cloneRange();
this.start = this.range.startContainer;
this.end = this.range.endContainer;
}
function addAnnotationElement(this, elem) {
var text, textParent, origText, prevText, nextText, childCount,
annotationTextRange,
span = this.htmlDoc.createElement('span');
if (elem.nodeType === 3) {
span.setAttribute('class', this.annotationClass);
span.dataset.name = this.annotationName;
span.dataset.comment = '';
span.dataset.page = '1';
origText = elem.textContent;
annotationTextRange = validateTextRange(this, elem);
if (annotationTextRange == 'textBeforeRangeButIntersect') {
text = origText.substring(0, this.range.endOffset);
nextText = origText.substring(this.range.endOffset);
} else if (annotationTextRange == 'textAfterRangeButIntersect') {
prevText = origText.substring(0, this.range.startOffset);
text = origText.substring(this.range.startOffset);
} else if (annotationTextRange == 'textExactlyInRange') {
text = origText
} else if (annotationTextRange == 'textWithinRange') {
prevText = origText.substring(0, this.range.startOffset);
text = origText.substring(this.range.startOffset,this.range.endOffset);
nextText = origText.substring(this.range.endOffset);
} else if (annotationTextRange == 'textNotInRange') {
return;
}
span.textContent = text;
textParent = elem.parentElement;
textParent.replaceChild(span, elem);
if (prevText) {
var prevDOM = this.htmlDoc.createTextNode(prevText);
textParent.insertBefore(prevDOM, span);
}
if (nextText) {
var nextDOM = this.htmlDoc.createTextNode(nextText);
textParent.insertBefore(nextDOM, span.nextSibling);
}
return;
}
childCount = elem.childNodes.length;
for (var i = 0; i < childCount; i++) {
var elemChildNode = elem.childNodes[i];
if( Helper.isUndefined(elemChildNode.tagName) ||
! ( elemChildNode.tagName.toLowerCase() === 'span' &&
elemChildNode.classList.contains(this.annotationClass) ) ) {
addAnnotationElement(this, elem.childNodes[i]);
}
childCount = elem.childNodes.length;
}
}
function validateTextRange(this, elem) {
var textRange = document.createRange();
textRange.selectNodeContents (elem);
if (this.range.compareBoundaryPoints (Range.START_TO_END, textRange) <= 0) {
return 'textNotInRange';
}
else {
if (this.range.compareBoundaryPoints (Range.END_TO_START, textRange) >= 0) {
return 'textNotInRange';
}
else {
var startPoints = this.range.compareBoundaryPoints (Range.START_TO_START, textRange),
endPoints = this.range.compareBoundaryPoints (Range.END_TO_END, textRange);
if (startPoints < 0) {
if (endPoints < 0) {
return 'textBeforeRangeButIntersect';
}
else {
return "textExactlyInRange";
}
}
else {
if (endPoints > 0) {
return 'textAfterRangeButIntersect';
}
else {
if (startPoints === 0 && endPoints === 0) {
return "textExactlyInRange";
}
else {
return 'textWithinRange';
}
}
}
}
}
}
精彩评论