Выделение текста в элементе (сродни выделению мышью)

424

Я бы хотел, чтобы пользователи нажимали на ссылку, а затем он выбирал текст HTML в другом элементе (а не во входных данных).

Под «выбором» я подразумеваю то же самое, что и при выделении текста, перетаскивая на него мышку. Это было медведем для исследования, потому что все говорят о «избранном» или «выделении» другими словами.

Это возможно? Мой код до сих пор:

HTML:

<a href="javascript:" onclick="SelectText('xhtml-code')">Select Code</a>
<code id="xhtml-code">Some Code here </code>

JS:

function SelectText(element) {
    $("#" + element).select();
}

Я что-то упускаю явно очевидное?

Джейсон
источник

Ответы:

613

Простой Javascript

function selectText(node) {
    node = document.getElementById(node);

    if (document.body.createTextRange) {
        const range = document.body.createTextRange();
        range.moveToElementText(node);
        range.select();
    } else if (window.getSelection) {
        const selection = window.getSelection();
        const range = document.createRange();
        range.selectNodeContents(node);
        selection.removeAllRanges();
        selection.addRange(range);
    } else {
        console.warn("Could not select text in node: Unsupported browser.");
    }
}

const clickable = document.querySelector('.click-me');
clickable.addEventListener('click', () => selectText('target'));
<div id="target"><p>Some text goes here!</p><p>Moar text!</p></div>
<p class="click-me">Click me!</p>

Вот рабочая демка . Для тех из вас, кто ищет плагин jQuery, я тоже сделал один из них .


JQuery (оригинальный ответ)

Я нашел решение для этого в этой теме . Мне удалось изменить предоставленную информацию и смешать ее с небольшим количеством jQuery, чтобы создать совершенно потрясающую функцию для выделения текста в любом элементе, независимо от браузера:

function SelectText(element) {
    var text = document.getElementById(element);
    if ($.browser.msie) {
        var range = document.body.createTextRange();
        range.moveToElementText(text);
        range.select();
    } else if ($.browser.mozilla || $.browser.opera) {
        var selection = window.getSelection();
        var range = document.createRange();
        range.selectNodeContents(text);
        selection.removeAllRanges();
        selection.addRange(range);
    } else if ($.browser.safari) {
        var selection = window.getSelection();
        selection.setBaseAndExtent(text, 0, text, 1);
    }
}
Джейсон
источник
Решение jQuery дает мне Uncaught TypeError: Невозможно прочитать свойство 'msie' из undefined
egmfrs
@egmfrs да, так как этот ответ был опубликован, jquery удалил их browserанализатор.
Джейсон
127

Вот версия, в которой браузер не анализирует и не использует jQuery:

function selectElementText(el, win) {
    win = win || window;
    var doc = win.document, sel, range;
    if (win.getSelection && doc.createRange) {
        sel = win.getSelection();
        range = doc.createRange();
        range.selectNodeContents(el);
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (doc.body.createTextRange) {
        range = doc.body.createTextRange();
        range.moveToElementText(el);
        range.select();
    }
}

selectElementText(document.getElementById("someElement"));
selectElementText(elementInIframe, iframe.contentWindow);
Тим Даун
источник
Есть ли способ выбрать текст div contentEditable='true'?
старик
@PrimitiveNom: этот код будет работать с contenteditable элементом, но вы должны сначала убедиться, что он имеет фокус.
Тим Даун
18

Код Джейсона нельзя использовать для элементов внутри iframe (так как область видимости отличается от окна и документа). Я исправил эту проблему и изменил ее для использования в качестве любого другого плагина jQuery (цепного):

Пример 1. Выделение всего текста внутри тегов <code> одним щелчком мыши и добавление класса selected.

$(function() {
    $("code").click(function() {
        $(this).selText().addClass("selected");
    });
});

Пример 2: При нажатии кнопки выберите элемент внутри Iframe:

$(function() {
    $("button").click(function() {
        $("iframe").contents().find("#selectme").selText();
    });
});

Примечание: помните, что источник iframe должен находиться в том же домене, чтобы избежать ошибок безопасности.

Плагин jQuery:

jQuery.fn.selText = function() {
    var obj = this[0];
    if ($.browser.msie) {
        var range = obj.offsetParent.createTextRange();
        range.moveToElementText(obj);
        range.select();
    } else if ($.browser.mozilla || $.browser.opera) {
        var selection = obj.ownerDocument.defaultView.getSelection();
        var range = obj.ownerDocument.createRange();
        range.selectNodeContents(obj);
        selection.removeAllRanges();
        selection.addRange(range);
    } else if ($.browser.safari) {
        var selection = obj.ownerDocument.defaultView.getSelection();
        selection.setBaseAndExtent(obj, 0, obj, 1);
    }
    return this;
}

Я тестировал его в IE8, Firefox, Opera, Safari, Chrome (текущие версии). Я не уверен, работает ли он в старых версиях IE (искренне, мне все равно).

Лепе
источник
3
$ .browser теперь устарел / удален - это нужно переписать
Джеймс Маккормак,
2
@JamesMcCormack: да. Я не уверен, что переписывание будет стоить того, поскольку здесь размещены другие решения, в которых не используется $ .browser.
Лепе
16

Эта тема содержит действительно замечательные вещи. Но я не могу сделать это прямо на этой странице, используя FF 3.5b99 + FireBug из-за «Ошибка безопасности».

Yipee !! Я смог выделить всю правую боковую панель с этим кодом, надеюсь, он вам поможет:

    var r = document.createRange();
    var w=document.getElementById("sidebar");  
    r.selectNodeContents(w);  
    var sel=window.getSelection(); 
    sel.removeAllRanges(); 
    sel.addRange(r); 

PS: - Я не смог использовать объекты, возвращаемые селекторами jquery, такими как

   var w=$("div.welovestackoverflow",$("div.sidebar"));

   //this throws **security exception**

   r.selectNodeContents(w);
TheVillageIdiot
источник
2
Вам нужно получить элемент из jQuery, так как вы пытаетесь выбрать объект jQuery: var w = $ ("div.welovestackoverflow", $ ("div.sidebar")). Get (0);
Blixt
не работает ... я получаю сообщение об ошибке "объект не поддерживает этот метод" и выделяет первую строку. я немного покопался и обнаружил, что есть «document.body.createTextRange ()», но тогда «selectNodeContents» не работает .... и это в IE
Джейсон
я прочитал ту ветку, которую вы нашли ... удивительно ... я смог создать функцию из этой информации, которая работает во всех браузерах. Спасибо огромное! Мое решение опубликовано
Джейсон
6

Вы можете использовать следующую функцию для выбора содержимого любого элемента:

jQuery.fn.selectText = function(){
    this.find('input').each(function() {
        if($(this).prev().length == 0 || !$(this).prev().hasClass('p_copy')) { 
            $('<p class="p_copy" style="position: absolute; z-index: -1;"></p>').insertBefore($(this));
        }
        $(this).prev().html($(this).val());
    });
    var doc = document;
    var element = this[0];
    console.log(this, element);
    if (doc.body.createTextRange) {
        var range = document.body.createTextRange();
        range.moveToElementText(element);
        range.select();
    } else if (window.getSelection) {
        var selection = window.getSelection();        
        var range = document.createRange();
        range.selectNodeContents(element);
        selection.removeAllRanges();
        selection.addRange(range);
    }
};

Эта функция может быть вызвана следующим образом:

$('#selectme').selectText();
Wolfack
источник
5

Я искал то же самое, мое решение было таким:

$('#el-id').focus().select();
Auston
источник
3
Вы не можете использовать focus()не входные данные, о чем этот вопрос.
Джейсон
4
но вы можете использовать его на элементе textarea - что я и решил найти здесь. Я виноват в том, что не прочитал вопрос до конца.
Остон
4

Мне понравился ответ Лепе, за исключением нескольких вещей:

  1. Обнюхивание браузера, JQuery или нет не является оптимальным
  2. DRY
  3. Не работает в IE8, если родительский объект obj не поддерживает createTextRange
  4. Возможность Chrome использовать setBaseAndExtent должна быть усилена (IMO)
  5. Не будет выделять текст, охватывающий несколько элементов DOM (элементов внутри «выбранного» элемента). Другими словами, если вы вызовете selText для div, содержащего несколько элементов span, он не будет выделять текст каждого из этих элементов. Для меня это было нарушением соглашения, YMMV.

Вот то, что я придумал, с кивком на ответ Лепе для вдохновения. Я уверен, что меня высмеют, поскольку это, возможно, немного жестко (и на самом деле может быть более, но я отвлекся). Но это работает и избегает перехвата браузера, и в этом суть .

selectText:function(){

    var range,
        selection,
        obj = this[0],
        type = {
            func:'function',
            obj:'object'
        },
        // Convenience
        is = function(type, o){
            return typeof o === type;
        };

    if(is(type.obj, obj.ownerDocument)
        && is(type.obj, obj.ownerDocument.defaultView)
        && is(type.func, obj.ownerDocument.defaultView.getSelection)){

        selection = obj.ownerDocument.defaultView.getSelection();

        if(is(type.func, selection.setBaseAndExtent)){
            // Chrome, Safari - nice and easy
            selection.setBaseAndExtent(obj, 0, obj, $(obj).contents().size());
        }
        else if(is(type.func, obj.ownerDocument.createRange)){

            range = obj.ownerDocument.createRange();

            if(is(type.func, range.selectNodeContents)
                && is(type.func, selection.removeAllRanges)
                && is(type.func, selection.addRange)){
                // Mozilla
                range.selectNodeContents(obj);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        }
    }
    else if(is(type.obj, document.body) && is(type.obj, document.body.createTextRange)) {

        range = document.body.createTextRange();

        if(is(type.obj, range.moveToElementText) && is(type.obj, range.select)){
            // IE most likely
            range.moveToElementText(obj);
            range.select();
        }
    }

    // Chainable
    return this;
}

Вот и все. Кое-что из того, что вы видите, предназначено для удобства чтения и / или удобства. Протестировано на Mac в последних версиях Opera, Safari, Chrome, Firefox и IE. Также проверено в IE8. Также я обычно объявляю переменные только в том случае, если / когда это необходимо, внутри блоков кода, но jslint предлагает объявить их все вверху. Хорошо, JSLINT.

Редактировать Я забыл включить, как связать это с кодом операции:

function SelectText(element) {
    $("#" + element).selectText();
}

ура

Madbreaks
источник
Да, это выглядит для меня властно, хотя кажется правильным. Моя главная проблема в том, что использование нестандартного setBaseAndExtent()только потому, что оно существует, кажется мне бессмысленным, когда вы можете просто удалить эту ветку, и все работает так же хорошо и на основе стандартов. Функция обнаружения хороша, но я бы устал от тестирования всего этого довольно быстро.
Тим Даун
Ну, @TimDown смысл использования setBaseAndExtentзаключается в том, что он значительно более эффективен, и даже с добавленным ifstatemnent все еще намного больше, чем если бы вы «удалили эту ветку». Я не очень понимаю комментарий "Я бы устал ..."? Запишите это и забудьте, единственное, что вам нужно сделать, это вызвать функцию, а не написать ее. :)
Madbreaks
@Madbreaks Я был бы удивлен, если бы setBaseAndExtentбыл значительно более производительным, чем addRange. С чего бы это? Мой другой комментарий был связан с вашим принципом тестирования возможностей, распространяющимся на все взаимодействия с DOM: очень много кода для тестирования каждого метода и свойства DOM перед его использованием. Я не одобряю; Я просто счастлив подвести черту и сделать несколько допущений в своем коде.
Тим Даун
4

Обновленная версия, которая работает в Chrome:

function SelectText(element) {
    var doc = document;
    var text = doc.getElementById(element);    
    if (doc.body.createTextRange) { // ms
        var range = doc.body.createTextRange();
        range.moveToElementText(text);
        range.select();
    } else if (window.getSelection) {
        var selection = window.getSelection();
        var range = doc.createRange();
        range.selectNodeContents(text);
        selection.removeAllRanges();
        selection.addRange(range);

    }
}

$(function() {
    $('p').click(function() {
        SelectText("selectme");

    });
});

http://jsfiddle.net/KcX6A/326/

Kimtho6
источник
3

Для любого тега можно выбрать весь текст внутри этого тега с помощью этого короткого и простого кода. Он выделит всю область тега желтым цветом и выделит текст внутри него одним щелчком мыши.

document.onclick = function(event) {
    var range, selection;
event.target.style.backgroundColor = 'yellow';
        selection = window.getSelection();
        range = document.createRange();
        range.selectNodeContents(event.target);
        selection.removeAllRanges();
        selection.addRange(range);
};
knjhaveri
источник
2

Лепе - Это прекрасно работает для меня, спасибо! Я поместил ваш код в файл плагина, а затем использовал его вместе с каждым оператором, чтобы вы могли иметь несколько предварительных тегов и несколько ссылок «Выбрать все» на одной странице, и он выбирает правильный предварительный текст для выделения:

<script type="text/javascript" src="../js/jquery.selecttext.js"></script>
<script type="text/javascript">
  $(document).ready(function() { 
        $(".selectText").each(function(indx) {
                $(this).click(function() {                 
                    $('pre').eq(indx).selText().addClass("selected");
                        return false;               
                    });
        });
  });

Daver
источник
1

Взгляните на объект Selection (движок Gecko) и объект TextRange (движок Trident). Я не знаю ни о каких фреймворках JavaScript, в которых реализована кросс-браузерная поддержка, но я также никогда не искал его, поэтому Вполне возможно, что даже JQuery есть.

Blixt
источник
0

Метод Тима отлично работает для моего случая - выделение текста в div для IE и FF после того, как я заменил следующее утверждение:

range.moveToElementText(text);

со следующим:

range.moveToElementText(el);

Текст в div выбирается щелчком по нему с помощью следующей функции jQuery:

$(function () {
    $("#divFoo").click(function () {
        selectElementText(document.getElementById("divFoo"));
    })
});
Hong
источник
0

Вот еще одно простое решение для получения выделенного текста в виде строки. Вы можете легко использовать эту строку, чтобы добавить дочерний элемент div в ваш код:

var text = '';

if (window.getSelection) {
    text = window.getSelection();

} else if (document.getSelection) {
    text = document.getSelection();

} else if (document.selection) {
    text = document.selection.createRange().text;
}

text = text.toString();
Надим Ясин
источник
Это для возврата выделенного текста, а не создания выделения.
MKN Web Solutions
0

Мой конкретный вариант использования заключался в выборе текстового диапазона внутри редактируемого элемента span, который, насколько я мог видеть, не описан ни в одном из ответов здесь.

Основное отличие заключается в том , что вы должны пройти узел типа Textк Rangeобъекту, как описано в документации Range.setStart () :

Если startNode является узлом типа Text, Comment или CDATASection , тогда startOffset - это количество символов от начала startNode. Для других типов узлов startOffset - это число дочерних узлов между началом startNode.

TextУзел является первым дочерним узлом двускатного элемента, так , чтобы получить его, доступ childNodes[0]элемента диапазона. Остальное такое же, как и в большинстве других ответов.

Вот пример кода:

var startIndex = 1;
var endIndex = 5;
var element = document.getElementById("spanId");
var textNode = element.childNodes[0];

var range = document.createRange();
range.setStart(textNode, startIndex);
range.setEnd(textNode, endIndex);

var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);

Другая соответствующая документация: Выбор
диапазона Document.createRange () Window.getSelection ()


Domysee
источник
-1

Добавлено jQuery.browser.webkitв «еще, если» для Chrome. Не удалось заставить это работать в Chrome 23.

Сделал этот скрипт ниже для выбора контента в <pre>теге, который имеет class="code".

jQuery( document ).ready(function() {
    jQuery('pre.code').attr('title', 'Click to select all');
    jQuery( '#divFoo' ).click( function() {
        var refNode = jQuery( this )[0];
        if ( jQuery.browser.msie ) {
            var range = document.body.createTextRange();
            range.moveToElementText( refNode );
            range.select();
        } else if ( jQuery.browser.mozilla || jQuery.browser.opera  || jQuery.browser.webkit ) {
            var selection = refNode.ownerDocument.defaultView.getSelection();
            console.log(selection);
            var range = refNode.ownerDocument.createRange();
            range.selectNodeContents( refNode );
            selection.removeAllRanges();
            selection.addRange( range );
        } else if ( jQuery.browser.safari ) {
            var selection = refNode.ownerDocument.defaultView.getSelection();
            selection.setBaseAndExtent( refNode, 0, refNode, 1 );
        }
    } );
} );
Дерк
источник
-1

Согласно документации jQuery select():

Инициируйте событие выбора каждого соответствующего элемента. Это приводит к выполнению всех функций, связанных с этим событием select, и вызывает действие выбора браузера по умолчанию для соответствующих элементов.

Вот ваше объяснение, почему jQuery select()не будет работать в этом случае.

Кеес де Коутер
источник
4
Я не пытаюсь выделить текст в стиле CSS. Я хочу, чтобы текст был выбран.
Джейсон