Selecionando texto em um elemento (semelhante ao destaque com o mouse)

424

Gostaria que os usuários cliquem em um link e, em seguida, ele seleciona o texto HTML em outro elemento ( não uma entrada).

Por "selecionar", quero dizer da mesma maneira que você selecionaria o texto arrastando o mouse sobre ele. Isso tem sido motivo de pesquisa, porque todo mundo fala sobre "selecionar" ou "destacar" em outros termos.

Isso é possível? Meu código até agora:

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

Estou perdendo algo flagrantemente óbvio?

Jason
fonte

Respostas:

613

Javascript simples

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>

Aqui está uma demonstração de trabalho . Para aqueles que procuram um plugin jQuery, eu também fiz um .


jQuery (resposta original)

Eu encontrei uma solução para isso neste tópico . Consegui modificar as informações fornecidas e misturá-las com um pouco de jQuery para criar uma função totalmente impressionante para selecionar o texto em qualquer elemento, independentemente do navegador:

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
fonte
solução jQuery me dá TypeError não capturado: Não é possível ler a propriedade 'msie' de indefinido
egmfrs
@egmfrs sim, desde que esta resposta foi publicada, o jquery removeu o browsersniffer.
Jason
127

Aqui está uma versão sem farejador de navegador e sem dependência do 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
fonte
existe alguma maneira de selecionar o texto de a div contentEditable='true'?
Oldboy
@PrimitiveNom: este código funcionará em um elemento editável por conteúdo, mas você precisará garantir que ele se concentre primeiro.
Tim Baixo
18

O código de Jason não pode ser usado para elementos dentro de um iframe (como o escopo difere da janela e do documento). Corrigi o problema e o modifiquei para ser usado como qualquer outro plugin do jQuery (encadeado):

Exemplo 1: Seleção de todo o texto dentro das tags <code> com um único clique e adicione a classe "selecionada":

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

Exemplo 2: Ao clicar no botão, selecione um elemento dentro de um Iframe:

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

Nota: lembre-se de que a origem do iframe deve residir no mesmo domínio para evitar erros de segurança.

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

Eu testei no IE8, Firefox, Opera, Safari, Chrome (versões atuais). Não tenho certeza se funciona em versões mais antigas do IE (sinceramente, não me importo).

lepe
fonte
3
$ .browser agora está obsoleta / removido - este precisa de uma reescrita
James McCormack
2
@JamesMcCormack: sim. Não tenho certeza se reescrever valerá a pena, pois existem outras soluções postadas aqui que não envolvem $ .browser.
Lepe
16

Esta discussão contém coisas realmente maravilhosas. Mas não consigo fazer isso direito nesta página usando o FF 3.5b99 + FireBug devido a "Erro de segurança".

Yipee !! Consegui selecionar toda a barra lateral direita com este código, espero que ajude você:

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

PS: - Não pude usar objetos retornados por seletores jquery como

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

   //this throws **security exception**

   r.selectNodeContents(w);
TheVillageIdiot
fonte
2
Você precisa obter o elemento do jQuery, enquanto tenta selecionar um objeto jQuery: var w = $ ("div.welovestackoverflow", $ ("div.sidebar")). Get (0);
Blixt
não funciona ... recebo um erro "objeto não suporta este método" e destaca a primeira linha. Eu fiz algumas escavações e descobri que existe um "document.body.createTextRange ()", mas depois "selectNodeContents" não funciona .... e isso está no IE
Jason
Eu li o tópico que você encontrou ... incrível ... eu era capaz de criar uma função a partir dessa informação que funciona em todos os navegadores. Muito obrigado! Minha solução está publicada
Jason
6

Você pode usar a seguinte função para selecionar o conteúdo de qualquer elemento:

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

Esta função pode ser chamada da seguinte maneira:

$('#selectme').selectText();
Wolfack
fonte
5

Eu estava procurando a mesma coisa, minha solução foi a seguinte:

$('#el-id').focus().select();
Auston
fonte
3
você não pode usar focus()em uma não entrada, que é a questão desta pergunta.
21411 Jason
4
mas você pode usá-lo em um elemento textarea - que foi o problema que pesquisei ao chegar aqui. Minha culpa por não ter lido a pergunta até o fim.
Auston # 28/04
4

Gostei da resposta de lepe, exceto por algumas coisas:

  1. Detectar o navegador, jQuery ou não, não é o ideal
  2. SECO
  3. Não funciona no IE8 se o pai do obj não suportar createTextRange
  4. Capacidade do Chrome de usar setBaseAndExtent deve ser aproveitada (IMO)
  5. Não seleciona texto que se estende por vários elementos DOM (elementos dentro do elemento "selecionado"). Em outras palavras, se você chamar selText em uma div contendo vários elementos de amplitude, ele não selecionará o texto de cada um desses elementos. Isso foi um negócio para mim, YMMV.

Aqui está o que eu criei, com um aceno de cabeça para a resposta de Lepe em busca de inspiração. Tenho certeza de que serei ridicularizado, pois isso talvez seja um pouco pesado (e na verdade poderia ser mais, mas eu discordo). Mas funciona e evita farejar o navegador, e esse é o ponto .

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

É isso aí. Parte do que você vê é de legibilidade e / ou conveniência. Testado no Mac nas versões mais recentes do Opera, Safari, Chrome, Firefox e IE. Também testado no IE8. Também normalmente declaro apenas variáveis ​​se / quando necessárias dentro de blocos de código, mas o jslint sugeriu que todas elas fossem declaradas no topo. Ok jslint.

Editar Eu esqueci de incluir como vincular isso ao código da operação:

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

Felicidades

Madbreaks
fonte
Sim, isso parece pesado para mim, embora pareça correto. Minha queixa principal é que usar o não-padrão setBaseAndExtent()apenas porque existe parece inútil para mim quando você pode simplesmente remover esse ramo e tudo funciona tão bem quanto de maneira baseada em padrões. A detecção de recursos é boa, mas eu me cansaria de testar tudo rapidamente.
Tim Baixo
Bem, o @TimDown, o ponto de alavancagem setBaseAndExtenté que é significativamente mais eficiente, e mesmo com a ifdeclaração adicional ainda é muito mais do que se você "remover esse ramo". Eu realmente não entendo o comentário de "Eu me cansaria .."? Escreva e esqueça, a única coisa que você precisa fazer é chamar a função, não escrevê-la. :)
Madbreaks
@ Madbreaks Eu ficaria surpreso se setBaseAndExtenttivesse um desempenho significativamente maior que o addRange. Por que seria? Meu outro comentário foi relacionado ao ethos de teste de recursos estendido a todas as interações do DOM: é muito código para testar todos os métodos e propriedades do DOM antes de usá-lo. Eu não desaprovo; Estou feliz em traçar a linha e fazer mais algumas suposições no meu código.
Tim Down
4

Uma versão atualizada que funciona no 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
fonte
3

Para qualquer tag, é possível selecionar todo o texto dentro dessa tag por este código curto e simples. Ele destacará toda a área da etiqueta com a cor amarela e selecionará o texto dentro dela com um único clique.

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

lepe - Isso funciona muito bem para mim, obrigado! Coloquei seu código em um arquivo de plug-in e, em seguida, usei-o em conjunto com cada instrução, para que você possa ter várias pré tags e vários links "Selecionar tudo" em uma página e escolher a pré correta para destacar:

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

Dê uma olhada no objeto Selection (mecanismo Gecko) e no objeto TextRange (mecanismo Trident.) Não conheço nenhuma estrutura JavaScript que tenha suporte entre navegadores para isso implementado, mas também nunca o procurei. é possível que até o jQuery tenha.

Blixt
fonte
0

O método de Tim funciona perfeitamente para o meu caso - selecionando o texto em uma div para o IE e o FF após a substituição da seguinte declaração:

range.moveToElementText(text);

com o seguinte:

range.moveToElementText(el);

O texto na div é selecionado clicando nele com a seguinte função jQuery:

$(function () {
    $("#divFoo").click(function () {
        selectElementText(document.getElementById("divFoo"));
    })
});
Hong
fonte
0

aqui está outra solução simples para selecionar o texto na forma de string, você pode usar essa string facilmente para acrescentar um filho do elemento div ao seu código:

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
fonte
Isto é para retornar o texto destacado, não criando um destaque.
MKN Web Solutions
0

Meu caso de uso específico foi selecionar um intervalo de texto dentro de um elemento de extensão editável que, até onde pude ver, não está descrito em nenhuma das respostas aqui.

A principal diferença é que você precisa passar um nó do tipo Textpara o Rangeobjeto, conforme descrito na documentação de Range.setStart () :

Se o startNode for um Nó do tipo Text, Comment ou CDATASection , startOffset é o número de caracteres desde o início do startNode. Para outros tipos de nós, startOffset é o número de nós filhos entre o início do startNode.

O Textnó é o primeiro nó filho de um elemento de extensão; portanto, para obtê-lo, acesso childNodes[0]ao elemento de extensão. O resto é o mesmo que na maioria das outras respostas.

Aqui está um exemplo de código:

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

Outra documentação relevante: Seleção de
intervalo Document.createRange () Window.getSelection ()


Domysee
fonte
-1

Adicionado jQuery.browser.webkitao "else if" no Chrome. Não foi possível fazer isso funcionar no Chrome 23.

Crie este script abaixo para selecionar o conteúdo em uma <pre>tag que possui o 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
fonte
-1

De acordo com a documentação do jQuery de select():

Acione o evento de seleção de cada elemento correspondente. Isso faz com que todas as funções vinculadas a esse evento de seleção sejam executadas e chama a ação de seleção padrão do navegador no (s) elemento (s) correspondente (s).

A sua explicação é por que o jQuery select()não funcionará neste caso.

Kees de Kooter
fonte
4
Não estou tentando destacar o texto com um estilo CSS. Eu quero que o texto seja selecionado.
1212 Jason