JavaScript obtiene datos del portapapeles al pegar el evento (navegador cruzado)

Resuelto Alex asked hace 14 años • 23 respuestas

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

Me gustaría eliminar el contenido HTML antes de pegar el texto 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 texto nuevo, se pierde todo el formato. Quiero limpiar solo el texto pegado y dejar intacto el 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 avatar Feb 01 '10 20:02 Alex
Aceptado

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 n.° 2.

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);
<div id='editableDiv' contenteditable='true'>Paste</div>
Expandir fragmento

JSFiddle

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 este artículo.


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

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

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);
}
<div id='div' contenteditable='true'>Paste</div>
Expandir fragmento

JSFiddle

Explicación

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

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


La handlepastefunción:

Esta función tiene dos ramas.

El primero verifica la existencia event.clipboardDatay verifica si su typespropiedad contiene 'texto/html' ( typespuede ser una cadena DOMStringListque se verifica usando el containsmétodo o una cadena que se verifica usando el indexOfmétodo). Si se cumplen todas estas condiciones, procedemos como en la solución n.º 1, excepto que con 'texto/html' en lugar de 'texto/plain'. Esto funciona actualmente 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 unDocumentFragment
  2. Vaciar el elemento
  3. Llame 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 aparecen de inmediato. Cuando los datos hayan aparecido:

  1. Guarda el HTML interno 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, puedes hacer lo que quieras. Probablemente querrás 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, probablemente 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 estaba el cursor cuando el usuario inició la acción de pegar.

Recursos en MDN

  • pegar evento
  • Fragmento de documento
  • DomStringList

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

Nico Burns avatar Jul 24 '2011 03:07 Nico Burns

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

En el pasado, esto generalmente no era posible en varios navegadores. 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 más antiguos (en particular, Firefox < 22).

Cuando necesita admitir navegadores más antiguos, lo que puede hacer es bastante complicado y un pequeño truco que funcionará en 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 -1000px) al documento, apague designModey active focus()el área de texto, moviendo así el cursor y redirigiendo efectivamente el pegado.
  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, vuelva a activarlo designMode, restaure la selección del usuario y pegue el texto.

Tenga en cuenta que esto solo funcionará para eventos de pegado de teclado y no para pegados desde los menús contextuales o de edición. Cuando se activa el evento de pegado, ya es demasiado tarde para redirigir el cursor al área de texto (al menos en algunos navegadores).

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

Tim Down avatar Feb 01 '2010 13:02 Tim Down

Versión sencilla:

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

Usando clipboardData

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

Edge, Firefox, Chrome, Safari, Opera probados.

Document.execCommand() ya está obsoleto .


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

l2aelba avatar Oct 09 '2013 10:10 l2aelba

Demo en vivo

Probado en Chrome/FF/IE11

Hay una molestia de Chrome/IE y es que estos navegadores agregan <div>elementos para cada nueva línea. Hay una publicación sobre esto aquí y se puede solucionar configurando el elemento contenteditable comodisplay:inline-block

Seleccione algo de 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>
Expandir fragmento

vsync avatar Feb 15 '2015 17:02 vsync