JavaScript pobiera dane ze schowka dotyczące zdarzenia wklejania (w przeglądarce)

299

W jaki sposób aplikacja internetowa może wykryć zdarzenie wklejania i pobrać dane do wklejenia?

Chciałbym usunąć treść HTML przed wklejeniem tekstu do edytora tekstu sformatowanego.

Czyszczenie tekstu po wklejeniu później działa, ale problem polega na tym, że całe poprzednie formatowanie zostało utracone. Na przykład mogę napisać zdanie w edytorze i pogrubić je, ale po wklejeniu nowego tekstu całe formatowanie zostanie utracone. Chcę wyczyścić tylko wklejony tekst i pozostawić poprzednie formatowanie nietknięte.

Idealnie rozwiązanie powinno działać we wszystkich nowoczesnych przeglądarkach (np. MSIE, Gecko, Chrome i Safari).

Zauważ, że MSIE ma clipboardData.getData(), ale nie mogłem znaleźć podobnej funkcjonalności dla innych przeglądarek.

Alex
źródło
Wszystkie te odpowiedzi wyjaśniają, jak uzyskać treść tekstową. Pobieranie zawartości obrazu lub pliku wymaga znacznie więcej pracy. Być może możemy zmienić tytuł na „JavaScript wyczyść dane ze schowka tekstowego ...”
1.21 gigawatów
1
jak powiedział Nico: event.clipboardData.getData('Text')pracował dla mnie.
Andre Elrico,
document.addEventListener('paste'...działało dla mnie, ale powodowało konflikty, jeśli użytkownik chciał mieć możliwość wklejenia w innym miejscu strony. Potem próbowałem myCanvasElement.addEventListener('paste'..., ale to nie działało. W końcu doszedłem do wniosku, że myCanvasElement.parentElement.addEventListener('paste'...działa.
Ryan

Odpowiedzi:

149

Sytuacja zmieniła się od czasu napisania tej odpowiedzi: teraz, gdy Firefox dodał obsługę w wersji 22, wszystkie główne przeglądarki obsługują teraz dostęp do danych schowka w przypadku wklejania. Zobacz przykład Nico Burnsa .

W przeszłości nie było to na ogół możliwe w przeglądarce. Idealnym rozwiązaniem byłoby uzyskanie wklejonej zawartości za pośrednictwem pastezdarzenia, co jest możliwe w najnowszych przeglądarkach, ale nie w niektórych starszych przeglądarkach (w szczególności Firefox <22).

Kiedy potrzebujesz obsługi starszych przeglądarek, możesz zaangażować się i trochę włamać się do przeglądarki Firefox 2+, IE 5.5+ i przeglądarek WebKit, takich jak Safari lub Chrome. Najnowsze wersje TinyMCE i CKEditor używają tej techniki:

  1. Wykryj zdarzenie ctrl-v / shift-ins za pomocą procedury obsługi zdarzeń naciśnięcia klawisza
  2. W tym module obsługi zapisz bieżący wybór użytkownika, dodaj element textarea poza ekranem (powiedzmy po lewej - 1000px) do dokumentu, designModewyłącz i włącz focus()obszar tekstowy, przesuwając kursor i skutecznie przekierowując pastę
  3. Ustaw bardzo krótki licznik czasu (powiedzmy 1 milisekundę) w module obsługi zdarzeń, aby wywoływał inną funkcję, która przechowuje wartość designModepola tekstowego , usuwa obszar tekstowy z dokumentu, włącza się ponownie, przywraca wybór użytkownika i wkleja tekst.

Pamiętaj, że będzie to działać tylko w przypadku zdarzeń wklejania klawiatury, a nie wkleja się z menu kontekstowego lub edycji. Do czasu uruchomienia zdarzenia wklejania jest już za późno, aby przekierować kursor do obszaru tekstowego (przynajmniej w niektórych przeglądarkach).

W mało prawdopodobnym przypadku, gdy potrzebujesz obsługi Firefox 2, pamiętaj, że musisz umieścić obszar tekstowy w dokumencie nadrzędnym zamiast dokumentu iframe edytora WYSIWYG w tej przeglądarce.

Tim Down
źródło
1
Wow, dzięki za to! Wygląda jednak na bardzo wyrafinowany hack ;-) Czy mógłbyś opisać nieco ten tryb projektowania i selekcji, szczególnie w kroku 3? Wielkie dzięki!
Alex
5
Miałem okropne przeczucie, że o to zapytasz. Jak mówię, jest to dość zaangażowane: sugerowałbym przyjrzenie się źródłu TinyMCE lub CKEditor, ponieważ nie mam czasu, aby nakreślić wszystkie związane z tym problemy. Krótko mówiąc, designModejest to właściwość logiczna documenti umożliwia edycję całej strony, gdy true. Edytory WYSIWYG zwykle używają ramki iframe z designModewłączonym jako okienko do edycji. Zapisywanie i przywracanie wyboru użytkownika odbywa się w IE, a innym w innych przeglądarkach, podobnie jak wklejanie zawartości do edytora. Musisz uzyskać TextRangew IE i Rangeinnych przeglądarkach.
Tim Down
6
@Samuel: Możesz to wykryć za pomocą pastezdarzenia, ale na ogół jest już za późno, aby przekierować wklej do innego elementu, więc ten hack nie zadziała. W większości edytorów awarią jest wyświetlenie okna dialogowego, w którym użytkownik może wkleić.
Tim Down
6
Więcej informacji na ten temat: Firefox nie pozwoli Ci przenieść fokusu na inny element w pastewydarzeniu, ale pozwoli ci wyczyścić zawartość tego elementu (i zapisać go w zmiennej, abyś mógł później ją przywrócić). Jeśli ten kontener jest div(prawdopodobnie też działa iframe), możesz następnie przechodzić między wklejonymi treściami przy użyciu normalnych metod dom lub uzyskać jako ciąg znaków przy użyciu innerHTML. Następnie możesz przywrócić poprzednią zawartość divi wstawić dowolną treść. Aha, i musisz użyć tego samego hacka z timerem jak powyżej. Dziwi mnie, że TinyMCE tego nie robi ...
Nico Burns,
8
@ResistDesign: Nie zgadzam się - to nieelegancki i skomplikowany sposób nadrobienia braku rozsądnego API. Lepiej byłoby móc pobrać wklejoną zawartość bezpośrednio ze zdarzenia wklejania, co jest możliwe w ograniczonym zakresie w niektórych przeglądarkach .
Tim Down
318

Rozwiązanie nr 1 (tylko zwykły tekst i wymaga przeglądarki Firefox 22+)

Działa dla IE6 +, FF 22+, Chrome, Safari, Edge (testowany tylko w IE9 +, ale powinien działać dla niższych wersji)

Jeśli potrzebujesz pomocy przy wklejaniu HTML lub Firefox <= 22, zobacz Rozwiązanie nr 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/

Zauważ, że w tym rozwiązaniu użyto parametru „Tekst” dla getDatafunkcji, co jest niestandardowe. Działa jednak we wszystkich przeglądarkach w momencie pisania.


Rozwiązanie nr 2 (HTML i działa w przeglądarce Firefox <= 22)

Testowane w 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/

Wyjaśnienie

onpasteWydarzenie divma handlePastefunkcję dołączony do niej i podał jeden argument: eventobiekt dla zdarzenia wklejania. Szczególnie interesuje nas clipboardDatawłaściwość tego wydarzenia, która umożliwia dostęp do schowka w innych przeglądarkach. W IE odpowiednikiem jest window.clipboardData, choć ma nieco inny interfejs API.

Zobacz sekcję zasobów poniżej.


handlepasteFunkcja:

Ta funkcja ma dwie gałęzie.

Pierwsze sprawdzenie istnienia event.clipboardDatai sprawdzenie, czy jego typeswłaściwość zawiera „text / html” ( typesmoże to być albo DOMStringListsprawdzany za pomocą containsmetody, albo ciąg sprawdzany za pomocąindexOf metody). Jeśli wszystkie te warunki są spełnione, postępujemy jak w rozwiązaniu nr 1, z wyjątkiem „text / html” zamiast „text / plain”. Obecnie działa to w Chrome i Firefox 22+.

Jeśli ta metoda nie jest obsługiwana (wszystkie inne przeglądarki), to my

  1. Zapisz zawartość elementu w DocumentFragment
  2. Opróżnij element
  3. Wywołaj waitForPastedDatafunkcję

waitforpastedataFunkcja:

Ta funkcja najpierw sonduje wklejone dane (raz na 20 ms), co jest konieczne, ponieważ nie pojawia się od razu. Gdy pojawią się dane:

  1. Zapisuje innerHTML edytowalnego div (który jest teraz wklejonym danymi) do zmiennej
  2. Przywraca zawartość zapisaną w DocumentFragment
  3. Wywołuje funkcję „processPaste” z pobranymi danymi

processpasteFunkcja:

Wykonuje dowolne czynności z wklejonymi danymi. W takim przypadku po prostu ostrzegamy dane, możesz zrobić, co chcesz. Prawdopodobnie będziesz chciał uruchomić wklejone dane w procesie dezynfekcji danych.


Zapisywanie i przywracanie pozycji kursora

W prawdziwej sytuacji prawdopodobnie zechcesz wcześniej zapisać wybór i przywrócić go później ( Ustaw pozycję kursora na contentEditable <div> ). Następnie można wstawić wklejone dane w miejscu, w którym znajdował się kursor, gdy użytkownik zainicjował akcję wklejania.

Zasoby:

Dziękujemy Timowi Downowi za zasugerowanie użycia DocumentFragment i dziękuję za złapanie błędu w Firefoksie z powodu użycia DOMStringList zamiast ciągu dla clipboardData.types

Nico Burns
źródło
4
Ciekawy. Myślałem, że próbowałem tego w przeszłości i to nie działało w niektórych przeglądarkach, ale jestem pewien, że masz rację. Zdecydowanie wolałbym przenieść istniejącą zawartość na inną DocumentFragmentniż używać innerHTMLz kilku powodów: po pierwsze, zachowujesz istniejące programy obsługi zdarzeń; po drugie, innerHTMLnie można zagwarantować , że zapisanie i przywrócenie spowoduje utworzenie identycznej kopii poprzedniego DOM; po trzecie, możesz następnie zapisać zaznaczenie jako opcję Rangezamiast marnować czas na dodawanie elementów znaczników lub obliczanie przesunięć tekstu (co musisz zrobić, jeśli użyjesz innerHTML).
Tim Down
3
Rzeczywiście występuje flash bez zawartości (FONC?), Co oczywiście będzie gorsze, jeśli przetwarzanie wklejonej zawartości zajmie trochę czasu. Przy okazji, dlaczego ekstrakcja DocumentFragmentjest uciążliwa w IE? Jest to to samo, co w innych przeglądarkach, chyba że użyjesz Range i extractContents()zrobisz to, co w żadnym wypadku nie jest bardziej zwięzłe niż alternatywa. Wdrożyłem przykład Twojej techniki, używając Rangy, aby zachować ładność i jednolitość w różnych przeglądarkach: jsfiddle.net/bQeWC/4 .
Tim Down
1
@Martin: Demo jsFiddle, które zamieściłem w komentarzach, może pomóc.
Tim Down
1
Wygląda na to, że nie działa już w przeglądarce Firefox 28 (przynajmniej) dla systemu Windows. Nigdy nie wychodzi z waitforpastedatafunkcji
Oliboy50
1
FYI: Edge obsługuje teraz odczytywanie danych za pomocą MIME-Type text/htmlza pomocą W3C Clipboard API. W przeszłości taka próba rzucałaby wyjątek. Więc nie trzeba już tego obejścia / hakowania dla Edge.
Jenny O'Reilly,
130

Prosta wersja:

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

Za pomocą clipboardData

Próbny : http://jsbin.com/nozifexasu/edit?js,output

Testowane Edge, Firefox, Chrome, Safari, Opera.

Document.execCommand () jest już nieaktualny .


Uwaga: pamiętaj, aby sprawdzać także wejścia / wyjścia po stronie serwera (takie jak PHP strip-tags )

L2aelba
źródło
4
Działa to naprawdę dobrze, ale żadna wersja IE nie pozwala na dostęp do clipboardData ze zdarzenia :( Świetne rozwiązanie, ale powinno być wyższe!
Eric Wood
1
Wygląda na to, że możesz dostać się do danych schowka w IE w inny sposób, więc jeśli wykryjesz IE, możesz użyć tych danych zamiast natychmiastowego powrotu: msdn.microsoft.com/en-us/library/ie/ms535220(v = vs.85) .aspx
Andrew
4
jak dotąd najlepsza odpowiedź dla wielu przeglądarek. wystarczy dodać kod dla IE i jest idealny.
Arturo
6
Działa to w IE (ach, słodko, przeciwnie 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

Demo na żywo

Testowane na Chrome / FF / IE11

Denerwuje Chrome / IE, ponieważ przeglądarki te dodają <div>element dla każdej nowej linii. Jest tutaj post na ten temat , który można naprawić, ustawiając contentediable element nadisplay:inline-block

Wybierz podświetlony kod HTML i wklej go tutaj:

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
źródło
1
Potrzebowałem wkleić jako funkcję zwykłego tekstu. Testowany na IE9 i IE10 i działa świetnie. Nie trzeba dodawać, że działa również w głównych przeglądarkach ... Dzięki.
Savas Vedova
2
Twój kod zawiera błąd: jeśli (e.originalEvent.clipboardData) może powodować NPE, ponieważ nie wiesz, czy w tym momencie istnieje e.originalEvent
Sebastian
15

Napisałem tutaj mały dowód koncepcji propozycji Tima Downsa z obszarem tekstowym poza ekranem. A oto kod:

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

Po prostu skopiuj i wklej cały kod do jednego pliku HTML i spróbuj wkleić (używając ctrl-v) tekst ze schowka w dowolnym miejscu dokumentu.

Przetestowałem to w IE9 i nowych wersjach Firefox, Chrome i Opera. Działa całkiem dobrze. Dobrze też, że można użyć dowolnej kombinacji klawiszy, którą preferuje, aby uruchomić tę funkcję. Oczywiście nie zapomnij podać źródeł jQuery.

Zachęcamy do korzystania z tego kodu, a jeśli pojawią się jakieś poprawki lub problemy, prosimy o przesłanie ich z powrotem. Zauważ też, że nie jestem programistą Javascript, więc mogłem coś przeoczyć (=> zrób swój własny testign).

JanM
źródło
Mac nie wkleja się za pomocą ctrl-v, używają cmd-v. Więc ustaw ctrlKey = 91 zamiast 17
Jeremy T
2
A może nie zawsze jest to 91: stackoverflow.com/questions/3834175/... Niezależnie od tego, jestem pewien, że jQuery zajmuje się tym wszystkim, po prostu sprawdź e.ctrlKey lub e.metaKey.
Jeremy T
3
e.ctrlKey lub e.metaKey jest częścią JavaScript DOM, a nie jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne
2
Nie sądzę, że to działa w przypadku klikania prawym przyciskiem i wklejania. Wiele osób przyjmuje takie podejście.
Eric Wood,
10

Oparty na l2aelba anwser . Zostało to przetestowane na FF, Safari, Chrome, IE (8,9,10 i 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
źródło
Czy istnieje sposób na zachowanie nowych wierszy podczas wklejania do IE?
Staysee
10

Ten nie używa żadnej metody setTimeout ().

Użyłem tego świetnego artykułu, aby uzyskać wsparcie dla różnych przeglądarek.

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

Ten kod jest rozszerzony o uchwyt wyboru przed wklejeniem: demo

AsgarAli
źródło
+1 Podoba mi się ten bardziej niż Nico Burns, choć myślę, że każdy ma swoje miejsce.
n0nag0n
5

W przypadku czyszczenia wklejonego tekstu i zastępowania aktualnie zaznaczonego tekstu wklejonym tekstem sprawa jest dość trywialna:

<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
źródło
Czy możesz podać stronę demonstracyjną, na której to działa? Próbowałem i nie działa
vsync
5

Powinno to działać we wszystkich przeglądarkach obsługujących zdarzenie onpaste i obserwatora mutacji.

To rozwiązanie wykracza poza uzyskiwanie samego tekstu, pozwala na edycję wklejonej zawartości przed wklejeniem do elementu.

Działa przy użyciu edytowalnego zdarzenia onpaste (obsługiwanego przez wszystkie główne przeglądarki) i obserwatora mutacji (obsługiwanego przez Chrome, Firefox i IE11 +)

krok 1

Utwórz element HTML z treścią

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

krok 2

W kodzie JavaScript dodaj następujące zdarzenie

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

Musimy powiązać funkcję pasteCallBack, ponieważ obserwator mutacji zostanie wywołany asynchronicznie.

krok 3

Dodaj następującą funkcję do swojego kodu

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

Co robi kod:

  1. Ktoś odpala zdarzenie wklejania za pomocą ctrl-v, menu kontekstowego lub w inny sposób
  2. W przypadku wklejania tworzony jest nowy element z contenteditable (element z contenteditable ma podwyższone uprawnienia)
  3. Pozycja karetki elementu docelowego zostaje zapisana.
  4. Fokus jest ustawiony na nowy element
  5. Treść zostaje wklejona do nowego elementu i jest renderowana w DOM.
  6. Obserwator mutacji łapie to (rejestruje wszystkie zmiany w drzewie dom i zawartości). Następnie odpala zdarzenie mutacji.
  7. Dom wklejonej treści zostanie sklonowany do zmiennej i zwrócony do wywołania zwrotnego. Element tymczasowy zostaje zniszczony.
  8. Oddzwonienie otrzymuje sklonowany DOM. Karetka zostanie przywrócona. Możesz to edytować, zanim dodasz go do celu. element. W tym przykładzie używam funkcji Tim Downs do zapisywania / przywracania kursora i wklejania HTML do elementu.

Przykład


Wielkie dzięki dla Tim Down Zobacz ten post za odpowiedź:

Pobierz wklejoną treść w dokumencie podczas wklejania zdarzenia

Mouser
źródło
4

Rozwiązaniem, które działa dla mnie, jest dodanie detektora zdarzeń w celu wklejenia zdarzenia, jeśli wklejasz tekst. Ponieważ zdarzenie wklejania zdarza się przed zmianą tekstu w danych wejściowych, w moim module obsługi wklejania tworzę odroczoną funkcję, w której sprawdzam zmiany w polu wprowadzania, które nastąpiły podczas wklejania:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}
Lex
źródło
2
Horror, niestety, jest częścią naszego opisu pracy;) Ale zgadzam się, to jest hack i hacków należy używać TYLKO, gdy wszystkie inne opcje są wyczerpane.
Lex
4

To było zbyt długie, aby skomentować odpowiedź Nica, która, jak sądzę, nie działa już w Firefoksie (w przypadku komentarzy) i nie działała dla mnie w Safari w obecnej postaci.

Po pierwsze, wydaje się, że możesz teraz czytać bezpośrednio ze schowka. Zamiast kodu, takiego jak:

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

posługiwać się:

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

ponieważ Firefox ma typespole, DOMStringListktóre nie jest implementowane test.

Następny Firefox nie zezwoli na wklejanie, chyba że fokus znajduje się w contenteditable=truepolu.

Wreszcie Firefox nie zezwoli na niezawodne wklejanie, chyba że fokus jest ustawiony natextarea (lub być może danych wejściowych), który jest nie tylko, contenteditable=trueale także:

  • nie display:none
  • nie visibility:hidden
  • nie wielkości zero

Próbowałem ukryć pole tekstowe, aby umożliwić pracę wklejania na emulatorze JS VNC (tzn. Chodziło do zdalnego klienta i nie było w rzeczywistości textareaitp. Do wklejenia). Odkryłem, że próba ukrycia powyższego pola tekstowego dawała symptomy tam, gdzie czasami działała, ale zwykle nie udawało się przy drugim wklejaniu (lub gdy pole zostało wyczyszczone, aby zapobiec dwukrotnemu wklejeniu tych samych danych), ponieważ pole straciło fokus i nie odzyskało prawidłowo pomimo focus(). Rozwiązaniem, które wymyśliłem, było umieszczenie go z-order: -1000, zrobienie go display:none, ustawienie jako 1 na 1 piksel i ustawienie wszystkich kolorów na przezroczyste. Fuj

W Safari obowiązuje druga część powyższego, tzn. Musisz mieć taką, textareaktóra nie jest display:none.

w płomieniach
źródło
Być może programiści pracujący w silnikach renderujących przeglądarki powinni mieć stronę lub miejsce w witrynach z dokumentacją, których mogą używać do pisania notatek o funkcjach, nad którymi pracują. Na przykład, jeśli pracowaliby nad funkcją wklejania, dodaliby: „Wklejanie nie będzie działać, jeśli wyświetlanie nie będzie widoczne, widoczność jest ukryta lub rozmiar wynosi zero”.
1,21 gigawatów
3

Najpierw przychodzi mi na myśl narzędzie do wklejania biblioteki zamknięcia Google http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html

tDo
źródło
ten zdaje się bezpiecznie wykrywać zdarzenie wklejania, ale wydaje się, że nie jest w stanie złapać / zwrócić wklejonej treści?
Alex
@Alex: masz rację, a to działa tylko z obszarami tekstowymi, a nie z edytorami tekstu sformatowanego.
Tim Down
3

Proste rozwiązanie:

document.onpaste = function(e) {
    var pasted = e.clipboardData.getData('Text');
    console.log(pasted)
}
lama12345
źródło
2

To działało dla mnie:

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
źródło
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
źródło
1

Możesz to zrobić w ten sposób:

użyj tej wtyczki jQuery do zdarzeń przed i po wklejeniu:

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

Teraz możesz użyć tej wtyczki ;:

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

Wyjaśnienie

Najpierw ustaw identyfikator użytkownika dla wszystkich istniejących elementów jako atrybut danych.

Następnie porównaj wszystkie zdarzenia POST PASTE. Porównując, możesz więc zidentyfikować nowo wstawiony, ponieważ będzie on miał identyfikator UID, a następnie po prostu usuń atrybut style / class / id z nowo tworzonych elementów, aby zachować starsze formatowanie.

Peeyush
źródło
1
$('#dom').on('paste',function (e){
    setTimeout(function(){
        console.log(e.currentTarget.value);
    },0);
});
Roman Judin
źródło
1

Po prostu pozwól przeglądarce wkleić się jak zwykle w edytowalnym pliku div, a następnie po wklejeniu zamień dowolne elementy zakresu używane w niestandardowych stylach tekstu na sam tekst. Wygląda na to, że działa dobrze w przeglądarce Internet Explorer i innych przeglądarkach, których próbowałem ...

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

To rozwiązanie zakłada, że ​​korzystasz z jQuery i że nie chcesz formatowania tekstu w żadnym z edytowanych treści div .

Plusem jest to, że jest to bardzo proste.

DaveAlger
źródło
Dlaczego spantagować? Wyobrażam sobie, że pytanie dotyczy wszystkich tagów.
Alexis Wilke,
1

To rozwiązanie zastępuje znacznik html, jest proste i działa w wielu przeglądarkach; sprawdź to jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , kod podstawowy:

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

Uwaga: powinieneś popracować nad filtrem xss z tyłu, ponieważ to rozwiązanie nie może filtrować ciągów takich jak „<< >>”

TomWan
źródło
Filtrowanie XSS na serwerze nie ma nic wspólnego z tym, czy filtr JavaScript działa dobrze. Hakerzy i tak omijają 100% twojego filtrowania JS.
Alexis Wilke,
Nigdy nie używaj Regex do analizowania / przekształcania HTML!
SubliemeSiem
0

To jest istniejący kod opublikowany powyżej, ale zaktualizowałem go dla IE, błąd polegał na tym, że istniejący tekst został zaznaczony i wklejony nie spowoduje usunięcia wybranej zawartości. Zostało to naprawione przez poniższy kod

selRange.deleteContents(); 

Zobacz pełny kod poniżej

$('[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
źródło