Выделение выбора пользователя javascript

Я пытаюсь найти способ с помощью 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 .. жалость.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
24
0
33 766
7

Ответы 7

<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>

Это лучшая идея. Хороший ответ, но было бы неплохо потом снова выключить designMode.

Tim Down 12.01.2010 00:18

Хотя сообщения довольно старые, я все еще считаю это лучшим решением, которое я могу найти прямо сейчас

raoulinski 28.08.2014 19:14

К сожалению, эта функция устарела. developer.mozilla.org/en-US/docs/Web/API/Document/execComman‌ d

Almenon 07.06.2020 19:29

Что ж, вы можете сделать это, используя манипуляции с 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.

Это не работает, если выделение пересекает границы элемента (например, если оно занимает несколько абзацев).

Tim Down 05.01.2012 15:10

Этот ответ, вероятно, запоздал для вас на несколько лет, но я столкнулся с аналогичной проблемой и хотел задокументировать ее здесь, поскольку это первое попадание в 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, чтобы заставить множество разрозненных элементов хорошо смотреться вместе. Я надеюсь, что в конце концов это поможет какой-нибудь другой уставшей душе в Интернете!

Хотел бы я дать вам два положительных голоса за вашу усердную работу над этим.

The Unknown Dev 12.02.2016 00:57

Хорошая работа, но ... Ограничение этого решения - разделение целого на части и оставление этих частей разделенными. Вам также понадобится общий класс или поиск атрибутов, чтобы повторно объединить их, когда вы используете их так, как ожидает пользователь (создал их).

pds 21.03.2018 12:27

Большое спасибо за решение этой проблемы для всех нас. Это прекрасно работает. Один момент, который я хочу отметить в выделять, заключается в том, что эта функция возвращает несколько пустых диапазонов, создавая несколько ненужных элементов диапазона. Я добавил дополнительное условие в свою функцию highlightRange, чтобы избежать этой проблемы. Это условие для пропуска любого пустого диапазона. Спасибо, if (range.toString ()! == "" && range.toString (). Match (/ \ w + / g)! == null) {}

V P 28.04.2019 06:29

По большей части это работает хорошо, но не срабатывает при выделении прямого дочернего комментария к родительскому комментарию в reddit.com/r/Tinder/comments/gyb7vc/… «dm me ...» остается невыделенным.

Almenon 07.06.2020 20:04

Сегодня у меня была такая же проблема: выделение выбранных тегов в нескольких тегах. Решение:

  1. Найдите способ извлечь выбранную часть вместе с HTML-тегами.
  2. Оберните извлеченную часть элементом span и верните ее в DOM..

Обратитесь к приведенному ниже коду для получения дополнительных разъяснений.

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);
}

Это не работает при попытке выделить несколько элементов.

Zach Saucier 27.06.2018 23:37

@henrique Есть ли шанс сохранить выделенное в БД? Значит, когда вы вернетесь, он все еще выделен?

Andrew 10.07.2018 18:26

Вот полный код для выделения и снятия выделения текста

<!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/

Не работает для выделения текста в нескольких абзацах.

zgjie 16.07.2020 13:16

Я только что закончил выпуск пакета, который представляет собой порт машинописного текста для 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>

Другие вопросы по теме