JavaScript obtém dados da área de transferência no evento de colar (navegador cruzado)

299

Como um aplicativo Web detecta um evento de colagem e recupera os dados a serem colados?

Gostaria de remover o conteúdo HTML antes que o texto seja colado em um editor de rich text.

Limpar o texto depois de ser colado depois funciona, mas o problema é que toda a formatação anterior é perdida. Por exemplo, posso escrever uma frase no editor e torná-la em negrito, mas quando colo um novo texto, toda a formatação é perdida. Quero limpar apenas o texto colado e deixar intacta a formatação anterior.

Idealmente, a solução deve funcionar em todos os navegadores modernos (por exemplo, MSIE, Gecko, Chrome e Safari).

Observe que o MSIE possui clipboardData.getData(), mas não foi possível encontrar funcionalidades semelhantes para outros navegadores.

Alex
fonte
Todas essas respostas explicam como obter conteúdo de texto. Obter conteúdo de imagem ou arquivo requer muito mais trabalho. Talvez possamos mudar o título para "JavaScript, obter dados da área de transferência de texto higienizados ..."
1,21 gigawatts
1
como nico disse: event.clipboardData.getData('Text')trabalhou para mim.
Andre Elrico 6/08/19
document.addEventListener('paste'...funcionou para mim, mas causou conflitos se um usuário quisesse colar em outro lugar da página. Então eu tentei myCanvasElement.addEventListener('paste'..., mas isso não funcionou. Eventualmente, eu descobri que myCanvasElement.parentElement.addEventListener('paste'...funcionava.
Ryan

Respostas:

149

A situação mudou desde que escrevemos esta resposta: agora que o Firefox adicionou suporte na versão 22, todos os principais navegadores agora suportam o acesso aos dados da área de transferência em um evento de colagem. Veja a resposta de Nico Burns para um exemplo.

No passado, isso geralmente não era possível de uma maneira entre navegadores. O ideal seria conseguir o conteúdo colado por meio do pasteevento, o que é possível em navegadores recentes, mas não em navegadores antigos (em particular, Firefox <22).

Quando você precisa oferecer suporte a navegadores antigos, o que você pode fazer é bastante complicado e funciona um pouco, que funcionará nos navegadores Firefox 2+, IE 5.5+ e WebKit, como Safari ou Chrome. Versões recentes do TinyMCE e do CKEditor usam esta técnica:

  1. Detectar um evento ctrl-v / shift-ins usando um manipulador de eventos de pressionamento de tecla
  2. Nesse manipulador, salve a seleção atual do usuário, adicione um elemento de área de texto fora da tela (por exemplo, à esquerda -1000px) ao documento, desligue designModee chame focus()a área de texto, movendo o cursor e redirecionando a pasta
  3. Defina um cronômetro muito breve (digamos 1 milissegundo) no manipulador de eventos para chamar outra função que armazene o valor designModeda área de texto , remova a área de texto do documento, ligue novamente, restaure a seleção do usuário e cole o texto.

Observe que isso funcionará apenas para eventos de colagem do teclado e não para pastas do menu de contexto ou edição. Quando o evento de colagem é disparado, é tarde demais para redirecionar o cursor para a área de texto (em alguns navegadores, pelo menos).

No caso improvável de suporte ao Firefox 2, observe que você precisará colocar a área de texto no documento pai em vez do documento do iframe do editor WYSIWYG nesse navegador.

Tim Down
fonte
1
Uau, obrigado por isso! Parece ser um hack muito sofisticado ;-) Você poderia descrever um pouco mais esse designMode e a seleção, principalmente na etapa 3? Muito obrigado!
1028 Alex
5
Tive uma sensação horrível de que você perguntaria isso. Como já disse, está bastante envolvido: sugiro que procure a fonte do TinyMCE ou do CKEditor, pois não tenho tempo para descrever todos os problemas envolvidos. Resumidamente, porém, designModeé uma propriedade booleana documente torna a página inteira editável quando true. Os editores WYSIWYG geralmente usam um iframe com designModeon como o painel editável. O salvamento e a restauração da seleção do usuário são feitos de uma maneira no IE e de outros navegadores, assim como a colagem do conteúdo no editor. Você precisa obter um TextRangeno IE e um Rangeem outros navegadores.
Tim Baixo
6
@ Samuel: Você pode detectá-lo usando o pasteevento, mas geralmente é tarde demais para redirecionar a pasta para outro elemento, para que esse hack não funcione. O fallback na maioria dos editores é mostrar uma caixa de diálogo para o usuário colar.
Tim Baixo
6
Mais algumas informações sobre isso: o Firefox não permitirá que você mova o foco para outro elemento no pasteevento, no entanto, permitirá limpar o conteúdo do elemento (e salvá-lo em uma variável para que você possa restaurá-lo mais tarde). Se esse contêiner for um div(provavelmente também funciona para um iframe), você poderá percorrer o conteúdo colado usando métodos dom normais ou obtê-lo como uma string usando innerHTML. Você pode restaurar o conteúdo anterior do dive inserir o conteúdo que desejar. Ah, e você tem que usar o mesmo hack do temporizador acima. Eu estou surpreso que TinyMCE não faça isso ...
Nico Burns,
8
@ResistDesign: Eu discordo - é uma maneira deselegante e complicada de compensar a falta de uma API sensata. Seria melhor conseguir o conteúdo colado diretamente do evento de colagem, o que é possível de forma limitada em alguns navegadores .
Tim Baixo
318

Solução nº 1 (somente texto sem formatação e requer o Firefox 22+)

Funciona para IE6 +, FF 22+, Chrome, Safari, Edge (testado apenas no IE9 +, mas deve funcionar para versões inferiores)

Se você precisar de suporte para colar HTML ou Firefox <= 22, consulte a Solução 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/

Observe que esta solução usa o parâmetro 'Texto' para a getDatafunção, que não é padrão. No entanto, ele funciona em todos os navegadores no momento da escrita.


Solução 2 (HTML e funciona no Firefox <= 22)

Testado no 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/

Explicação

O onpasteevento do divpossui a handlePastefunção anexada e transmitiu um único argumento: o eventobjeto para o evento de colagem. De particular interesse para nós, é a clipboardDatapropriedade deste evento que permite o acesso à área de transferência em navegadores que não sejam. No IE, o equivalente é window.clipboardData, embora isso tenha uma API ligeiramente diferente.

Veja a seção de recursos abaixo.


A handlepastefunção:

Esta função tem dois ramos.

A primeira verifica a existência de event.clipboardDatae verifica se sua typespropriedade contém 'text / html' ( typespode ser um DOMStringListque é verificado usando o containsmétodo ou uma string que é verificada usando o indexOfmétodo). Se todas essas condições forem atendidas, procederemos como na solução 1, exceto com 'text / html' em vez de 'text / plain'. Atualmente, ele funciona no Chrome e Firefox 22+.

Se esse método não for suportado (todos os outros navegadores), então nós

  1. Salve o conteúdo do elemento em um DocumentFragment
  2. Esvaziar o elemento
  3. Chame a waitForPastedDatafunção

A waitforpastedatafunção:

Essa função pesquisa primeiro os dados colados (uma vez a cada 20ms), o que é necessário porque não aparece imediatamente. Quando os dados aparecerem:

  1. Salva o innerHTML da div editável (que agora são os dados colados) em uma variável
  2. Restaura o conteúdo salvo no DocumentFragment
  3. Chama a função 'processPaste' com os dados recuperados

A processpastefunção:

Faz coisas arbitrárias com os dados colados. Nesse caso, apenas alertamos os dados, você pode fazer o que quiser. Você provavelmente desejará executar os dados colados através de algum tipo de processo de limpeza de dados.


Salvando e restaurando a posição do cursor

Em uma situação real, você provavelmente desejaria salvar a seleção antes e restaurá-la depois ( Defina a posição do cursor em contentEditable <div> ). Você pode inserir os dados colados na posição em que o cursor estava quando o usuário iniciou a ação de colar.

Recursos:

Agradecemos a Tim Down por sugerir o uso de um DocumentFragment, e também por detectar um erro no Firefox devido ao uso de DOMStringList em vez de uma string para clipboardData.types

Nico Burns
fonte
4
Interessante. Eu pensei que tinha tentado isso no passado e não funcionou em algum navegador, mas tenho certeza que você está certo. Definitivamente, eu preferiria mover o conteúdo existente para um DocumentFragmentlugar em vez de usá-lo innerHTMLpor vários motivos: primeiro, você mantém os manipuladores de eventos existentes; segundo, salvar e restaurar innerHTMLnão é garantido para criar uma cópia idêntica do DOM anterior; terceiro, você pode salvar a seleção como uma Rangealternativa, em vez de precisar adicionar elementos de marcador ou calcular deslocamentos de texto (que é o que você faria se usasse innerHTML).
Tim Baixo
3
De fato, existe um flash sem conteúdo (FONC?), Que obviamente será pior se o processamento do conteúdo colado demorar algum tempo. Btw, por que está extraindo para DocumentFragmentuma dor no IE? É o mesmo que em outros navegadores, a menos que você use um intervalo e o extractContents()faça, o que não é mais conciso do que a alternativa em qualquer caso. Implementei um exemplo de sua técnica, usando o Rangy para manter as coisas agradáveis ​​e uniformes nos navegadores: jsfiddle.net/bQeWC/4 .
Tim Baixo
1
@ Martin: A demonstração do jsFiddle que publiquei nos comentários pode ajudar.
Tim Down
1
Parece que não funciona mais no Firefox 28 (pelo menos) para Windows. Ele nunca sai da waitforpastedatafunção
Oliboy50
1
FYI: O Edge agora suporta a leitura de dados com o tipo MIME text/htmlusando a API da área de transferência do W3C. No passado, essa tentativa lançaria uma exceção. Portanto, não é mais necessário esta solução alternativa / hack para o Edge.
Jenny O'Reilly
130

Versão simples:

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

Demonstração: http://jsbin.com/nozifexasu/edit?js,output

Teste de Edge, Firefox, Chrome, Safari, Opera.

Document.execCommand () está obsoleto agora.


Nota: Lembre-se de verificar a entrada / saída no lado do servidor também (como tags de faixa PHP )

l2aelba
fonte
4
Muito bem isso funciona, mas nenhuma versão do IE permite o acesso a clipboardData do evento :( grande solução, no entanto, isso deve ser maior!
Eric Wood
1
Parece que você pode acessar os dados da área de transferência no IE de uma maneira diferente; portanto, se você detectar o IE, poderá usá-los em vez do retorno imediato: msdn.microsoft.com/en-us/library/ie/ms535220(v = VS.85) .aspx
Andrew
4
melhor resposta entre navegadores encontrada até agora. basta adicionar o código para o IE e é perfeito.
Arturo
6
Isso funciona no IE (ah, doce, ao contrário IE)window.clipboardData.getData('Text');
Benjineer
9
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 31/07/2015
26

Demonstração ao vivo

Testado no Chrome / FF / IE11

Há um incômodo no Chrome / IE, que é o fato de esses navegadores adicionarem <div>elemento para cada nova linha. Há um post sobre isso aqui e ele pode ser corrigido definindo o elemento contenteditable comodisplay:inline-block

Selecione algum HTML destacado e cole-o aqui:

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
fonte
1
Eu precisava de uma pasta como recurso de texto simples. Testado no IE9 e IE10 e funciona muito bem. Escusado será mencionar que funciona nos principais navegadores também ... Obrigado.
Savas Vedova
2
Seu código contém um erro: if (e.originalEvent.clipboardData) pode causar um NPE, pois você não sabe se o e.originalEvent existe nesse momento.
Sebastian
15

Eu escrevi uma pequena prova de conceito para a proposta de Tim Downs aqui com área de texto fora da tela. E aqui vai o 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>

Apenas copie e cole o código inteiro em um arquivo html e tente colar (usando ctrl-v) o texto da área de transferência em qualquer lugar do documento.

Eu testei no IE9 e em novas versões do Firefox, Chrome e Opera. Funciona muito bem. Também é bom que se possa usar qualquer combinação de teclas que ele preferir para aprimorar essa funcionalidade. Claro que não se esqueça de incluir fontes jQuery.

Sinta-se à vontade para usar esse código e, se vier com algumas melhorias ou problemas, poste-os de volta. Observe também que eu não sou desenvolvedor de Javascript, por isso posso ter perdido alguma coisa (=> faça seu próprio testign).

JanM
fonte
Macs não colam com ctrl-v, eles usam cmd-v. Então defina ctrlKey = 91 em vez de 17
Jeremy T
2
Ou talvez nem sempre seja: stackoverflow.com/questions/3834175/… Independentemente disso, tenho certeza que o jQuery lida com tudo isso para você, basta verificar e.ctrlKey ou e.metaKey, eu acho.
Jeremy T
3
e.ctrlKey ou e.metaKey faz parte do JavaScript DOM, não jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne
2
Não acho que isso funcione para clicar e colar com o botão direito do mouse. Muitas pessoas adotam essa abordagem.
Eric Wood
10

Baseado no l2aelba anwser. Isso foi testado em FF, Safari, Chrome, IE (8,9,10 e 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
fonte
Existe uma maneira de preservar novas linhas ao colar no IE?
Staysee
10

Este não usa nenhum setTimeout ().

Eu usei este ótimo artigo para obter suporte entre 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 é estendido com o identificador de seleção antes de colar: demo

AsgarAli
fonte
+1 Gosto mais deste do que Nico Burns, apesar de achar que cada um tem seu próprio lugar.
N0nag0n
5

Para limpar o texto colado e substituir o texto atualmente selecionado pelo texto colado, o assunto é 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
fonte
Você pode fornecer uma página de demonstração onde isso funciona? Eu tentei e não funciona
vsync
5

Isso deve funcionar em todos os navegadores que suportam o evento onpaste e o observador de mutações.

Essa solução vai além da obtenção apenas do texto, na verdade permite editar o conteúdo colado antes de ser colado em um elemento.

Ele funciona usando evento onpaste, contenteditable (suportado por todos os principais navegadores) e observadores de mutação (suportados pelo Chrome, Firefox e IE11 +)

passo 1

Crie um elemento HTML com contenteditable

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

passo 2

No seu código Javascript, adicione o seguinte evento

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

Precisamos vincular pasteCallBack, já que o observador da mutação será chamado de forma assíncrona.

etapa 3

Adicione a seguinte função ao seu 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();
    }
}
}

O que o código faz:

  1. Alguém dispara o evento colar usando ctrl-v, contextmenu ou outros meios
  2. No evento de colar, um novo elemento com contenteditable é criado (um elemento com contenteditable possui privilégios elevados)
  3. A posição do cursor do elemento de destino é salva.
  4. O foco está definido para o novo elemento
  5. O conteúdo é colado no novo elemento e é renderizado no DOM.
  6. O observador da mutação captura isso (registra todas as alterações na árvore e no conteúdo do dom). Em seguida, dispara o evento de mutação.
  7. O domínio do conteúdo colado é clonado em uma variável e retornado ao retorno de chamada. O elemento temporário é destruído.
  8. O retorno de chamada recebe o DOM clonado. O sinal de intercalação é restaurado. Você pode editar isso antes de anexá-lo ao seu destino. elemento. Neste exemplo, estou usando as funções de Tim Downs para salvar / restaurar o cursor e colar HTML no elemento.

Exemplo


Muito obrigado a Tim Down. Veja este post pela resposta:

Obter o conteúdo colado no documento ao colar

Mouser
fonte
4

A solução que funciona para mim é adicionar um ouvinte de evento para colar um evento, se você estiver colando em uma entrada de texto. Como o evento paste acontece antes que o texto nas alterações de entrada, dentro do meu manipulador on paste, crio uma função adiada dentro da qual checo por alterações na minha caixa de entrada que ocorreram na pasta:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}
Lex
fonte
2
Infelizmente, o horror faz parte da descrição de nosso trabalho;) Mas eu concordo, este é um hack e os hacks devem ser usados ​​SOMENTE quando todas as outras opções estiverem esgotadas.
Lex
4

Isso foi muito longo para um comentário sobre a resposta de Nico, que eu acho que não funciona mais no Firefox (pelos comentários), e não funcionou para mim no Safari como está.

Primeiro, agora você parece ser capaz de ler diretamente da área de transferência. Em vez de codificar 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');
}

usar:

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 o Firefox tem um typescampo DOMStringListque não é implementado test.

O próximo Firefox não permitirá colar, a menos que o foco esteja em um contenteditable=truecampo.

Por fim, o Firefox não permitirá colar de maneira confiável , a menos que o foco esteja em uma textarea(ou talvez entrada) que não é apenas contenteditable=truemas também:

  • não display:none
  • não visibility:hidden
  • não tem tamanho zero

Eu estava tentando ocultar o campo de texto para que a colar funcionasse em um emulador JS VNC (ou seja, estava indo para um cliente remoto e não havia realmente textareaetc para colar). Descobri que tentar ocultar o campo de texto no exemplo acima apresentava sintomas onde, às vezes, funcionava, mas geralmente falhava na segunda pasta (ou quando o campo era limpo para evitar colar os mesmos dados duas vezes), pois o campo perdia o foco e não recuperava adequadamente apesar disso focus(). A solução que eu encontrei foi colocá-lo em z-order: -1000, torná-lo display:none, 1px por 1px e definir todas as cores para transparentes. Que nojo.

No Safari, você aplica a segunda parte do acima, ou seja, você precisa de um textareaque não é display:none.

abligh
fonte
Talvez os desenvolvedores que trabalham nos mecanismos de renderização do navegador devam ter uma página ou espaço nos sites de documentação que possam usar para escrever notas sobre os recursos em que trabalham. Por exemplo, se eles trabalhassem na função de colar, eles adicionariam "Colar não funcionará se a exibição for nenhuma, a visibilidade estiver oculta ou o tamanho for zero".
1,21 gigawatts
3

O primeiro que vem à mente é o manipulador de pastas da lib de fechamento do Google, http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html

tDo
fonte
este parece detectar com segurança um evento de colagem, mas parece não conseguir capturar / retornar o conteúdo colado?
Alex
@ Alex: você está correto, e isso também funciona apenas com áreas de texto, não com editores de rich text.
Tim Baixo
3

Solução simples:

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

Isso funcionou para mim:

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

Você pode fazer isso desta maneira:

use este plugin jQuery para eventos de pré e pós-colar:

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

Agora você pode 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();

Explicação

Primeiro, defina um uid para todos os elementos existentes como atributo de dados.

Em seguida, compare todos os nós POST PASTE event. Portanto, comparando, você pode identificar o recém-inserido porque eles terão um uid e, em seguida, basta remover o atributo style / class / id dos elementos recém-criados, para que você possa manter sua formatação mais antiga.

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

Apenas deixe o navegador colar, como de costume, em sua div editável de conteúdo e, depois da pasta, troque os elementos de extensão usados ​​para estilos de texto personalizados pelo próprio texto. Isso parece funcionar bem no Internet Explorer e nos outros navegadores que tentei ...

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

Esta solução pressupõe que você esteja executando o jQuery e que não deseja formatação de texto em nenhum dos seus divs editáveis ​​de conteúdo .

O lado positivo é que é super simples.

DaveAlger
fonte
Por que spanmarcar? Eu imaginaria que a pergunta era sobre todas as tags.
Alexis Wilke
1

Esta solução é substituir a tag html, é simples e em vários navegadores; verifique este jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , código principal:

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: você deve fazer algum trabalho sobre o filtro xss na parte de trás, porque esta solução não pode filtrar cadeias de caracteres como '<< >>'

TomWan
fonte
A filtragem XSS no servidor não tem nada a ver com o bom desempenho do filtro JavaScript. Os hackers ignoram 100% da sua filtragem JS de qualquer maneira.
Alexis Wilke
Nunca use Regex para analisar / transformar HTML!
SubliemeSiem
0

Este é um código existente publicado acima, mas eu o atualizei para o IE, o erro ocorreu quando o texto existente é selecionado e colado não exclui o conteúdo selecionado. Isso foi corrigido pelo código abaixo

selRange.deleteContents(); 

Veja o código completo abaixo

$('[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
fonte