Zaznaczanie tekstu w elemencie (podobnie jak podświetlanie za pomocą myszy)

424

Chciałbym, aby użytkownicy kliknęli link, a następnie zaznacza tekst HTML w innym elemencie ( nie na wejściu).

Przez „wybierz” mam na myśli ten sam sposób, w jaki zaznaczasz tekst, przeciągając myszą nad nim. To był niedźwiedź do badań, ponieważ wszyscy mówią o „wybierz” lub „wyróżnij” innymi słowami.

czy to możliwe? Mój kod do tej pory:

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

Czy brakuje mi czegoś rażąco oczywistego?

Jason
źródło

Odpowiedzi:

612

Zwykły 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>

Oto działające demo . Dla tych z Was, którzy szukają wtyczki jQuery, zrobiłem też jedną z nich .


jQuery (oryginalna odpowiedź)

W tym wątku znalazłem rozwiązanie tego problemu . Byłem w stanie zmodyfikować podane informacje i wymieszać je z odrobiną jQuery, aby stworzyć całkowicie niesamowitą funkcję zaznaczania tekstu w dowolnym elemencie, niezależnie od przeglądarki:

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);
    }
}
Jason
źródło
Rozwiązanie jQuery daje mi Uncaught TypeError: Nie można odczytać właściwości „msie” niezdefiniowanej
egmfrs
@egmfrs tak, odkąd ta odpowiedź została opublikowana, jquery usunęło browsersniffer.
Jason
127

Oto wersja bez wąchania przeglądarki i bez polegania na 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);
Tim Down
źródło
czy jest jakiś sposób na wybranie tekstu div contentEditable='true'?
oldboy
@PrimitiveNom: Ten kod będzie działał na elemencie, który da się zadowolić, ale najpierw musisz się upewnić, że jest skoncentrowany.
Tim Down
18

Kod Jasona nie może być używany dla elementów wewnątrz ramki iframe (ponieważ zakres różni się od okna i dokumentu). Rozwiązałem ten problem i zmodyfikowałem go, aby mógł być używany jak każda inna wtyczka jQuery (łańcuchowa):

Przykład 1: Zaznaczenie całego tekstu w tagach <code> jednym kliknięciem i dodanie klasy „zaznaczono”:

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

Przykład 2: Po kliknięciu przycisku wybierz element wewnątrz ramki iframe:

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

Uwaga: pamiętaj, że źródło iframe powinno znajdować się w tej samej domenie, aby uniknąć błędów bezpieczeństwa.

jQuery Plugin:

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

Testowałem to w IE8, Firefox, Opera, Safari, Chrome (aktualne wersje). Nie jestem pewien, czy to działa w starszych wersjach IE (szczerze mnie to nie obchodzi).

lepe
źródło
3
$ .browser jest teraz przestarzały / usunięty - wymaga przepisania
James McCormack
2
@JamesMcCormack: tak. Nie jestem pewien, czy przepisywanie będzie tego warte, ponieważ zamieszczono tu inne rozwiązania, które nie wymagają $ .browser.
lepe
16

Ten wątek zawiera naprawdę wspaniałe rzeczy. Ale nie mogę tego zrobić na tej stronie przy użyciu FF 3.5b99 + FireBug z powodu „błędu bezpieczeństwa”.

Yipee !! Dzięki temu kodowi mogłem wybrać cały pasek boczny po prawej stronie, mam nadzieję, że pomoże Ci:

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

PS: - Nie byłem w stanie używać obiektów zwróconych przez selektory jquery, takie jak

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

   //this throws **security exception**

   r.selectNodeContents(w);
TheVillageIdiot
źródło
2
Musisz pobrać element z jQuery, gdy próbujesz wybrać obiekt jQuery: var w = $ („div.welovestackoverflow”, $ („div.sidebar”)). Get (0);
Blixt,
nie działa ... pojawia się błąd „obiekt nie obsługuje tej metody” i podświetla pierwszą linię. Zrobiłem trochę kopania i odkryłem, że istnieje „document.body.createTextRange ()”, ale potem „selectNodeContents” nie działa .... i to jest w IE
Jason
przeczytałem ten wątek, który znalazłeś ... niesamowity ... byłem w stanie stworzyć funkcję z tych informacji, która działa we wszystkich przeglądarkach. Dziękuję bardzo! Moje rozwiązanie zostało opublikowane
Jason
6

Aby wybrać zawartość dowolnego elementu, możesz użyć następującej funkcji:

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

Tę funkcję można wywołać w następujący sposób:

$('#selectme').selectText();
Wolfack
źródło
5

Szukałem tego samego, moje rozwiązanie było następujące:

$('#el-id').focus().select();
Auston
źródło
3
nie można użyć focus()w przypadku braku danych wejściowych, o to właśnie chodzi w tym pytaniu.
Jason
4
ale możesz go użyć w elemencie textarea - na ten problem trafiłem tutaj. Moja wina, że ​​nie przeczytałem pytania do końca.
Auston
4

Lubiłem odpowiedź Lepe'a z wyjątkiem kilku rzeczy:

  1. Wąchanie przeglądarki, jQuery lub brak nie jest optymalne
  2. SUCHY
  3. Nie działa w IE8, jeśli obiekt nadrzędny obiektu obj nie obsługuje metody createTextRange
  4. Zdolność Chrome do korzystania z setBaseAndExtent powinna być wykorzystywana (IMO)
  5. Nie wybierze tekstu obejmującego wiele elementów DOM (elementy w ramach elementu „zaznaczonego”). Innymi słowy, jeśli wywołasz selText na div zawierającym wiele elementów rozpiętości, nie wybierze on tekstu każdego z tych elementów. To było dla mnie przełomowe, YMMV.

Oto, co wymyśliłem, kiwając głową na odpowiedź Lepe'a w poszukiwaniu inspiracji. Jestem pewien, że będę wyśmiewany, ponieważ jest to może trochę ciężkie ręce (i właściwie może to być więcej, ale dygresuję). Ale to działa i unika wąchania przeglądarki i o to właśnie chodzi .

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

Otóż ​​to. Niektóre z nich dotyczą czytelności i / lub wygody. Testowane na komputerach Mac w najnowszych wersjach Opera, Safari, Chrome, Firefox i IE. Testowany również w IE8. Zazwyczaj deklaruję zmienne tylko wtedy, gdy są potrzebne w blokach kodu, ale jslint zasugerował, że wszystkie powinny być zadeklarowane do góry. Ok jslint.

Edytuj Nie pamiętam, aby dołączyć sposób powiązania tego z kodem operacji:

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

Twoje zdrowie

Madbreaks
źródło
Tak, to wydaje mi się ciężkie, choć wydaje się prawidłowe. Główną przeszkodą jest to, że używanie niestandardowego setBaseAndExtent()tylko dlatego, że istnieje, wydaje mi się bezcelowe, kiedy można po prostu usunąć tę gałąź i wszystko działa równie dobrze i w sposób zgodny ze standardami. Wykrywanie funkcji jest przyjemne, ale zmęczyło mnie testowanie wszystkiego dość szybko.
Tim Down
Otóż ​​@TimDown chodzi o setBaseAndExtentto, że jest to znacznie bardziej wydajne, a nawet przy dodanym ifstatemnent jest o wiele bardziej, niż jeśli „usuniesz tę gałąź”. Naprawdę nie rozumiem komentarza „Zmęczyłbym się…”? Napisz i zapomnij, jedyne, co musisz zrobić, to wywołać funkcję, a nie napisać. :)
Madbreaks
@Madbreaks Byłbym zaskoczony, gdyby setBaseAndExtentbył znacznie bardziej wydajny niż addRange. Dlaczego miałoby to być? Mój drugi komentarz był związany z etosem testowania funkcji, rozszerzonym na wszystkie interakcje DOM: jest okropnie dużo kodu, aby przetestować każdą metodę i właściwość DOM przed użyciem. Nie potępiam; Cieszę się, że mogę narysować linię i poczynić jeszcze kilka założeń w moim kodzie.
Tim Down
4

Zaktualizowana wersja działająca w 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
źródło
3

Dla każdego znacznika można zaznaczyć cały tekst wewnątrz tego znacznika za pomocą tego krótkiego i prostego kodu. Podświetli cały obszar znacznika kolorem żółtym i zaznaczy tekst wewnątrz niego jednym kliknięciem.

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
źródło
2

lepe - To działa świetnie dla mnie dzięki! Umieszczam Twój kod w pliku wtyczki, a następnie używam go w połączeniu z każdą instrukcją, abyś mógł mieć wiele tagów wstępnych i wiele linków „Zaznacz wszystko” na jednej stronie, a następnie wybierze prawidłowy kod wstępny do podświetlenia:

<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
źródło
1

Spójrz na obiekt Selection (silnik Gecko) i obiekt TextRange (silnik Trident). Nie wiem o żadnych strukturach JavaScript obsługujących tę zaimplementowaną przeglądarkę, ale nigdy go nie szukałem, więc możliwe, że ma to nawet jQuery.

Blixt
źródło
0

Metoda Tima działa idealnie w moim przypadku - zaznaczanie tekstu w div zarówno dla IE, jak i FF po zastąpieniu następującej instrukcji:

range.moveToElementText(text);

z następującymi:

range.moveToElementText(el);

Tekst w div wybiera się, klikając go za pomocą następującej funkcji jQuery:

$(function () {
    $("#divFoo").click(function () {
        selectElementText(document.getElementById("divFoo"));
    })
});
Hong
źródło
0

oto inne proste rozwiązanie, aby uzyskać zaznaczony tekst w postaci ciągu, możesz łatwo użyć tego ciągu, aby dodać element div elementu podrzędnego do swojego kodu:

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();
Nadeem Yasin
źródło
Służy do zwracania podświetlonego tekstu, a nie tworzenia podświetlenia.
MKN Web Solutions
0

Moim szczególnym przypadkiem użycia było wybranie zakresu tekstu w edytowalnym elemencie zakresu, który, o ile mogłem zobaczyć, nie jest opisany w żadnej z odpowiedzi tutaj.

Główną różnicą jest to, że musisz przekazać węzeł typu Textdo Rangeobiektu, jak opisano w dokumentacji Range.setStart () :

Jeśli startNode jest Węzłem typu Tekst, Komentarz lub CDATASection , to startOffset to liczba znaków od początku startNode. W przypadku innych typów węzłów startOffset to liczba węzłów potomnych między początkiem węzła startNode.

TextWęzeł węzeł jest pierwszym dzieckiem elementu przęsła, tak aby zdobyć, dostęp do childNodes[0]elementu zakresu. Reszta jest taka sama jak w większości innych odpowiedzi.

Oto przykład kodu:

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

Inna istotna dokumentacja: Wybór
zakresu Document.createRange () Window.getSelection ()


Domysee
źródło
-1

Dodano jQuery.browser.webkitdo „else if” dla Chrome. Nie można uruchomić tego w Chrome 23.

Wykonałem poniższy skrypt do wybierania treści w <pre>znaczniku, który ma 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 );
        }
    } );
} );
Derk
źródło
-1

Zgodnie z dokumentacją jQuery select():

Uruchom zdarzenie select każdego dopasowanego elementu. Powoduje to, że wszystkie funkcje powiązane z tym zdarzeniem select zostaną wykonane, i wywołuje domyślną akcję select przeglądarki na pasujących elementach.

Wyjaśniasz, dlaczego jQuery select()nie działa w tym przypadku.

Kees de Kooter
źródło
4
nie próbuję podświetlać tekstu stylem css. Chcę, aby tekst został wybrany.
Jason