JavaScript ruft Zwischenablagedaten beim Einfügen ab (Cross-Browser)


299

Wie kann eine Webanwendung ein Einfügeereignis erkennen und die einzufügenden Daten abrufen?

Ich möchte HTML-Inhalte entfernen, bevor der Text in einen Rich-Text-Editor eingefügt wird.

Das Bereinigen des Texts nach dem Einfügen funktioniert, aber das Problem ist, dass alle vorherigen Formatierungen verloren gehen. Zum Beispiel kann ich einen Satz in den Editor schreiben und fett machen, aber wenn ich neuen Text einfüge, geht die gesamte Formatierung verloren. Ich möchte nur den eingefügten Text bereinigen und alle vorherigen Formatierungen unberührt lassen.

Idealerweise sollte die Lösung in allen modernen Browsern (z. B. MSIE, Gecko, Chrome und Safari) funktionieren.

Beachten Sie, dass MSIE hat clipboardData.getData(), aber ich konnte keine ähnliche Funktionalität für andere Browser finden.


Alle diese Antworten erklären, wie Sie Textinhalte erhalten. Das Abrufen von Bildinhalten oder Dateiinhalten erfordert viel mehr Arbeit. Vielleicht können wir den Titel in "JavaScript erhalten bereinigte Text Zwischenablage Daten ..."
1,21 Gigawatt

1
wie nico sagte: event.clipboardData.getData('Text')hat für mich gearbeitet.
Andre Elrico

document.addEventListener('paste'...hat für mich funktioniert, aber Konflikte verursacht, wenn ein Benutzer in der Lage sein wollte, an anderer Stelle auf der Seite einzufügen. Dann habe ich es versucht myCanvasElement.addEventListener('paste'..., aber das hat nicht funktioniert. Schließlich fand ich heraus, dass es myCanvasElement.parentElement.addEventListener('paste'...funktioniert.
Ryan

Antworten:


149

Die Situation hat sich seit dem Schreiben dieser Antwort geändert: Nachdem Firefox in Version 22 Unterstützung hinzugefügt hat, unterstützen alle gängigen Browser den Zugriff auf die Zwischenablagedaten in einem Einfügeereignis. Siehe Nico Burns Antwort für ein Beispiel.

In der Vergangenheit war dies im Allgemeinen nicht browserübergreifend möglich. Ideal wäre es, den eingefügten Inhalt über das pasteEreignis abrufen zu können. Dies ist in neueren Browsern möglich , in einigen älteren Browsern jedoch nicht (insbesondere in Firefox <22).

Wenn Sie ältere Browser unterstützen müssen, ist das, was Sie tun können, ziemlich kompliziert und ein bisschen wie ein Hack, der in Firefox 2+ -, IE 5.5+ - und WebKit-Browsern wie Safari oder Chrome funktioniert. Neuere Versionen von TinyMCE und CKEditor verwenden diese Technik:

  1. Erkennen Sie ein Strg-V / Shift-Ins-Ereignis mithilfe eines Tastendruck-Ereignishandlers
  2. Speichern Sie in diesem Handler die aktuelle Benutzerauswahl, fügen Sie dem Dokument ein Textbereichselement außerhalb des Bildschirms hinzu (z. B. links -1000 Pixel), deaktivieren Sie den Textbereich designModeund rufen Sie ihn focus()auf. Dadurch wird das Caret verschoben und die Paste effektiv umgeleitet
  3. Stellen Sie im Ereignishandler einen sehr kurzen Timer (z. B. 1 Millisekunde) ein, um eine andere Funktion aufzurufen, die den Textbereichswert speichert, den Textbereich aus dem Dokument entfernt, wieder einschaltet designMode, die Benutzerauswahl wiederherstellt und den Text einfügt.

Beachten Sie, dass dies nur für Tastatureinfügeereignisse funktioniert und nicht für Einfügungen aus dem Kontext oder zum Bearbeiten von Menüs. Zum Zeitpunkt des Auslösens des Einfügeereignisses ist es zu spät, das Caret in den Textbereich umzuleiten (zumindest in einigen Browsern).

Beachten Sie für den unwahrscheinlichen Fall, dass Sie Firefox 2 unterstützen müssen, dass Sie den Textbereich im übergeordneten Dokument und nicht im Dokument des WYSIWYG-Editors iframe in diesem Browser platzieren müssen.


1
Wow, danke dafür! Scheint allerdings ein sehr ausgefeilter Hack zu sein ;-) Könnten Sie bitte diesen DesignMode und die Auswahlsache etwas genauer beschreiben, insbesondere in Schritt 3? Vielen Dank!
Alex

5
Ich hatte das schreckliche Gefühl, dass du das fragen würdest. Wie gesagt, es ist ziemlich kompliziert: Ich würde vorschlagen, die Quelle von TinyMCE oder CKEditor zu betrachten, da ich nicht die Zeit habe, alle damit verbundenen Probleme zu skizzieren. Kurz gesagt, designModeist eine boolesche Eigenschaft von documentund macht die gesamte Seite bearbeitbar, wenn true. WYSIWYG-Editoren verwenden normalerweise einen Iframe mit designModeon als bearbeitbaren Bereich. Das Speichern und Wiederherstellen der Benutzerauswahl erfolgt auf eine Weise im IE und auf andere Weise in anderen Browsern, ebenso wie das Einfügen des Inhalts in den Editor. Sie müssen eine TextRangein IE und eine Rangein anderen Browsern erhalten.
Tim Down

6
@Samuel: Sie können es anhand des pasteEreignisses erkennen, aber bis dahin ist es im Allgemeinen zu spät, um die Paste in ein anderes Element umzuleiten, sodass dieser Hack nicht funktioniert. Der Fallback in den meisten Editoren besteht darin, ein Dialogfeld anzuzeigen, in das der Benutzer einfügen kann.
Tim Down

6
Weitere Informationen hierzu: In Firefox können Sie den Fokus im pasteEreignis nicht auf ein anderes Element verschieben. Sie können jedoch den Inhalt des Elements löschen (und in einer Variablen speichern, damit Sie es später wiederherstellen können). Wenn es sich bei diesem Container um einen handelt div(wahrscheinlich funktioniert er auch für einen iframe), können Sie den eingefügten Inhalt mit normalen dom-Methoden durchlaufen oder ihn als Zeichenfolge abrufen innerHTML. Sie können dann den vorherigen Inhalt von wiederherstellen divund den gewünschten Inhalt einfügen. Oh, und Sie müssen den gleichen Timer-Hack wie oben verwenden. Ich bin überrascht, dass TinyMCE dies nicht tut ...
Nico Burns

8
@ResistDesign: Ich bin anderer Meinung - es ist eine unelegante und komplizierte Möglichkeit, das Fehlen einer vernünftigen API auszugleichen. Es ist besser, den eingefügten Inhalt direkt aus dem Einfügeereignis abrufen zu können, was in einigen Browsern nur eingeschränkt möglich ist .
Tim Down

318

Lösung Nr. 1 (Nur einfacher Text und erfordert Firefox 22+)

Funktioniert für IE6 +, FF 22+, Chrome, Safari, Edge (Nur in IE9 + getestet, sollte aber für niedrigere Versionen funktionieren)

Wenn Sie Unterstützung zum Einfügen von HTML oder Firefox <= 22 benötigen, lesen Sie Lösung 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/

Beachten Sie, dass diese Lösung den Parameter 'Text' für die getDataFunktion verwendet, der nicht dem Standard entspricht. Es funktioniert jedoch zum Zeitpunkt des Schreibens in allen Browsern.


Lösung 2 (HTML und funktioniert für Firefox <= 22)

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

Erläuterung

Dem onpasteEreignis von divist die handlePasteFunktion zugeordnet, und es wurde ein einziges Argument übergeben: das eventObjekt für das Einfügeereignis. Von besonderem Interesse für uns ist die clipboardDataEigenschaft dieses Ereignisses, die den Zugriff auf die Zwischenablage in nicht-dh Browsern ermöglicht. Im IE ist das Äquivalent window.clipboardData, obwohl dies eine etwas andere API hat.

Siehe Abschnitt Ressourcen unten.


Die handlepasteFunktion:

Diese Funktion hat zwei Zweige.

Der erste prüft auf das Vorhandensein von event.clipboardDataund prüft, ob seine typesEigenschaft 'text / html' enthält ( typesentweder eine, DOMStringListdie mit der containsMethode überprüft wird, oder eine Zeichenfolge, die mit der indexOfMethode überprüft wird). Wenn alle diese Bedingungen erfüllt sind, fahren wir wie in Lösung 1 fort, außer mit 'text / html' anstelle von 'text / plain'. Dies funktioniert derzeit in Chrome und Firefox 22+.

Wenn diese Methode nicht unterstützt wird (alle anderen Browser), dann wir

  1. Speichern Sie den Inhalt des Elements in a DocumentFragment
  2. Leeren Sie das Element
  3. Rufen Sie die waitForPastedDataFunktion auf

Die waitforpastedataFunktion:

Diese Funktion fragt zuerst nach den eingefügten Daten (einmal pro 20 ms), was erforderlich ist, da sie nicht sofort angezeigt werden. Wenn die Daten erschienen sind:

  1. Speichert das innerHTML des bearbeitbaren div (das jetzt die eingefügten Daten sind) in einer Variablen
  2. Stellt den im DocumentFragment gespeicherten Inhalt wieder her
  3. Ruft die Funktion 'processPaste' mit den abgerufenen Daten auf

Die processpasteFunktion:

Macht willkürliche Dinge mit den eingefügten Daten. In diesem Fall alarmieren wir nur die Daten, Sie können tun, was Sie wollen. Möglicherweise möchten Sie die eingefügten Daten durch einen Datenbereinigungsprozess ausführen.


Speichern und Wiederherstellen der Cursorposition

In einer realen Situation möchten Sie wahrscheinlich die Auswahl vorher speichern und danach wiederherstellen ( Cursorposition auf contentEditable <div> setzen ). Sie können die eingefügten Daten dann an der Position einfügen, an der sich der Cursor befand, als der Benutzer die Einfügemaßnahme initiierte.

Ressourcen:

Vielen Dank an Tim Down für den Vorschlag, ein DocumentFragment zu verwenden, und für das Abfangen eines Fehlers in Firefox aufgrund der Verwendung von DOMStringList anstelle einer Zeichenfolge für clipboardData.types


4
Interessant. Ich dachte, ich hätte es in der Vergangenheit versucht und es hatte in keinem Browser funktioniert, aber ich bin mir sicher, dass Sie Recht haben. Ich würde es definitiv vorziehen, den vorhandenen Inhalt in einen zu verschieben, DocumentFragmentanstatt ihn innerHTMLaus mehreren Gründen zu verwenden: Erstens behalten Sie alle vorhandenen Ereignishandler bei; Zweitens wird beim Speichern und Wiederherstellen innerHTMLnicht garantiert, dass eine identische Kopie des vorherigen DOM erstellt wird. RangeDrittens können Sie die Auswahl dann als speichern , anstatt sich mit dem Hinzufügen von Markierungselementen oder dem Berechnen von Textversätzen herumschlagen zu müssen (was Sie tun müssten, wenn Sie es verwenden würden innerHTML).
Tim Down

3
Es gibt tatsächlich einen Flash ohne Inhalt (FONC?), Was offensichtlich schlimmer ist, wenn die Verarbeitung des eingefügten Inhalts einige Zeit in Anspruch nimmt. Übrigens, warum ist das Extrahieren zu DocumentFragmenteinem Schmerz im IE? Es ist dasselbe wie in anderen Browsern, es sei denn, Sie verwenden einen Bereich und extractContents()tun dies, was auf keinen Fall prägnanter als die Alternative ist. Ich habe ein Beispiel für Ihre Technik implementiert und Rangy verwendet, um die Dinge in allen Browsern schön und einheitlich zu halten: jsfiddle.net/bQeWC/4 .
Tim Down

1
@ Martin: Die jsFiddle-Demo, die ich in den Kommentaren gepostet habe, kann helfen.
Tim Down

1
Es scheint, dass es unter Firefox 28 (zumindest) für Windows nicht mehr funktioniert. Es geht nie aus der waitforpastedataFunktion
Oliboy50

1
Zu Ihrer Information: Edge unterstützt jetzt das Lesen von Daten mit MIME-Type text/htmlmithilfe der W3C-Zwischenablage-API. In der Vergangenheit würde ein solcher Versuch eine Ausnahme auslösen. Man braucht diese Problemumgehung / diesen Hack für Edge also nicht mehr.
Jenny O'Reilly

130

Einfache Version:

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

Verwenden von clipboardData

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

Edge, Firefox, Chrome, Safari, Opera getestet.

Document.execCommand () ist jetzt veraltet .


Hinweis: Denken Sie daran, die Eingabe / Ausgabe auch serverseitig zu überprüfen (wie PHP-Strip-Tags ).


4
Dies funktioniert wirklich gut, aber keine Version von IE erlaubt den Zugriff auf clipboardData von der Veranstaltung :( Tolle Lösung, dies sollte jedoch höher sein!
Eric Wood

1
Es sieht so aus, als könnten Sie die Daten der Zwischenablage im IE auf andere Weise abrufen.
Andrew

4
beste browserübergreifende Antwort bisher gefunden. Fügen Sie einfach den Code für IE und seine perfekte.
Arturo

6
Dies funktioniert in IE (ah, süß, im Gegensatz zu 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

Live-Demo

Getestet auf Chrome / FF / IE11

Es gibt einen Chrome / IE-Ärger, der darin besteht, dass diese Browser <div>für jede neue Zeile ein Element hinzufügen . Es gibt einen Beitrag über das hier und es kann durch Setzen des fixiert werden contenteditable Element zu seindisplay:inline-block

Wählen Sie hervorgehobenes HTML aus und fügen Sie es hier ein:

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
Ich brauchte eine Einfügung als Nur-Text-Funktion. Getestet auf IE9 und IE10 und funktioniert super. Unnötig zu erwähnen, dass dies auch in großen Browsern funktioniert ... Danke.
Savas Vedova

2
Ihr Code enthält einen Fehler: if (e.originalEvent.clipboardData) kann eine NPE verursachen, da Sie nicht wissen, ob e.originalEvent zu diesem Zeitpunkt vorhanden ist
Sebastian

15

Ich habe hier einen kleinen Proof of Concept für den Vorschlag von Tim Downs mit Textbereich außerhalb des Bildschirms geschrieben. Und hier geht der Code:

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

Kopieren Sie einfach den gesamten Code und fügen Sie ihn in eine HTML-Datei ein. Versuchen Sie dann, (mit Strg-V) Text aus der Zwischenablage an einer beliebigen Stelle im Dokument einzufügen.

Ich habe es in IE9 und neuen Versionen von Firefox, Chrome und Opera getestet. Funktioniert ganz gut. Es ist auch gut, dass man jede Tastenkombination verwenden kann, die er bevorzugt, um diese Funktionalität zu nutzen. Vergessen Sie natürlich nicht, jQuery-Quellen einzuschließen.

Fühlen Sie sich frei, diesen Code zu verwenden. Wenn Sie Verbesserungen oder Probleme haben, senden Sie diese bitte zurück. Beachten Sie auch, dass ich kein Javascript-Entwickler bin, sodass ich möglicherweise etwas verpasst habe (=> mache dein eigenes Testign).


Macs fügen nicht mit Strg-V ein, sondern verwenden cmd-v. Also setze ctrlKey = 91 statt 17
Jeremy T

2
Oder vielleicht ist es nicht immer 91: stackoverflow.com/questions/3834175/… Unabhängig davon bin ich mir ziemlich sicher, dass jQuery all das für Sie erledigt. Überprüfen Sie einfach e.ctrlKey oder e.metaKey, denke ich.
Jeremy T

3
e.ctrlKey oder e.metaKey ist Teil des JavaScript-DOM, nicht jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne

2
Ich denke nicht, dass dies zum Klicken und Einfügen mit der rechten Maustaste funktioniert. Viele Leute verfolgen diesen Ansatz.
Eric Wood

10

Basierend auf l2aelba Antwort. Dies wurde auf FF, Safari, Chrome, IE (8,9,10 und 11) getestet.

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

Gibt es eine Möglichkeit, neue Zeilen beim Einfügen in den Internet Explorer beizubehalten?
Staysee

10

Dieser verwendet kein setTimeout ().

Ich habe diesen großartigen Artikel verwendet, um Cross-Browser-Unterstützung zu erreichen.

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

Dieser Code wird vor dem Einfügen: Demo um das Auswahlhandle erweitert


+1 Ich mag dieses besser als Nico Burns, obwohl ich denke, dass jeder seinen eigenen Platz hat.
n0nag0n

5

Um den eingefügten Text zu bereinigen und den aktuell ausgewählten Text durch den eingefügten Text zu ersetzen, ist die Sache ziemlich 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();
}

Können Sie eine Demoseite bereitstellen, auf der dies funktioniert? Ich habe es versucht und es funktioniert nicht
vsync

5

Dies sollte in allen Browsern funktionieren, die das Ereignis onpaste und den Mutationsbeobachter unterstützen.

Diese Lösung geht weit über das Abrufen nur des Texts hinaus. Sie ermöglicht es Ihnen, den eingefügten Inhalt zu bearbeiten, bevor er in ein Element eingefügt wird.

Es funktioniert mit inhaltsbearbeitbaren Onpaste-Ereignissen (von allen gängigen Browsern unterstützt) und Mutationsbeobachtern (unterstützt von Chrome, Firefox und IE11 +).

Schritt 1

Erstellen Sie ein HTML-Element mit contenteditable

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

Schritt 2

Fügen Sie in Ihrem Javascript-Code das folgende Ereignis hinzu

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

Wir müssen pasteCallBack binden, da der Mutationsbeobachter asynchron aufgerufen wird.

Schritt 3

Fügen Sie Ihrem Code die folgende Funktion hinzu

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

Was der Code macht:

  1. Jemand löst das Einfügeereignis mit Strg-V, Kontextmenü oder anderen Mitteln aus
  2. Im Einfügeereignis wird ein neues Element mit contenteditable erstellt (ein Element mit contenteditable verfügt über erhöhte Berechtigungen).
  3. Die Caret-Position des Zielelements wird gespeichert.
  4. Der Fokus wird auf das neue Element gesetzt
  5. Der Inhalt wird in das neue Element eingefügt und im DOM gerendert.
  6. Der Mutationsbeobachter fängt dies ab (er registriert alle Änderungen am Dombaum und Inhalt). Dann wird das Mutationsereignis ausgelöst.
  7. Der Dom des eingefügten Inhalts wird in eine Variable geklont und an den Rückruf zurückgegeben. Das temporäre Element wird zerstört.
  8. Der Rückruf empfängt das geklonte DOM. Das Caret ist restauriert. Sie können dies bearbeiten, bevor Sie es an Ihr Ziel anhängen. Element. In diesem Beispiel verwende ich Tim Downs-Funktionen zum Speichern / Wiederherstellen des Carets und zum Einfügen von HTML in das Element.

Beispiel


Vielen Dank an Tim Down In diesem Beitrag finden Sie die Antwort:

Ruft den eingefügten Inhalt beim Einfügen in ein Dokument ab


4

Die für mich geeignete Lösung besteht darin, einen Ereignis-Listener zum Einfügen eines Ereignisses hinzuzufügen, wenn Sie eine Texteingabe einfügen. Da das Einfügeereignis auftritt, bevor sich Text in der Eingabe ändert, erstelle ich in meinem Handler zum Einfügen eine verzögerte Funktion, in der ich nach Änderungen in meinem Eingabefeld suche, die beim Einfügen aufgetreten sind:

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

2
Horror ist leider ein Teil unserer Stellenbeschreibung;) Aber ich stimme zu, dies ist ein Hack und Hacks sollten NUR verwendet werden, wenn alle anderen Optionen ausgeschöpft sind.
Lex

4

Dies war zu lang für einen Kommentar zu Nicos Antwort, von der ich glaube, dass sie in Firefox nicht mehr funktioniert (gemäß den Kommentaren), und in Safari nicht so wie sie ist.

Erstens scheinen Sie jetzt in der Lage zu sein, direkt aus der Zwischenablage zu lesen. Anstatt Code wie:

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

verwenden:

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

weil Firefox ein typesFeld hat, das DOMStringListnicht implementiert ist test.

Next Firefox lässt das Einfügen nur zu, wenn der Fokus in einem contenteditable=trueFeld liegt.

Schließlich erlaubt Firefox das zuverlässige Einfügen nur, wenn der Fokus auf einer textarea(oder vielleicht einer Eingabe) liegt, die nicht nur, contenteditable=truesondern auch:

  • nicht display:none
  • nicht visibility:hidden
  • nicht null groß

Ich habe versucht, das Textfeld auszublenden, damit das Einfügen über einen JS VNC-Emulator funktioniert (dh es wurde an einen Remoteclient gesendet und es gab kein textareausw. zum Einfügen). Ich habe festgestellt, dass der Versuch, das Textfeld oben auszublenden, Symptome hervorrief, bei denen es manchmal funktionierte, aber normalerweise beim zweiten Einfügen fehlschlug (oder wenn das Feld gelöscht wurde, um zu verhindern, dass dieselben Daten zweimal eingefügt wurden), da das Feld den Fokus verlor und nicht richtig wiederhergestellt wurde es trotz focus(). Die Lösung, die ich mir ausgedacht habe, war, es auf 1px mal 1px zu setzen z-order: -1000, zu machen display:noneund alle Farben auf transparent zu setzen. Yuck.

Auf Safari gilt der zweite Teil der oben genannten Punkte, dh Sie benötigen einen, textareader nicht vorhanden ist display:none.


Möglicherweise sollten Entwickler, die an Browser-Rendering-Engines arbeiten, eine Seite oder einen Bereich auf Dokumentationsseiten haben, mit denen sie Notizen zu den Funktionen schreiben können, an denen sie arbeiten. Wenn sie beispielsweise an der Einfügefunktion gearbeitet haben, würden sie hinzufügen: "Einfügen funktioniert nicht, wenn die Anzeige keine ist, die Sichtbarkeit ausgeblendet ist oder die Größe Null ist."
1,21 Gigawatt


3

Einfache Lösung:

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

2

Das hat bei mir funktioniert:

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

Sie können dies folgendermaßen tun:

Verwenden Sie dieses jQuery-Plugin für Ereignisse vor und nach dem Einfügen:

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

Jetzt können Sie dieses Plugin verwenden;:

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

Erklärung

Legen Sie zunächst eine UID für alle vorhandenen Elemente als Datenattribut fest.

Vergleichen Sie dann das POST PASTE-Ereignis aller Knoten. Durch Vergleichen können Sie also die neu eingefügte identifizieren, da sie eine UID haben. Entfernen Sie dann einfach das Attribut style / class / id aus den neu erstellten Elementen, damit Sie Ihre ältere Formatierung beibehalten können.


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

1

Lassen Sie den Browser einfach wie gewohnt in seinem inhaltsbearbeitbaren Div einfügen und tauschen Sie nach dem Einfügen alle für benutzerdefinierte Textstile verwendeten Span-Elemente mit dem Text selbst aus. Dies scheint im Internet Explorer und den anderen Browsern, die ich ausprobiert habe, in Ordnung zu sein ...

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

Diese Lösung setzt voraus, dass Sie jQuery ausführen und keine Textformatierung in einem Ihrer inhaltsbearbeitbaren Divs wünschen .

Das Plus ist, dass es super einfach ist.


Warum spantaggen? Ich würde mir vorstellen, dass die Frage alle Tags betraf.
Alexis Wilke

1

Diese Lösung ersetzt das HTML-Tag. Sie ist einfach und browserübergreifend. Überprüfen Sie diese jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , Kerncode :

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

Hinweis: Sie sollten einige Arbeiten zum xss-Filter auf der Rückseite durchführen, da diese Lösung keine Zeichenfolgen wie '<< >>' filtern kann.


XSS-Filering auf dem Server hat nichts damit zu tun, ob Ihr JavaScript-Filter gute Arbeit leistet. Hacker umgehen ohnehin 100% Ihrer JS-Filterung.
Alexis Wilke

Verwenden Sie niemals Regex, um HTML zu analysieren / zu transformieren!
SubliemeSiem

0

Dies ist ein vorhandener Code, der oben veröffentlicht wurde, aber ich habe ihn für IE aktualisiert. Der Fehler war, dass beim Auswählen und Einfügen des vorhandenen Texts der ausgewählte Inhalt nicht gelöscht wird. Dies wurde durch den folgenden Code behoben

selRange.deleteContents(); 

Siehe vollständigen Code unten

$('[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);
    }        
});
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.