Seleccionar texto en un elemento (similar a resaltar con el mouse)

424

Me gustaría que los usuarios hagan clic en un enlace, luego selecciona el texto HTML en otro elemento ( no una entrada).

Por "seleccionar" me refiero a la misma forma en que seleccionaría texto arrastrando el mouse sobre él. Esto ha sido difícil de investigar porque todos hablan de "seleccionar" o "resaltar" en otros términos.

es posible? Mi código hasta ahora:

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

¿Me estoy perdiendo algo descaradamente obvio?

Jason
fuente

Respuestas:

613

Javascript simple

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>

Aquí hay una demostración en funcionamiento . Para aquellos de ustedes que buscan un complemento jQuery, también hice uno de esos .


jQuery (respuesta original)

He encontrado una solución para esto en este hilo . Pude modificar la información dada y mezclarla con un poco de jQuery para crear una función totalmente increíble para seleccionar el texto en cualquier elemento, independientemente del 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
fuente
La solución jQuery me da Error de tipo no
capturado
@egmfrs sí, desde que se publicó esta respuesta, jquery ha eliminado su browsersniffer.
Jason
127

Aquí hay una versión sin rastreo del navegador y sin confianza en 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
fuente
¿Hay alguna manera de seleccionar el texto de un div contentEditable='true'?
oldboy
@PrimitiveNom: este código funcionará en un elemento contentable, pero primero deberá asegurarse de que tenga el foco.
Tim Down
18

El código de Jason no se puede usar para elementos dentro de un iframe (ya que el alcance difiere de la ventana y el documento). Solucioné ese problema y lo modifiqué para usarlo como cualquier otro complemento jQuery (encadenable):

Ejemplo 1: Selección de todo el texto dentro de las etiquetas <código> con un solo clic y agregue la clase "seleccionado":

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

Ejemplo 2: al hacer clic en el botón, seleccione un elemento dentro de un Iframe:

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

Nota: recuerde que la fuente del iframe debe residir en el mismo dominio para evitar errores de seguridad.

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

Lo probé en IE8, Firefox, Opera, Safari, Chrome (versiones actuales). No estoy seguro de si funciona en versiones anteriores de IE (sinceramente, no me importa).

lepe
fuente
3
$ .browser ahora está en desuso / eliminado - esto necesita una reescritura
James McCormack
2
@JamesMcCormack: sí. No estoy seguro de si reescribirlo valdrá la pena, ya que hay otras soluciones publicadas aquí que no involucran $ .browser.
lepe
16

Este hilo contiene cosas realmente maravillosas. Pero no puedo hacerlo bien en esta página usando FF 3.5b99 + FireBug debido a "Error de seguridad".

Yipee !! Pude seleccionar toda la barra lateral derecha con este código, espero que te ayude:

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

PD: - No pude usar objetos devueltos por selectores jquery como

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

   //this throws **security exception**

   r.selectNodeContents(w);
TheIdiot de la aldea
fuente
2
Necesita obtener el elemento de jQuery, ya que está intentando seleccionar un objeto jQuery: var w = $ ("div.welovestackoverflow", $ ("div.sidebar")). Get (0);
Blixt
no funciona ... aparece el error "el objeto no admite este método" y resalta la primera línea. investigué un poco y descubrí que hay un "document.body.createTextRange ()" pero luego "selectNodeContents" no funciona ... y esto está en IE
Jason
leí ese hilo que encontraste ... increíble ... pude crear una función a partir de esa información que funciona en todos los navegadores. Muchas gracias! Mi solución está publicada
Jason
6

Puede usar la siguiente función para seleccionar el contenido de cualquier 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 función se puede llamar de la siguiente manera:

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

Estaba buscando lo mismo, mi solución fue esta:

$('#el-id').focus().select();
Auston
fuente
3
no se puede usar focus()en una no entrada, de eso se trata esta pregunta.
Jason
44
pero puedes usarlo en un elemento textarea, que fue el problema que busqué en Google para llegar aquí. Mi culpa por no haber leído la pregunta hasta el final.
Auston
4

Me gustó la respuesta de Lepe, excepto por algunas cosas:

  1. El rastreo del navegador, jQuery o no no es óptimo
  2. SECO
  3. No funciona en IE8 si el padre de obj no admite createTextRange
  4. La capacidad de Chrome para usar setBaseAndExtent debe aprovecharse (IMO)
  5. No seleccionará texto que abarque varios elementos DOM (elementos dentro del elemento "seleccionado"). En otras palabras, si llama a selText en un div que contiene múltiples elementos span, no seleccionará el texto de cada uno de esos elementos. Eso fue un factor decisivo para mí, YMMV.

Esto es lo que se me ocurrió, con un guiño a la respuesta de Lepe en busca de inspiración. Estoy seguro de que seré ridiculizado ya que esto es quizás un poco duro (y en realidad podría ser más, pero me estoy desviando). Pero funciona y evita el rastreo del navegador y ese es el punto .

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

Eso es. Algo de lo que ve es la legibilidad y / o conveniencia. Probado en Mac en las últimas versiones de Opera, Safari, Chrome, Firefox e IE. También probado en IE8. Además, normalmente solo declaro variables si es necesario dentro de los bloques de código, pero jslint sugirió que todas se declaren en la parte superior. Ok jslint.

Editar Olvidé incluir cómo vincular esto con el código de operación:

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

Salud

Madbreaks
fuente
Sí, eso me parece duro, aunque parece correcto. Mi queja principal es que usar el no estándar setBaseAndExtent()solo porque existe me parece inútil cuando simplemente puedes eliminar esa rama y todo funciona igual de bien y de una manera basada en estándares. La detección de funciones es buena, pero me cansaría de probar todo lo que se hace a fondo bastante rápido.
Tim Down
Bueno, @TimDown el punto de apalancamiento setBaseAndExtentes que es significativamente más eficiente, e incluso con el estado agregado ifaún es mucho más que si se "elimina esa rama". ¿Realmente no entiendo el comentario de "Me cansaría ..."? Escríbelo y olvídalo, lo único que tienes que hacer es llamar a la función, no escribirla. :)
Madbreaks
@Madbreaks Me sorprendería si setBaseAndExtentfuera significativamente más eficiente que addRange. ¿Por qué sería? Mi otro comentario estaba relacionado con su ethos de prueba de características extendido a toda interacción DOM: es una gran cantidad de código para probar cada método y propiedad DOM antes de usarlo. No desapruebo; Estoy feliz de trazar la línea y hacer algunas suposiciones más en mi código.
Tim Down
4

Una versión actualizada que funciona en 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
fuente
3

Para cualquier etiqueta, se puede seleccionar todo el texto dentro de esa etiqueta mediante este código corto y simple. Destacará toda el área de la etiqueta con color amarillo y seleccionará el texto dentro de ella con un solo clic.

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

lepe - Eso me funciona muy bien, gracias! Puse su código en un archivo de complemento, luego lo usé junto con cada una de las declaraciones para que pueda tener múltiples etiquetas pre y múltiples enlaces "Seleccionar todo" en una página y selecciona la pre correcta para resaltar:

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

Eche un vistazo al objeto Selection (motor Gecko) y al objeto TextRange (motor Trident). No conozco ningún framework de JavaScript que tenga implementado soporte para varios navegadores, pero tampoco lo he buscado, así que nunca lo he buscado, así que Es posible que incluso jQuery lo tenga.

Blixt
fuente
0

El método de Tim funciona perfectamente para mi caso: seleccionando el texto en un div para IE y FF después de reemplazar la siguiente declaración:

range.moveToElementText(text);

con lo siguiente:

range.moveToElementText(el);

El texto en el div se selecciona haciendo clic con la siguiente función jQuery:

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

Aquí hay otra solución simple para obtener el texto seleccionado en forma de cadena, puede usar esta cadena fácilmente para agregar un elemento div en su 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
fuente
Esto es para devolver el texto resaltado, no para crear un resaltado.
MKN Web Solutions
0

Mi caso de uso particular fue seleccionar un rango de texto dentro de un elemento span editable, que, por lo que pude ver, no se describe en ninguna de las respuestas aquí.

La principal diferencia es que debe pasar un nodo de tipo Textal Rangeobjeto, como se describe en la documentación de Range.setStart () :

Si startNode es un nodo de tipo Text, Comment o CDATASection , startOffset es el número de caracteres desde el inicio de startNode. Para otros tipos de nodos, startOffset es el número de nodos secundarios entre el inicio de startNode.

El Textnodo es el primer nodo hijo de un elemento span, por lo que para obtenerlo, acceda childNodes[0]al elemento span. El resto es igual que en la mayoría de las otras respuestas.

Aquí un ejemplo 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);

Otra documentación relevante:
Range
Selection
Document.createRange ()
Window.getSelection ()

Domysee
fuente
-1

Agregado jQuery.browser.webkita "else if" para Chrome. No se pudo hacer que esto funcione en Chrome 23.

Hizo este script a continuación para seleccionar el contenido en una <pre>etiqueta que tiene el 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
fuente
-1

De acuerdo con la documentación de jQuery de select():

Activa el evento select de cada elemento coincidente. Esto hace que se ejecuten todas las funciones que se han vinculado a ese evento select, y llama a la acción de selección predeterminada del navegador en los elementos coincidentes.

Hay una explicación de por qué jQuery select()no funcionará en este caso.

Kees de Kooter
fuente
44
No estoy tratando de resaltar el texto con un estilo CSS. Quiero que se seleccione el texto.
Jason