JavaScript ottiene i dati degli appunti sull'evento incolla (Cross browser)


299

In che modo un'applicazione Web può rilevare un evento incolla e recuperare i dati da incollare?

Vorrei rimuovere il contenuto HTML prima che il testo venga incollato in un editor di testo avanzato.

La pulizia del testo dopo essere stata incollata successivamente funziona, ma il problema è che si perde tutta la formattazione precedente. Ad esempio, posso scrivere una frase nell'editor e renderla in grassetto, ma quando incollo un nuovo testo, tutta la formattazione viene persa. Voglio pulire solo il testo incollato e lasciare intatta qualsiasi formattazione precedente.

Idealmente, la soluzione dovrebbe funzionare su tutti i browser moderni (ad es. MSIE, Gecko, Chrome e Safari).

Si noti che MSIE ha clipboardData.getData(), ma non sono riuscito a trovare funzionalità simili per altri browser.


Tutte queste risposte spiegano come ottenere il contenuto del testo. Ottenere contenuti di immagini o file richiede molto più lavoro. Forse possiamo cambiare il titolo in "JavaScript ottiene i dati degli appunti di testo disinfettati ..."
1,21 gigawatt

1
come ha detto nico: ha event.clipboardData.getData('Text')lavorato per me.
Andre Elrico,

document.addEventListener('paste'...ha funzionato per me, ma ha causato conflitti se un utente voleva essere in grado di incollare altrove sulla pagina. Poi ho provato myCanvasElement.addEventListener('paste'..., ma non ha funzionato. Alla fine ho capito che myCanvasElement.parentElement.addEventListener('paste'...funzionava.
Ryan

Risposte:


149

La situazione è cambiata da quando ho scritto questa risposta: ora che Firefox ha aggiunto il supporto nella versione 22, tutti i principali browser ora supportano l'accesso ai dati degli appunti in un evento incolla. Vedi la risposta di Nico Burns per un esempio.

In passato ciò non era generalmente possibile in modo cross-browser. L'ideale sarebbe essere in grado di ottenere il contenuto incollato tramite l' pasteevento, che è possibile nei browser recenti ma non in alcuni browser meno recenti (in particolare Firefox <22).

Quando devi supportare i browser più vecchi, quello che puoi fare è abbastanza coinvolto e un po 'di un hack che funzionerà nei browser Firefox 2+, IE 5.5+ e WebKit come Safari o Chrome. Le versioni recenti di TinyMCE e CKEditor utilizzano questa tecnica:

  1. Rileva un evento ctrl-v / shift-ins utilizzando un gestore eventi di pressione dei tasti
  2. In quel gestore, salva la selezione dell'utente corrente, aggiungi un elemento textarea fuori dallo schermo (diciamo a sinistra -1000px) al documento, designModespegni e richiama focus()l'area di testo, spostando così il cursore e reindirizzando efficacemente la pasta
  3. Impostare un timer molto breve (diciamo 1 millisecondo) nel gestore eventi per chiamare un'altra funzione che memorizza il valore textarea, rimuove l'area text dal documento, si designModeriaccende, ripristina la selezione dell'utente e incolla il testo.

Nota che questo funzionerà solo per gli eventi di incolla della tastiera e non per le paste dal menu contestuale o di modifica. Al momento dell'evento incolla, è troppo tardi per reindirizzare il cursore nella textarea (almeno in alcuni browser).

Nel caso improbabile in cui sia necessario supportare Firefox 2, tenere presente che è necessario posizionare l'area di testo nel documento principale anziché il documento iframe dell'editor WYSIWYG in quel browser.


1
Wow, grazie per quello! Sembra essere un trucco molto sofisticato però ;-) Potresti per favore descrivere quel designMode e la cosa di selezione un po 'di più, specialmente nel passaggio 3? Molte grazie!
Alex

5
Ho avuto una sensazione orribile che lo avresti chiesto. Come ho già detto, è abbastanza coinvolto: suggerirei di guardare alla fonte di TinyMCE o CKEditor, poiché non ho il tempo di delineare tutti i problemi coinvolti. In breve, però, designModeè una proprietà booleana di documente rende modificabile l'intera pagina quando true. Gli editor WYSIWYG di solito usano un iframe con designModeon come riquadro modificabile. Il salvataggio e il ripristino della selezione utente viene effettuato in un modo in IE e in un altro in altri browser, così come incollare il contenuto nell'editor. È necessario ottenere un TextRangein IE e un Rangein altri browser.
Tim Down

6
@Samuel: puoi rilevarlo usando l' pasteevento, ma in genere è troppo tardi per reindirizzare la pasta in un altro elemento, quindi questo hack non funzionerà. Il fallback nella maggior parte degli editor è mostrare una finestra di dialogo in cui l'utente può incollare.
Tim Down,

6
Altre informazioni su questo: Firefox non ti consentirà di spostare lo stato attivo su un altro elemento pastenell'evento, tuttavia ti consentirà di cancellare il contenuto dell'elemento (e salvarlo in una variabile in modo da poterlo ripristinare in seguito). Se questo contenitore è un div(probabilmente funziona anche per un iframe), è quindi possibile scorrere il contenuto incollato utilizzando i normali metodi dom o ottenerlo come stringa utilizzando innerHTML. È quindi possibile ripristinare i contenuti precedenti di dive inserire qualsiasi contenuto che ti piace. Oh, e devi usare lo stesso hack timer di cui sopra. Sono sorpreso che TinyMCE non lo faccia ...
Nico Burns,

8
@ResistDesign: Non sono d'accordo - è un modo inelegante e complicato per compensare la mancanza di un'API ragionevole. Sarebbe meglio essere in grado di ottenere il contenuto incollato direttamente dall'evento incolla, che è possibile in modo limitato in alcuni browser .
Tim Down

318

Soluzione n. 1 (solo testo normale e richiede Firefox 22+)

Funziona con IE6 +, FF 22+, Chrome, Safari, Edge (testato solo su IE9 +, ma dovrebbe funzionare per le versioni precedenti)

Se hai bisogno di supporto per incollare HTML o Firefox <= 22, vedi Soluzione 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/

Questa soluzione utilizza il parametro 'Testo' per la getDatafunzione, che non è standard. Tuttavia, funziona in tutti i browser al momento della scrittura.


Soluzione n. 2 (HTML e funziona per Firefox <= 22)

Testato in 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/

Spiegazione

L' onpasteevento di divha la handlePastefunzione associata e ha passato un singolo argomento: l' eventoggetto per l'evento paste. Di particolare interesse per noi è la clipboardDataproprietà di questo evento che consente l'accesso agli Appunti nei browser non ie. In IE l'equivalente è window.clipboardData, sebbene abbia un'API leggermente diversa.

Vedi la sezione risorse di seguito.


La handlepastefunzione:

Questa funzione ha due rami.

Il primo controlla l'esistenza event.clipboardDatae controlla se la sua typesproprietà contiene 'text / html' ( typespuò essere o una DOMStringListche viene controllata usando il containsmetodo, o una stringa che viene controllata usando il indexOfmetodo). Se tutte queste condizioni sono soddisfatte, procediamo come nella soluzione n. 1, tranne con "text / html" anziché "text / plain". Questo attualmente funziona in Chrome e Firefox 22+.

Se questo metodo non è supportato (tutti gli altri browser), allora noi

  1. Salvare il contenuto dell'elemento in a DocumentFragment
  2. Svuota l'elemento
  3. Chiama la waitForPastedDatafunzione

La waitforpastedatafunzione:

Questa funzione esegue prima il polling dei dati incollati (una volta ogni 20 ms), il che è necessario perché non appare immediatamente. Quando i dati sono apparsi:

  1. Salva il HTML interno del div modificabile (che ora è i dati incollati) in una variabile
  2. Ripristina il contenuto salvato in DocumentFragment
  3. Chiama la funzione 'processPaste' con i dati recuperati

La processpastefunzione:

Fa cose arbitrarie con i dati incollati. In questo caso avvisiamo solo i dati, puoi fare quello che vuoi. Probabilmente vorrai eseguire i dati incollati attraverso una sorta di processo di sanificazione dei dati.


Salvataggio e ripristino della posizione del cursore

In una vera e propria seduta probabilmente vorrai salvare la selezione prima e ripristinarla in seguito ( Imposta la posizione del cursore su contentEditable <div> ). È quindi possibile inserire i dati incollati nella posizione in cui si trovava il cursore quando l'utente ha avviato l'azione incolla.

risorse:

Grazie a Tim Down per aver suggerito l'uso di DocumentFragment e per aver scoperto un errore in Firefox a causa dell'uso di DOMStringList invece di una stringa per appuntiData.types


4
Interessante. Pensavo di averlo provato in passato e non aveva funzionato in alcuni browser, ma sono sicuro che tu abbia ragione. Preferirei sicuramente spostare il contenuto esistente in aDocumentFragment piuttosto che utilizzarlo innerHTMLper diversi motivi: in primo luogo, mantieni tutti i gestori di eventi esistenti; in secondo luogo, innerHTMLnon è garantito il salvataggio e il ripristino di creare una copia identica del precedente DOM; terzo, puoi quindi salvare la selezione come Rangepiuttosto che doverti preoccupare di aggiungere elementi marcatore o calcolare gli offset del testo (che è quello che avresti dovuto fare se lo avessi usato innerHTML).
Tim Down,

3
C'è davvero un lampo di nessun contenuto (FONC?), Che ovviamente peggiorerà se l'elaborazione del contenuto incollato richiede del tempo. A proposito, perché l'estrazione di DocumentFragmentun dolore in IE? È lo stesso di altri browser, a meno che tu non utilizzi un intervallo e extractContents()lo faccia, il che non è in ogni caso più conciso dell'alternativa. Ho implementato un esempio della tua tecnica, usando Rangy per mantenere le cose belle e uniformi su tutti i browser: jsfiddle.net/bQeWC/4 .
Tim Down,

1
@Martin: la demo di jsFiddle che ho pubblicato nei commenti può essere d'aiuto.
Tim Down

1
Sembra che non funzioni più su Firefox 28 (almeno) per Windows. Non esce mai dalla waitforpastedatafunzione
Oliboy50,

1
Cordiali saluti: Edge ora supporta la lettura dei dati con tipo MIME text/htmlutilizzando l'API degli Appunti W3C. In passato un simile tentativo avrebbe generato un'eccezione. Quindi uno non ha più bisogno di questa soluzione alternativa / hack per Edge.
Jenny O'Reilly,

130

Versione semplice:

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

utilizzando clipboardData

Demo: http://jsbin.com/nozifexasu/edit?js,output

Edge, Firefox, Chrome, Safari, Opera testati.

Document.execCommand () è obsoleto ora.


Nota: ricordarsi di controllare anche l'input / output sul lato server (come i tag strip PHP )


4
Funziona davvero bene, ma nessuna versione di IE consente l'accesso agli AppuntiDati dall'evento :( Ottima soluzione, però, dovrebbe essere più alta!
Eric Wood,

1
Sembra che potresti ottenere i dati degli Appunti in IE in un modo diverso, quindi se rilevi IE potresti usare quei dati invece del prompt fallback: msdn.microsoft.com/en-us/library/ie/ms535220(v = vs.85) .aspx
Andrew,

4
la migliore risposta cross browser trovata finora. basta aggiungere il codice per IE ed è perfetto.
Arturo,

6
Funziona in IE (ah, dolce, al contrario 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,

26

Dimostrazione dal vivo

Testato su Chrome / FF / IE11

C'è un problema di Chrome / IE che è che questi browser aggiungono <div>elemento per ogni nuova linea. C'è un post su questo qui e può essere risolto impostando l' elemento contenteditable sudisplay:inline-block

Seleziona alcuni HTML evidenziati e incollali qui:

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>


1
Avevo bisogno di un incolla come funzione di testo semplice. Testato su IE9 e IE10 e funziona alla grande. Inutile dire che funziona anche sui principali browser ... Grazie.
Savas Vedova,

2
Il tuo codice contiene un bug: if (e.originalEvent.clipboardData) può causare un NPE in quanto non sai se e.originalEvent esiste a quel punto
Sebastian

15

Ho scritto una piccola dimostrazione del concetto per la proposta di Tim Downs qui con textarea fuori schermo. E qui va il codice:

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

Basta copiare e incollare l'intero codice in un file html e provare a incollare (usando ctrl-v) il testo dagli appunti in qualsiasi punto del documento.

L'ho testato in IE9 e nuove versioni di Firefox, Chrome e Opera. Funziona abbastanza bene. Inoltre è bene che si possa usare qualsiasi combinazione di tasti che preferisce trigerare questa funzionalità. Ovviamente non dimenticare di includere le fonti jQuery.

Sentiti libero di usare questo codice e se ti imbatti in alcuni miglioramenti o problemi ti preghiamo di inviarli di nuovo. Nota anche che non sono uno sviluppatore Javascript, quindi potrei aver perso qualcosa (=> fai il tuo testign).


I Mac non incollano con ctrl-v, usano cmd-v. Quindi imposta ctrlKey = 91 invece di 17
Jeremy T

2
O forse non è sempre 91: stackoverflow.com/questions/3834175/… Indipendentemente da ciò, sono abbastanza sicuro che jQuery gestisca tutto ciò per te, basta controllare e.ctrlKey o e.metaKey penso.
Jeremy T,

3
e.ctrlKey o e.metaKey fa parte del DOM JavaScript, non di jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne,

2
Non penso che questo funzioni per fare clic con il tasto destro e incollare. Molte persone adottano questo approccio.
Eric Wood,

10

Basato su l2aelba anwser. Questo è stato testato su 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);
            }
        }
    });

C'è un modo per preservare nuove linee quando si incolla su IE?
Soggiorno

10

Questo non usa alcun setTimeout ().

Ho usato questo fantastico articolo per ottenere il supporto cross-browser.

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

Questo codice viene esteso con la maniglia di selezione prima di incolla: demo


+1 Mi piace questo meglio di Nico Burns, anche se penso che ognuno abbia il proprio posto.
n0nag0n

5

Per pulire il testo incollato e sostituire il testo attualmente selezionato con il testo incollato, la questione è piuttosto banale:

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

Potete fornire una pagina dimostrativa in cui funziona? L'ho provato e non funziona
vsync il

5

Questo dovrebbe funzionare su tutti i browser che supportano l'evento onpaste e l'osservatore delle mutazioni.

Questa soluzione va oltre il semplice recupero del testo, in realtà ti consente di modificare il contenuto incollato prima di incollarlo in un elemento.

Funziona utilizzando eventi onpaste contenteditable (supportati da tutti i principali browser) e osservatori di mutazioni (supportati da Chrome, Firefox e IE11 +)

passo 1

Crea un elemento HTML con contenteditable

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

passo 2

Nel tuo codice Javascript aggiungi il seguente evento

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

Dobbiamo associare pasteCallBack, poiché l'osservatore della mutazione verrà chiamato in modo asincrono.

passaggio 3

Aggiungi la seguente funzione al tuo codice

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

Cosa fa il codice:

  1. Qualcuno genera l'evento incolla usando ctrl-v, contextmenu o altri mezzi
  2. Nel caso incolla viene creato un nuovo elemento con contenteditable (un elemento con contenteditable ha privilegi elevati)
  3. La posizione del cursore dell'elemento target viene salvata.
  4. L'attenzione è impostata sul nuovo elemento
  5. Il contenuto viene incollato nel nuovo elemento e viene renderizzato nel DOM.
  6. L'osservatore delle mutazioni rileva ciò (registra tutte le modifiche all'albero e al contenuto del dom). Quindi genera l'evento di mutazione.
  7. La dom del contenuto incollato viene clonata in una variabile e restituita al callback. L'elemento temporaneo viene distrutto.
  8. Il callback riceve il DOM clonato. Il cursore viene ripristinato. Puoi modificarlo prima di aggiungerlo al tuo target. elemento. In questo esempio sto usando le funzioni di Tim Downs per salvare / ripristinare il cursore e incollare HTML nell'elemento.

Esempio


Mille grazie a Tim Down Vedi questo post per la risposta:

Ottieni il contenuto incollato sul documento all'evento incolla


4

La soluzione che funziona per me è l'aggiunta di listener di eventi per incollare l'evento se si sta incollando un input di testo. Poiché l'evento incolla si verifica prima che il testo cambi in input, all'interno del mio gestore on paste creo una funzione differita all'interno della quale controllo le modifiche nella mia casella di input che sono avvenute su paste:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}

2
L'horror, purtroppo, fa parte della nostra descrizione del lavoro;) Ma sono d'accordo, questo è un trucco e gli hack dovrebbero essere usati SOLO quando tutte le altre opzioni sono esaurite.
Lex,

4

Questo è stato troppo lungo per un commento sulla risposta di Nico, che non credo funzioni più su Firefox (secondo i commenti), e non ha funzionato per me su Safari così com'è.

In primo luogo, ora sembra che tu sia in grado di leggere direttamente dagli appunti. Invece di codice come:

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

uso:

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

perché Firefox ha un typescampo DOMStringListche non è implementabile test.

Il prossimo Firefox non consentirà di incollare se lo stato attivo non è in un contenteditable=truecampo.

Infine, Firefox non consentirà di incollare in modo affidabile a meno che il focus non sia su un textarea(o forse input) che non è solo contenteditable=truema anche:

  • non display:none
  • non visibility:hidden
  • non di dimensioni zero

Stavo cercando di nascondere il campo di testo in modo da poter far funzionare la pasta su un emulatore JS VNC (cioè stava andando su un client remoto e in realtà non c'era textareaecc. In cui incollare). Ho scoperto che il tentativo di nascondere il campo di testo sopra ha dato sintomi in cui ha funzionato a volte, ma in genere non è riuscito sulla seconda incolla (o quando il campo è stato cancellato per evitare di incollare gli stessi dati due volte) poiché il campo ha perso lo stato attivo e non si sarebbe ripreso correttamente nonostante focus(). La soluzione che mi è venuta in mente è stata quella di metterlo z-order: -1000, realizzarlodisplay:none , renderlo 1px per 1px e impostare tutti i colori su trasparente. Che schifo.

Su Safari, si applica la seconda parte di quanto sopra, vale a dire che è necessario disporre di un textareache non lo è display:none.


Forse gli sviluppatori che lavorano sui motori di rendering del browser dovrebbero avere una pagina o uno spazio su siti di documentazione che possono usare per scrivere nelle note sulle funzionalità su cui lavorano. Ad esempio, se avessero lavorato sulla funzione incolla avrebbero aggiunto: "Incolla non funzionerà se la visualizzazione è nessuna, la visibilità è nascosta o la dimensione è zero".
1,21 gigawatt


3

Soluzione semplice:

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

2

Questo ha funzionato per me:

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

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;

1

Puoi farlo in questo modo:

usa questo plugin jQuery per eventi pre e post incolla:

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

Ora puoi usare questo 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();

spiegazione

Innanzitutto imposta un uid per tutti gli elementi esistenti come attributo dati.

Quindi confrontare tutti i nodi POST PASTE event. Quindi confrontando puoi identificare quello appena inserito perché avranno un uid, quindi rimuovi l'attributo style / class / id dagli elementi appena creati, in modo da poter mantenere la formattazione precedente.


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

1

Lascia che il browser incolli come al solito nel suo contenuto modificabile div e quindi dopo l'incolla scambia tutti gli elementi di span utilizzati per stili di testo personalizzati con il testo stesso. Questo sembra funzionare bene in Internet Explorer e negli altri browser che ho provato ...

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

Questa soluzione presuppone che tu stia eseguendo jQuery e che non desideri la formattazione del testo in nessuno dei div modificabili del tuo contenuto .

Il lato positivo è che è super semplice.


Perché spantag? Immagino che la domanda riguardasse tutti i tag.
Alexis Wilke,

1

Questa soluzione sostituisce il tag html, è semplice e cross-browser; controlla questo jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , codice core:

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

nota: dovresti fare un po 'di lavoro sul filtro xss sul retro perché questa soluzione non può filtrare stringhe come' << >> '


Il filtraggio XSS sul server non ha nulla a che fare con il fatto che il tuo filtro JavaScript funzioni bene. Gli hacker ignorano comunque il 100% del tuo filtro JS.
Alexis Wilke,

Non usare mai Regex per analizzare / trasformare HTML!
SubliemeSiem

0

Questo è un codice esistente pubblicato sopra ma l'ho aggiornato per IE, il bug era quando il testo esistente è selezionato e incollato non eliminerà il contenuto selezionato. Questo è stato risolto dal seguente codice

selRange.deleteContents(); 

Vedi il codice completo di seguito

$('[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);
    }        
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.