JavaScript obtiene datos del portapapeles en caso de pegar (navegador cruzado)

299

¿Cómo puede una aplicación web detectar un evento de pegado y recuperar los datos que se pegarán?

Me gustaría eliminar el contenido HTML antes de que el texto se pegue en un editor de texto enriquecido.

Limpiar el texto después de pegarlo funciona, pero el problema es que se pierde todo el formato anterior. Por ejemplo, puedo escribir una oración en el editor y ponerla en negrita, pero cuando pego un texto nuevo, todo el formato se pierde. Quiero limpiar solo el texto que se pega y dejar intacto cualquier formato anterior.

Idealmente, la solución debería funcionar en todos los navegadores modernos (por ejemplo, MSIE, Gecko, Chrome y Safari).

Tenga en cuenta que MSIE tiene clipboardData.getData(), pero no pude encontrar una funcionalidad similar para otros navegadores.

Alex
fuente
Todas estas respuestas explican cómo obtener contenido de texto. Obtener contenido de imagen o contenido de archivo requiere mucho más trabajo. Tal vez podamos cambiar el título a "JavaScript obtener datos de portapapeles de texto desinfectados ..."
1,21 gigavatios
1
como dijo nico: event.clipboardData.getData('Text')funcionó para mí.
Andre Elrico
document.addEventListener('paste'...funcionó para mí, pero causó conflictos si un usuario deseaba poder pegar en otra parte de la página. Luego lo intenté myCanvasElement.addEventListener('paste'..., pero eso no funcionó. Finalmente me di cuenta de que myCanvasElement.parentElement.addEventListener('paste'...funcionó.
Ryan

Respuestas:

149

La situación ha cambiado desde que escribí esta respuesta: ahora que Firefox ha agregado soporte en la versión 22, todos los principales navegadores ahora admiten acceder a los datos del portapapeles en un evento de pegado. Vea la respuesta de Nico Burns para un ejemplo.

En el pasado, esto no era posible en general en un navegador cruzado. Lo ideal sería poder obtener el contenido pegado a través del pasteevento, lo cual es posible en navegadores recientes pero no en algunos navegadores antiguos (en particular, Firefox <22).

Cuando necesite admitir navegadores antiguos, lo que puede hacer es bastante complicado y un truco que funcionará en los navegadores Firefox 2+, IE 5.5+ y WebKit como Safari o Chrome. Las versiones recientes de TinyMCE y CKEditor utilizan esta técnica:

  1. Detectar un evento ctrl-v / shift-ins usando un controlador de eventos de pulsación de tecla
  2. En ese controlador, guarde la selección de usuario actual, agregue un elemento de área de texto fuera de la pantalla (digamos a la izquierda -1000 px) al documento, designModeapague y llame focus()al área de texto, moviendo así el cursor y redirigiendo efectivamente la pasta
  3. Configure un temporizador muy breve (digamos 1 milisegundo) en el controlador de eventos para llamar a otra función que almacene el valor del área de texto, elimine el área de texto del documento, designModevuelva a encenderlo , restaure la selección del usuario y pegue el texto.

Tenga en cuenta que esto solo funcionará para pegar eventos de teclado y no para pegar desde el contexto o editar menús. Para cuando se active el evento de pegado, es demasiado tarde para redirigir el cursor al área de texto (al menos en algunos navegadores).

En el improbable caso de que necesite admitir Firefox 2, tenga en cuenta que deberá colocar el área de texto en el documento principal en lugar del documento del iframe del editor WYSIWYG en ese navegador.

Tim Down
fuente
1
Wow, gracias por eso! Sin embargo, parece ser un truco muy sofisticado ;-) ¿Podría describir un poco más ese diseño y modo de selección, especialmente en el paso 3? ¡Muchas gracias!
Alex
55
Tuve la horrible sensación de que preguntarías eso. Como digo, está bastante involucrado: sugeriría mirar la fuente de TinyMCE o CKEditor, ya que no tengo tiempo para describir todos los problemas involucrados. Brevemente, sin embargo, designModees una propiedad booleana de documenty hace que toda la página sea editable cuando true. Los editores WYSIWYG usualmente usan un iframe con designModeon como el panel editable. Guardar y restaurar la selección del usuario se realiza de una manera en IE y otra en otros navegadores, al igual que pegar el contenido en el editor. Necesita obtener un TextRangeen IE y otro Rangeen otros navegadores.
Tim Down
66
@Samuel: Puedes detectarlo usando el pasteevento, pero generalmente es demasiado tarde para redirigir la pasta a otro elemento, por lo que este truco no funcionará. El respaldo en la mayoría de los editores es mostrar un diálogo para que el usuario lo pegue.
Tim Down
66
Más información sobre esto: Firefox no le permitirá mover el foco a otro elemento en el pasteevento, sin embargo, le permitirá borrar el contenido del elemento (y guardarlo en una variable para que pueda restaurarlo más adelante). Si este contenedor es una div(probablemente trabaja para una iframetambién) a continuación, a continuación, puede desplazarse por el contenido pegado utilizando métodos DOM normales, o conseguir como una cadena usando innerHTML. A continuación, puede restaurar el contenido anterior del dive insertar el contenido que desee. Ah, y tienes que usar el mismo truco de temporizador que el anterior. Me sorprende que TinyMCE no haga esto ...
Nico Burns
8
@ResistDesign: No estoy de acuerdo, es una forma poco elegante y complicada de compensar la falta de una API sensible. Sería mejor poder obtener el contenido pegado directamente desde el evento de pegado, que es posible de forma limitada en algunos navegadores .
Tim Down
318

Solución n. ° 1 (solo texto sin formato y requiere Firefox 22+)

Funciona para IE6 +, FF 22+, Chrome, Safari, Edge (solo probado en IE9 +, pero debería funcionar para versiones inferiores)

Si necesita soporte para pegar HTML o Firefox <= 22, consulte la Solución # 2.

HTML

<div id='editableDiv' contenteditable='true'>Paste</div>

JavaScript

function handlePaste (e) {
    var clipboardData, pastedData;

    // Stop data actually being pasted into div
    e.stopPropagation();
    e.preventDefault();

    // Get pasted data via clipboard API
    clipboardData = e.clipboardData || window.clipboardData;
    pastedData = clipboardData.getData('Text');
    
    // Do whatever with pasteddata
    alert(pastedData);
}

document.getElementById('editableDiv').addEventListener('paste', handlePaste);

JSFiddle: https://jsfiddle.net/swL8ftLs/12/

Tenga en cuenta que esta solución utiliza el parámetro 'Texto' para la getDatafunción, que no es estándar. Sin embargo, funciona en todos los navegadores al momento de escribir.


Solución n. ° 2 (HTML y funciona para Firefox <= 22)

Probado en IE6 +, FF 3.5+, Chrome, Safari, Edge

HTML

<div id='div' contenteditable='true'>Paste</div>

JavaScript

var editableDiv = document.getElementById('editableDiv');

function handlepaste (e) {
    var types, pastedData, savedContent;
    
    // Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
    if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
            
        // Check for 'text/html' in types list. See abligh's answer below for deatils on
        // why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as
        // Safari/Edge don't advertise HTML data even if it is available
        types = e.clipboardData.types;
        if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) {
        
            // Extract data and pass it to callback
            pastedData = e.clipboardData.getData('text/html');
            processPaste(editableDiv, pastedData);

            // Stop the data from actually being pasted
            e.stopPropagation();
            e.preventDefault();
            return false;
        }
    }
    
    // Everything else: Move existing element contents to a DocumentFragment for safekeeping
    savedContent = document.createDocumentFragment();
    while(editableDiv.childNodes.length > 0) {
        savedContent.appendChild(editableDiv.childNodes[0]);
    }
    
    // Then wait for browser to paste content into it and cleanup
    waitForPastedData(editableDiv, savedContent);
    return true;
}

function waitForPastedData (elem, savedContent) {

    // If data has been processes by browser, process it
    if (elem.childNodes && elem.childNodes.length > 0) {
    
        // Retrieve pasted content via innerHTML
        // (Alternatively loop through elem.childNodes or elem.getElementsByTagName here)
        var pastedData = elem.innerHTML;
        
        // Restore saved content
        elem.innerHTML = "";
        elem.appendChild(savedContent);
        
        // Call callback
        processPaste(elem, pastedData);
    }
    
    // Else wait 20ms and try again
    else {
        setTimeout(function () {
            waitForPastedData(elem, savedContent)
        }, 20);
    }
}

function processPaste (elem, pastedData) {
    // Do whatever with gathered data;
    alert(pastedData);
    elem.focus();
}

// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (editableDiv.addEventListener) {
    editableDiv.addEventListener('paste', handlepaste, false);
}
// IE <= 8
else {
    editableDiv.attachEvent('onpaste', handlepaste);
}

JSFiddle: https://jsfiddle.net/nicoburns/wrqmuabo/23/

Explicación

El onpasteevento del divtiene la handlePastefunción adjunta y pasó un único argumento: el eventobjeto para el evento de pegar. De particular interés para nosotros es la clipboardDatapropiedad de este evento que permite el acceso al portapapeles en navegadores que no sean. En IE, el equivalente es window.clipboardData, aunque tiene una API ligeramente diferente.

Vea la sección de recursos a continuación.


La handlepastefunción:

Esta función tiene dos ramas.

La primera verifica la existencia de event.clipboardDatay verifica si su typespropiedad contiene 'text / html' ( typespuede ser una DOMStringListque se verifica usando elcontains método o una cadena que se verifica con elindexOf método). Si se cumplen todas estas condiciones, entonces procedemos como en la solución # 1, excepto con 'text / html' en lugar de 'text / plain'. Actualmente funciona en Chrome y Firefox 22+.

Si este método no es compatible (todos los demás navegadores), entonces

  1. Guarde el contenido del elemento en un DocumentFragment
  2. Vaciar el elemento
  3. Llamar a la waitForPastedDatafunción

La waitforpastedatafunción:

Esta función primero sondea los datos pegados (una vez cada 20 ms), lo cual es necesario porque no aparece de inmediato. Cuando aparecieron los datos:

  1. Guarda el innerHTML del div editable (que ahora son los datos pegados) en una variable
  2. Restaura el contenido guardado en el DocumentFragment
  3. Llama a la función 'processPaste' con los datos recuperados

La processpastefunción:

Hace cosas arbitrarias con los datos pegados. En este caso, solo alertamos los datos, puede hacer lo que quiera. Probablemente desee ejecutar los datos pegados a través de algún tipo de proceso de desinfección de datos.


Guardar y restaurar la posición del cursor

En una situación real, es probable que desee guardar la selección antes y restaurarla después ( establezca la posición del cursor en contentEditable <div> ). Luego, podría insertar los datos pegados en la posición en la que se encontraba el cursor cuando el usuario inició la acción de pegar.

Recursos:

Gracias a Tim Down por sugerir el uso de un DocumentFragment, y sobretodo por detectar un error en Firefox debido al uso de DOMStringList en lugar de una cadena para clipboardData.types

Nico Burns
fuente
44
Interesante. Pensé que había intentado esto en el pasado y no había funcionado en algún navegador, pero estoy seguro de que tienes razón. Definitivamente, preferiría mover el contenido existente a un en DocumentFragmentlugar de usarlo innerHTMLpor varias razones: primero, mantiene los controladores de eventos existentes; segundo, guardar y restaurar innerHTMLno garantiza la creación de una copia idéntica del DOM anterior; En tercer lugar, puede guardar la selección como algo en Rangelugar de tener que cambiar el orden agregando elementos de marcador o calculando compensaciones de texto (que es lo que tendría que hacer si lo usara innerHTML).
Tim Down
3
De hecho, hay un destello sin contenido (FONC?), Que obviamente será peor si el procesamiento del contenido pegado lleva algún tiempo. Por cierto, ¿por qué es DocumentFragmentuna molestia extraer en IE? Es lo mismo que en otros navegadores, a menos que use un Range y extractContents()para hacerlo, lo que no es más conciso que la alternativa en cualquier caso. He implementado un ejemplo de su técnica, usando Rangy para mantener las cosas agradables y uniformes en los navegadores: jsfiddle.net/bQeWC/4 .
Tim Down
1
@ Martin: La demostración de jsFiddle que publiqué en los comentarios puede ayudar.
Tim Down
1
Parece que ya no funciona en Firefox 28 (al menos) para Windows. Nunca waitforpastedata
deja de
1
FYI: Edge ahora admite la lectura de datos con MIME-Type text/htmlutilizando la API del Portapapeles W3C. En el pasado, tal intento arrojaría una excepción. Por lo tanto, ya no se necesita esta solución / hack para Edge.
Jenny O'Reilly
130

Versión simple:

document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
    e.preventDefault();
    const text = (e.originalEvent || e).clipboardData.getData('text/plain');
    window.document.execCommand('insertText', false, text);
});

Utilizando clipboardData

Demostración: http://jsbin.com/nozifexasu/edit?js,output

Edge, Firefox, Chrome, Safari, Opera probado.

Document.execCommand () está obsoleto ahora.


Nota: Recuerde verificar la entrada / salida en el lado del servidor también (como las etiquetas de tira PHP )

l2aelba
fuente
44
Esto funciona muy bien, pero ninguna versión de IE permite el acceso a clipboardData desde el evento :( ¡Gran solución, sin embargo, esto debería ser más alto!
Eric Wood
1
Sin embargo, parece que podría acceder a los datos del portapapeles en IE de una manera diferente, por lo que si detecta IE, podría usar esos datos en lugar de la recuperación inmediata: msdn.microsoft.com/en-us/library/ie/ms535220(v = vs.85) .aspx
Andrew
44
mejor respuesta cruzada del navegador encontrada hasta ahora. simplemente agregue el código para IE y es perfecto.
Arturo
66
Esto funciona en IE (ah, dulce, IE contrario)window.clipboardData.getData('Text');
Benjineer
99
e.preventDefault(); if (e.clipboardData) { content = (e.originalEvent || e).clipboardData.getData('text/plain'); document.execCommand('insertText', false, content); } else if (window.clipboardData) { content = window.clipboardData.getData('Text'); document.selection.createRange().pasteHTML(content); }
Yukulelix
26

Demo en vivo

Probado en Chrome / FF / IE11

Hay una molestia de Chrome / IE que es que estos navegadores agregan <div>elementos para cada nueva línea. Hay un post sobre esto aquí y se puede fijar mediante el establecimiento de la contenteditable elemento a serdisplay:inline-block

Seleccione un HTML resaltado y péguelo aquí:

function onPaste(e){
  var content;
  e.preventDefault();

  if( e.clipboardData ){
    content = e.clipboardData.getData('text/plain');
    document.execCommand('insertText', false, content);
    return false;
  }
  else if( window.clipboardData ){
    content = window.clipboardData.getData('Text');
    if (window.getSelection)
      window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
  }
}


/////// EVENT BINDING /////////
document.querySelector('[contenteditable]').addEventListener('paste', onPaste);
[contenteditable]{ 
  /* chroem bug: https://stackoverflow.com/a/24689420/104380 */
  display:inline-block;
  width: calc(100% - 40px);
  min-height:120px; 
  margin:10px;
  padding:10px;
  border:1px dashed green;
}

/* 
 mark HTML inside the "contenteditable"  
 (Shouldn't be any OFC!)'
*/
[contenteditable] *{
  background-color:red;
}
<div contenteditable></div>

vsync
fuente
1
Necesitaba una función de pegar como texto plano. Probado en IE9 e IE10 y funciona muy bien. No hace falta mencionar que también funciona en los principales navegadores ... Gracias.
Savas Vedova
2
Su código contiene un error: si (e.originalEvent.clipboardData) puede causar un NPE ya que no sabe si e.originalEvent existe en ese momento
Sebastian
15

He escrito una pequeña prueba de concepto para la propuesta de Tim Downs aquí con el área de texto fuera de la pantalla. Y aquí va el código:

<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> 
<script language="JavaScript">
 $(document).ready(function()
{

var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;

$(document).keydown(function(e)
{
    if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
    if (e.keyCode == ctrlKey) ctrlDown = false;
});

$(".capture-paste").keydown(function(e)
{
    if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
        $("#area").css("display","block");
        $("#area").focus();         
    }
});

$(".capture-paste").keyup(function(e)
{
    if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){                      
        $("#area").blur();
        //do your sanitation check or whatever stuff here
        $("#paste-output").text($("#area").val());
        $("#area").val("");
        $("#area").css("display","none");
    }
});

});
</script>

</head>
<body class="capture-paste">

<div id="paste-output"></div>


    <div>
    <textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
    </div>

</body>
</html>

Simplemente copie y pegue todo el código en un archivo html e intente pegar (usando ctrl-v) texto del portapapeles en cualquier parte del documento.

Lo probé en IE9 y nuevas versiones de Firefox, Chrome y Opera. Funciona bastante bien También es bueno que uno pueda usar cualquier combinación de teclas que prefiera para activar esta funcionalidad. Por supuesto, no olvide incluir las fuentes jQuery.

Siéntase libre de usar este código y si tiene algunas mejoras o problemas, publíquelos nuevamente. También tenga en cuenta que no soy desarrollador de Javascript, por lo que es posible que me haya perdido algo (=> haga su propio testign).

JanM
fuente
Las Mac no pegan con ctrl-v, usan cmd-v. Así que configure ctrlKey = 91 en lugar de 17
Jeremy T
2
O tal vez no siempre sea 91: stackoverflow.com/questions/3834175/… Independientemente, estoy bastante seguro de que jQuery maneja todo eso por usted, solo revise e.ctrlKey o e.metaKey, creo.
Jeremy T
3
e.ctrlKey o e.metaKey es parte del JavaScript DOM, no jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne
2
No creo que esto funcione para hacer clic derecho y pegar. Mucha gente toma ese enfoque.
Eric Wood
10

Basado en l2aelba anwser. Esto fue probado en FF, Safari, Chrome, IE (8,9,10 y 11)

    $("#editText").on("paste", function (e) {
        e.preventDefault();

        var text;
        var clp = (e.originalEvent || e).clipboardData;
        if (clp === undefined || clp === null) {
            text = window.clipboardData.getData("text") || "";
            if (text !== "") {
                if (window.getSelection) {
                    var newNode = document.createElement("span");
                    newNode.innerHTML = text;
                    window.getSelection().getRangeAt(0).insertNode(newNode);
                } else {
                    document.selection.createRange().pasteHTML(text);
                }
            }
        } else {
            text = clp.getData('text/plain') || "";
            if (text !== "") {
                document.execCommand('insertText', false, text);
            }
        }
    });
tmorell
fuente
¿Hay alguna manera de preservar nuevas líneas al pegar en IE?
Staysee
10

Este no usa ningún setTimeout ().

He usado este excelente artículo para lograr compatibilidad con varios navegadores.

$(document).on("focus", "input[type=text],textarea", function (e) {
    var t = e.target;
    if (!$(t).data("EventListenerSet")) {
        //get length of field before paste
        var keyup = function () {
            $(this).data("lastLength", $(this).val().length);
        };
        $(t).data("lastLength", $(t).val().length);
        //catch paste event
        var paste = function () {
            $(this).data("paste", 1);//Opera 11.11+
        };
        //process modified data, if paste occured
        var func = function () {
            if ($(this).data("paste")) {
                alert(this.value.substr($(this).data("lastLength")));
                $(this).data("paste", 0);
                this.value = this.value.substr(0, $(this).data("lastLength"));
                $(t).data("lastLength", $(t).val().length);
            }
        };
        if (window.addEventListener) {
            t.addEventListener('keyup', keyup, false);
            t.addEventListener('paste', paste, false);
            t.addEventListener('input', func, false);
        }
        else {//IE
            t.attachEvent('onkeyup', function () {
                keyup.call(t);
            });
            t.attachEvent('onpaste', function () {
                paste.call(t);
            });
            t.attachEvent('onpropertychange', function () {
                func.call(t);
            });
        }
        $(t).data("EventListenerSet", 1);
    }
}); 

Este código se amplía con el controlador de selección antes de pegar: demo

AsgarAli
fuente
+1 Me gusta más que Nico Burns, aunque creo que cada uno tiene su propio lugar.
n0nag0n
5

Para limpiar el texto pegado y reemplazar el texto seleccionado actualmente con el texto pegado, el asunto es bastante trivial:

<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>

JS:

function handlepaste(el, e) {
  document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
  e.preventDefault();
}
Matt Crinklaw-Vogt
fuente
¿Puedes proporcionar una página de demostración donde esto funcione? Lo he probado y no funciona
vsync
5

Esto debería funcionar en todos los navegadores que admitan el evento onpaste y el observador de mutaciones.

Esta solución va más allá de obtener solo el texto, en realidad le permite editar el contenido pegado antes de pegarlo en un elemento.

Funciona mediante el uso de eventos onpaste contenteditable (compatible con todos los principales navegadores) en observadores de mutación (compatible con Chrome, Firefox e IE11 +)

paso 1

Crear un elemento HTML con contenteditable

<div contenteditable="true" id="target_paste_element"></div>

paso 2

En su código Javascript agregue el siguiente evento

document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);

Necesitamos vincular pasteCallBack, ya que el observador de mutaciones se llamará asincrónicamente.

paso 3

Agregue la siguiente función a su código

function pasteEventVerifierEditor(callback, e)
{
   //is fired on a paste event. 
    //pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
    //create temp div
    //save the caret position.
    savedCaret = saveSelection(document.getElementById("target_paste_element"));

    var tempDiv = document.createElement("div");
    tempDiv.id = "id_tempDiv_paste_editor";
    //tempDiv.style.display = "none";
    document.body.appendChild(tempDiv);
    tempDiv.contentEditable = "true";

    tempDiv.focus();

    //we have to wait for the change to occur.
    //attach a mutation observer
    if (window['MutationObserver'])
    {
        //this is new functionality
        //observer is present in firefox/chrome and IE11
        // select the target node
        // create an observer instance
        tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
        // configuration of the observer:
        var config = { attributes: false, childList: true, characterData: true, subtree: true };

        // pass in the target node, as well as the observer options
        tempDiv.observer.observe(tempDiv, config);

    }   

}



function pasteMutationObserver(callback)
{

    document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
    delete document.getElementById("id_tempDiv_paste_editor").observer;

    if (callback)
    {
        //return the copied dom tree to the supplied callback.
        //copy to avoid closures.
        callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
    }
    document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));

}

function pasteCallBack()
{
    //paste the content into the element.
    restoreSelection(document.getElementById("target_paste_element"), savedCaret);
    delete savedCaret;

    pasteHtmlAtCaret(this.innerHTML, false, true);
}   


saveSelection = function(containerEl) {
if (containerEl == document.activeElement)
{
    var range = window.getSelection().getRangeAt(0);
    var preSelectionRange = range.cloneRange();
    preSelectionRange.selectNodeContents(containerEl);
    preSelectionRange.setEnd(range.startContainer, range.startOffset);
    var start = preSelectionRange.toString().length;

    return {
        start: start,
        end: start + range.toString().length
    };
}
};

restoreSelection = function(containerEl, savedSel) {
    containerEl.focus();
    var charIndex = 0, range = document.createRange();
    range.setStart(containerEl, 0);
    range.collapse(true);
    var nodeStack = [containerEl], node, foundStart = false, stop = false;

    while (!stop && (node = nodeStack.pop())) {
        if (node.nodeType == 3) {
            var nextCharIndex = charIndex + node.length;
            if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                range.setStart(node, savedSel.start - charIndex);
                foundStart = true;
            }
            if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                range.setEnd(node, savedSel.end - charIndex);
                stop = true;
            }
            charIndex = nextCharIndex;
        } else {
            var i = node.childNodes.length;
            while (i--) {
                nodeStack.push(node.childNodes[i]);
            }
        }
    }

    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down

var sel, range;
if (window.getSelection) {
    // IE9 and non-IE
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
        range = sel.getRangeAt(0);
        range.deleteContents();

        // Range.createContextualFragment() would be useful here but is
        // only relatively recently standardized and is not supported in
        // some browsers (IE9, for one)
        var el = document.createElement("div");
        el.innerHTML = html;
        var frag = document.createDocumentFragment(), node, lastNode;
        while ( (node = el.firstChild) ) {
            lastNode = frag.appendChild(node);
        }
        var firstNode = frag.firstChild;
        range.insertNode(frag);

        // Preserve the selection
        if (lastNode) {
            range = range.cloneRange();
            if (returnInNode)
            {
                range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
            }
            else
            {
                range.setStartAfter(lastNode); 
            }
            if (selectPastedContent) {
                range.setStartBefore(firstNode);
            } else {
                range.collapse(true);
            }
            sel.removeAllRanges();
            sel.addRange(range);
        }
    }
} else if ( (sel = document.selection) && sel.type != "Control") {
    // IE < 9
    var originalRange = sel.createRange();
    originalRange.collapse(true);
    sel.createRange().pasteHTML(html);
    if (selectPastedContent) {
        range = sel.createRange();
        range.setEndPoint("StartToStart", originalRange);
        range.select();
    }
}
}

Lo que hace el código:

  1. Alguien dispara el evento pegar usando ctrl-v, contextmenu u otros medios
  2. En el caso de pegar, se crea un nuevo elemento con contenteditable (un elemento con contenteditable tiene privilegios elevados)
  3. Se guarda la posición de intercalación del elemento objetivo.
  4. El foco se establece en el nuevo elemento
  5. El contenido se pega en el nuevo elemento y se representa en el DOM.
  6. El observador de mutaciones capta esto (registra todos los cambios en el árbol de dom y el contenido). Luego dispara el evento de mutación.
  7. El dom del contenido pegado se clona en una variable y se devuelve a la devolución de llamada. El elemento temporal se destruye.
  8. La devolución de llamada recibe el DOM clonado. El caret está restaurado. Puede editar esto antes de agregarlo a su objetivo. elemento. En este ejemplo, estoy usando las funciones de Tim Downs para guardar / restaurar el cursor y pegar HTML en el elemento.

Ejemplo


Muchas gracias a Tim Down. Vea esta publicación para la respuesta:

Obtenga el contenido pegado en el documento en el evento pegar

Mouser
fuente
4

La solución que funciona para mí es agregar un detector de eventos para pegar el evento si está pegando en una entrada de texto. Dado que el evento pegar ocurre antes de que el texto en los cambios de entrada, dentro de mi controlador de pegar, creo una función diferida dentro de la cual verifico los cambios en mi cuadro de entrada que ocurrieron en pegar:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}
Lex
fuente
2
El horror, desafortunadamente, es parte de nuestra descripción de trabajo;) Pero estoy de acuerdo, este es un truco y los trucos deberían usarse SOLO cuando todas las demás opciones están agotadas.
Lex
4

Esto fue demasiado largo para un comentario sobre la respuesta de Nico, que creo que ya no funciona en Firefox (según los comentarios), y no funcionó para mí en Safari tal como está.

En primer lugar, ahora parece que puede leer directamente desde el portapapeles. En lugar de código como:

if (/text\/plain/.test(e.clipboardData.types)) {
    // shouldn't this be writing to elem.value for text/plain anyway?
    elem.innerHTML = e.clipboardData.getData('text/plain');
}

utilizar:

types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/plain")) ||
    (/text\/plain/.test(types))) {
    // shouldn't this be writing to elem.value for text/plain anyway?
    elem.innerHTML = e.clipboardData.getData('text/plain');
}

porque Firefox tiene un typescampo DOMStringListque no se implementatest .

Siguiente Firefox no permitirá pegar a menos que el foco esté en un contenteditable=true campo.

Finalmente, Firefox no permitirá pegar de manera confiable a menos que el foco esté en una textarea(o quizás entrada) que no solo sea contenteditable=truesino también:

  • no display:none
  • no visibility:hidden
  • no de tamaño cero

Estaba tratando de ocultar el campo de texto para poder pegar el trabajo sobre un emulador JS VNC (es decir, iba a un cliente remoto y en realidad no había textareaetc. para pegar). Descubrí que tratar de ocultar el campo de texto en el cuadro anterior daba síntomas en los que funcionaba a veces, pero generalmente fallaba en la segunda pegada (o cuando el campo se borró para evitar pegar los mismos datos dos veces) ya que el campo perdió el foco y no se recuperó correctamente a pesar de eso focus(). La solución que se me ocurrió fue ponerlo z-order: -1000, hacerlo display:none, hacerlo como 1px por 1px, y establecer todos los colores en transparentes. Yuck

En Safari, se aplica la segunda parte de lo anterior, es decir, necesita tener uno textareaque no lo es display:none.

abligh
fuente
Tal vez los desarrolladores que trabajan en motores de representación del navegador deberían tener una página o espacio en los sitios de documentación que puedan usar para escribir notas sobre las características en las que trabajan. Por ejemplo, si trabajaran en la función de pegar, agregarían: "Pegar no funcionará si la visualización es nula, la visibilidad está oculta o el tamaño es cero".
1.21 gigavatios
3

Solución simple:

document.onpaste = function(e) {
    var pasted = e.clipboardData.getData('Text');
    console.log(pasted)
}
lama12345
fuente
2

Esto funcionó para mí:

function onPasteMe(currentData, maxLen) {
    // validate max length of pasted text
    var totalCharacterCount = window.clipboardData.getData('Text').length;
}

<input type="text" onPaste="return onPasteMe(this, 50);" />
Timmy Duncan
fuente
2
function myFunct( e ){
    e.preventDefault();

    var pastedText = undefined;
    if( window.clipboardData && window.clipboardData.getData ){
    pastedText = window.clipboardData.getData('Text');
} 
else if( e.clipboardData && e.clipboardData.getData ){
    pastedText = e.clipboardData.getData('text/plain');
}

//work with text

}
document.onpaste = myFunct;
Ivan
fuente
1

Puedes hacer esto de esta manera:

use este complemento jQuery para eventos de pre y post pegado:

$.fn.pasteEvents = function( delay ) {
    if (delay == undefined) delay = 20;
    return $(this).each(function() {
        var $el = $(this);
        $el.on("paste", function() {
            $el.trigger("prepaste");
            setTimeout(function() { $el.trigger("postpaste"); }, delay);
        });
    });
};

Ahora puedes usar este plugin ;:

$('#txt').on("prepaste", function() { 

    $(this).find("*").each(function(){

        var tmp=new Date.getTime();
        $(this).data("uid",tmp);
    });


}).pasteEvents();

$('#txt').on("postpaste", function() { 


  $(this).find("*").each(function(){

     if(!$(this).data("uid")){
        $(this).removeClass();
          $(this).removeAttr("style id");
      }
    });
}).pasteEvents();

Explicación

Primero establezca un uid para todos los elementos existentes como atributo de datos.

Luego compare todos los nodos POST PASTE event. Entonces, al comparar, puede identificar el recién insertado porque tendrá un uid, luego simplemente elimine el atributo style / class / id de los elementos recién creados, para que pueda mantener su formato anterior.

Peeyush
fuente
1
$('#dom').on('paste',function (e){
    setTimeout(function(){
        console.log(e.currentTarget.value);
    },0);
});
Roman Yudin
fuente
1

Simplemente deje que el navegador pegue como de costumbre en su contenido editable div y luego, después de pegar, intercambie cualquier elemento span utilizado para estilos de texto personalizados con el texto mismo. Esto parece funcionar bien en Internet Explorer y en los otros navegadores que probé ...

$('[contenteditable]').on('paste', function (e) {
    setTimeout(function () {
        $(e.target).children('span').each(function () {
            $(this).replaceWith($(this).text());
        });
    }, 0);
});

Esta solución supone que está ejecutando jQuery y que no desea formatear el texto en ninguno de sus divs editables de contenido .

El lado positivo es que es súper simple.

DaveAlger
fuente
¿Por qué spanetiquetar? Me imagino que la pregunta era sobre todas las etiquetas.
Alexis Wilke
1

Esta solución es reemplazar la etiqueta html, es simple y de navegador cruzado; compruebe este jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , código central:

var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");

$plainText.on('paste', function (e) {
    window.setTimeout(function () {
        $plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
    }, 0);
});

$linkOnly.on('paste', function (e) {
    window.setTimeout(function () {
        $linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
    }, 0);
});

function replaceStyleAttr (str) {
    return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
        return b + 'style_replace' + d;
    });
}

function removeTagsExcludeA (str) {
    return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}

function removeAllTags (str) {
    return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}

aviso: debe hacer un trabajo sobre el filtro xss en la parte posterior porque esta solución no puede filtrar cadenas como '<< >>'

TomWan
fuente
El archivo XSS en el servidor no tiene nada que ver con si su filtro JavaScript hace un buen trabajo. Los hackers evitan el 100% de su filtrado JS de todos modos.
Alexis Wilke
¡Nunca use Regex para analizar / transformar HTML!
SubliemeSiem
0

Este es un código existente publicado anteriormente, pero lo he actualizado para IE, el error fue cuando se seleccionó el texto existente y pegado no eliminará el contenido seleccionado. Esto ha sido arreglado por el siguiente código

selRange.deleteContents(); 

Ver el código completo a continuación

$('[contenteditable]').on('paste', function (e) {
    e.preventDefault();

    if (window.clipboardData) {
        content = window.clipboardData.getData('Text');        
        if (window.getSelection) {
            var selObj = window.getSelection();
            var selRange = selObj.getRangeAt(0);
            selRange.deleteContents();                
            selRange.insertNode(document.createTextNode(content));
        }
    } else if (e.originalEvent.clipboardData) {
        content = (e.originalEvent || e).clipboardData.getData('text/plain');
        document.execCommand('insertText', false, content);
    }        
});
Ravi Selvaraj
fuente