Я пытаюсь найти способ с помощью javascript выделить текст, который пользователь выбирает, когда он нажимает какую-то странную кнопку выделения (как в <span style = "background-color: yellow"> выделенный текст </span>). Он должен работать только с 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>
Когда пользователь выбирает в браузере от «is text» до «this is» (даже если вы можете обернуть выделенные области вокруг каждого элемента в выделенном фрагменте, я бы хотел увидеть, как вы попытаетесь выделить этот средний текст).
Кажется, эта проблема нигде не решена, честно говоря, я сомневаюсь, что это возможно.
Было бы возможно, если бы вы могли получить Range, который вы получаете из выбора, в виде строки с html, который можно было бы проанализировать, а затем заменить, но, насколько я могу судить, вы не можете получить необработанный html Range .. жалость.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


<html>
<head>
<script type = "text/javascript">
function load(){
window.document.designMode = "On";
//run this in a button, will highlight selected text
window.document.execCommand("hiliteColor", false, "#000");
}
</script>
</head>
<body contentEditable = "true" onload = "load()">
this is text
</body>
</html>
Хотя сообщения довольно старые, я все еще считаю это лучшим решением, которое я могу найти прямо сейчас
К сожалению, эта функция устарела. developer.mozilla.org/en-US/docs/Web/API/Document/execComman d
Что ж, вы можете сделать это, используя манипуляции с 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.
Это не работает, если выделение пересекает границы элемента (например, если оно занимает несколько абзацев).
Этот ответ, вероятно, запоздал для вас на несколько лет, но я столкнулся с аналогичной проблемой и хотел задокументировать ее здесь, поскольку это первое попадание в Google.
Повторюсь, проблема заключается в том, что вы хотели бы просто захватить объект 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);
}
Но, как заявляет исходный родитель, это небезопасно. Он будет работать, если выделение не пересекает границы элемента, но вызовет ошибку DOM, если диапазон, созданный пользовательским выбором, является небезопасным диапазоном, который пересекает границы тегов HTML.
Решение предназначен для создания массива меньших объектов 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, чтобы избежать этой проблемы. Это условие для пропуска любого пустого диапазона. Спасибо, if (range.toString ()! == "" && range.toString (). Match (/ \ w + / g)! == null) {}
По большей части это работает хорошо, но не срабатывает при выделении прямого дочернего комментария к родительскому комментарию в reddit.com/r/Tinder/comments/gyb7vc/… «dm me ...» остается невыделенным.
Сегодня у меня была такая же проблема: выделение выбранных тегов в нескольких тегах. Решение:
Обратитесь к приведенному ниже коду для получения дополнительных разъяснений.
function getRangeObject(selectionObject){
try{
if (selectionObject.getRangeAt)
return selectionObject.getRangeAt(0);
}
catch(ex){
console.info(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;
}
};
Я публикую здесь впервые, но, просматривая ваши ответы, не сработает ли что-то подобное? У меня есть образец: 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 Есть ли шанс сохранить выделенное в БД? Значит, когда вы вернетесь, он все еще выделен?
Вот полный код для выделения и снятия выделения текста
<!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/
Не работает для выделения текста в нескольких абзацах.
Я только что закончил выпуск пакета, который представляет собой порт машинописного текста для texthighlighter (устаревшая библиотека). Простое преобразование его в машинописный текст выявило несколько ошибок и упростило работу над ним в будущем. Оформить заказ https://www.npmjs.com/package/@funktechno/texthighlighter. Это не имеет зависимостей и позволяет выделять выделение пользователя, объединять выделения, удалять выделения, сериализовать и десериализовать (применять на основе данных) выделения.
Обратите внимание, что вам нужно будет использовать событие mouseup javascript, чтобы правильно его запустить.
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>
Это лучшая идея. Хороший ответ, но было бы неплохо потом снова выключить
designMode.