JavaScript mendapatkan data clipboard pada acara tempel (Cross browser)

299

Bagaimana aplikasi web mendeteksi acara tempel dan mengambil data yang akan ditempelkan?

Saya ingin menghapus konten HTML sebelum teks disisipkan ke editor teks kaya.

Membersihkan teks setelah disisipkan sesudahnya berfungsi, tetapi masalahnya adalah bahwa semua pemformatan sebelumnya hilang. Misalnya, saya bisa menulis kalimat di editor dan membuatnya tebal, tetapi ketika saya menempelkan teks baru, semua pemformatan hilang. Saya ingin membersihkan hanya teks yang disisipkan, dan membiarkan format sebelumnya tidak tersentuh.

Idealnya, solusinya harus bekerja di semua browser modern (misalnya, MSIE, Gecko, Chrome, dan Safari).

Perhatikan bahwa MSIE memiliki clipboardData.getData(), tetapi saya tidak dapat menemukan fungsionalitas yang serupa untuk browser lain.

Alex
sumber
Semua jawaban ini menjelaskan cara mendapatkan konten teks. Mendapatkan konten gambar atau konten file membutuhkan lebih banyak pekerjaan. Mungkin kita dapat mengubah judul menjadi "JavaScript dapatkan data papan klip teks ..."
1,21 gigawatt
1
seperti kata nico: event.clipboardData.getData('Text')bekerja untuk saya.
Andre Elrico
document.addEventListener('paste'...bekerja untuk saya tetapi menyebabkan konflik jika pengguna ingin dapat menempel di tempat lain pada halaman. Kemudian saya mencoba myCanvasElement.addEventListener('paste'..., tetapi itu tidak berhasil. Akhirnya saya tahu myCanvasElement.parentElement.addEventListener('paste'...berhasil.
Ryan

Jawaban:

149

Situasi telah berubah sejak menulis jawaban ini: sekarang Firefox telah menambahkan dukungan di versi 22, semua browser utama sekarang mendukung mengakses data clipboard dalam acara tempel. Lihat jawaban Nico Burns sebagai contoh.

Di masa lalu ini umumnya tidak mungkin dilakukan dengan cara lintas-browser. Yang ideal adalah untuk bisa mendapatkan konten yang disisipkan melalui pasteacara, yang dimungkinkan di browser terbaru tetapi tidak di beberapa browser lama (khususnya, Firefox <22).

Ketika Anda perlu mendukung browser lama, apa yang dapat Anda lakukan cukup terlibat dan sedikit peretasan yang akan bekerja di Firefox 2+, IE 5.5+ dan browser WebKit seperti Safari atau Chrome. Versi terbaru dari TinyMCE dan CKEditor menggunakan teknik ini:

  1. Deteksi acara ctrl-v / shift-in menggunakan pengendali acara tombol ditekan
  2. Dalam penangan itu, simpan pilihan pengguna saat ini, tambahkan elemen textarea off-screen (katakan di kiri -1000px) ke dokumen, matikan designModedan panggil focus()textarea, sehingga menggerakkan tanda sisipan dan secara efektif mengarahkan ulang pasta
  3. Atur pengatur waktu yang sangat singkat (katakanlah 1 milidetik) di pengatur acara untuk memanggil fungsi lain yang menyimpan nilai textarea, menghapus textarea dari dokumen, menghidupkan designModekembali, mengembalikan pilihan pengguna dan menempelkan teks masuk.

Perhatikan bahwa ini hanya akan berfungsi untuk acara tempel keyboard dan bukan pasta dari konteks atau edit menu. Pada saat acara paste diaktifkan, sudah terlambat untuk mengarahkan ulang tanda sisipan ke dalam textarea (setidaknya di beberapa browser, setidaknya).

Jika Anda perlu mendukung Firefox 2, perhatikan bahwa Anda harus meletakkan textarea di dokumen induk daripada dokumen iframe editor WYSIWYG di peramban itu.

Tim Down
sumber
1
Wow, terima kasih untuk itu! Tampaknya menjadi hack yang sangat canggih ;-) Bisakah Anda jelaskan bahwa designMode dan seleksi sedikit lebih, terutama pada langkah 3? Terima kasih banyak!
Alex
5
Aku punya firasat buruk kau akan menanyakan itu. Seperti yang saya katakan, ini cukup terlibat: Saya sarankan melihat sumber TinyMCE atau CKEditor, karena saya belum punya waktu untuk menguraikan semua masalah yang terlibat. Singkatnya, designModeadalah properti Boolean dari documentdan membuat seluruh halaman dapat diedit kapan true. Editor WYSIWYG biasanya menggunakan iframe dengan designModeon sebagai panel yang dapat diedit. Menyimpan dan memulihkan pilihan pengguna dilakukan dengan satu cara di IE dan lainnya di browser lain, seperti menempelkan konten ke editor. Anda harus mendapatkan TextRangedi IE dan Rangebrowser lainnya.
Tim Down
6
@Samuel: Anda dapat mendeteksinya menggunakan pasteacara tersebut tetapi umumnya sudah terlambat untuk mengarahkan ulang pasta ke elemen lain, sehingga peretasan ini tidak akan berfungsi. Fallback di sebagian besar editor adalah untuk menunjukkan dialog bagi pengguna untuk menempel.
Tim Down
6
Beberapa info lebih lanjut tentang ini: Firefox tidak akan memungkinkan Anda untuk memindahkan fokus ke elemen lain dalam pasteacara tersebut, namun Firefox akan memungkinkan Anda untuk menghapus konten elemen (dan menyimpannya ke variabel sehingga Anda dapat mengembalikannya nanti). Jika kontainer ini adalah div(mungkin berfungsi iframejuga) maka Anda dapat menggilir konten yang ditempel menggunakan metode dom normal, atau mendapatkannya sebagai string menggunakan innerHTML. Anda kemudian dapat mengembalikan konten sebelumnya div, dan menyisipkan konten apa pun yang Anda suka. Oh, dan Anda harus menggunakan timer hack yang sama seperti di atas. Saya terkejut TinyMCE tidak melakukan ini ...
Nico Burns
8
@ResistDesign: Saya tidak setuju - ini adalah cara yang tidak elegan dan rumit untuk menebus kurangnya API yang masuk akal. Akan lebih baik untuk bisa mendapatkan konten yang disisipkan langsung dari acara tempel, yang dimungkinkan secara terbatas di beberapa browser .
Tim Down
318

Solusi # 1 (Khusus Teks Biasa dan membutuhkan Firefox 22+)

Bekerja untuk IE6 +, FF 22+, Chrome, Safari, Edge (Hanya diuji di IE9 +, tetapi seharusnya berfungsi untuk versi yang lebih rendah)

Jika Anda memerlukan dukungan untuk menempelkan HTML atau Firefox <= 22, lihat Solusi # 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/

Perhatikan bahwa solusi ini menggunakan parameter 'Teks' untuk getDatafungsi, yang tidak standar. Namun, ini berfungsi di semua browser pada saat penulisan.


Solusi # 2 (HTML dan berfungsi untuk Firefox <= 22)

Diuji dalam 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/

Penjelasan

The onpasteevent dari divmemiliki handlePastefungsi yang melekat padanya dan melewati argumen tunggal: eventobjek untuk acara paste. Yang menarik bagi kami adalah clipboardDataproperti acara ini yang memungkinkan akses clipboard di browser non-ie. Di IE, yang setara adalah window.clipboardData, meskipun ini memiliki API yang sedikit berbeda.

Lihat bagian sumber daya di bawah ini.


The handlepasteFungsi:

Fungsi ini memiliki dua cabang.

Pemeriksaan pertama untuk keberadaan event.clipboardDatadan memeriksa apakah typesproperti itu mengandung 'teks / html' ( typesbisa berupa DOMStringListyang diperiksa menggunakan containsmetode, atau string yang diperiksa menggunakan indexOfmetode). Jika semua kondisi ini terpenuhi, maka kami melanjutkan seperti dalam solusi # 1, kecuali dengan 'teks / html' alih-alih 'teks / polos'. Ini saat ini berfungsi di Chrome dan Firefox 22+.

Jika metode ini tidak didukung (semua browser lain), maka kami

  1. Simpan konten elemen ke a DocumentFragment
  2. Kosongkan elemennya
  3. Panggil waitForPastedDatafungsinya

The waitforpastedataFungsi:

Fungsi ini pertama-tama memberikan jajak pendapat untuk data yang di-paste (sekali per 20 ms), yang diperlukan karena tidak langsung muncul. Ketika data telah muncul itu:

  1. Menyimpan innerHTML dari div yang dapat diedit (yang sekarang menjadi data yang disisipkan) ke variabel
  2. Kembalikan konten yang disimpan di DocumentFragment
  3. Memanggil fungsi 'processPaste' dengan data yang diambil

Itu processpasteFungsi:

Apakah hal-hal yang sewenang-wenang dengan data yang di-paste. Dalam hal ini kami hanya memberitahukan data, Anda dapat melakukan apa pun yang Anda suka. Anda mungkin ingin menjalankan data yang di-paste melalui semacam proses sanitasi data.


Menyimpan dan mengembalikan posisi kursor

Dalam situasi nyata Anda mungkin ingin menyimpan pilihan sebelumnya, dan mengembalikannya setelah itu ( Tetapkan posisi kursor pada contentEditable <div> ). Anda kemudian bisa menyisipkan data yang di-paste di posisi kursor ketika pengguna memulai tindakan tempel.

Sumber:

Terima kasih kepada Tim Down untuk menyarankan penggunaan DocumentFragment, dan abligh untuk menangkap kesalahan di Firefox karena penggunaan DOMStringList bukan string untuk clipboardData.types

Nico Burns
sumber
4
Menarik. Saya pikir saya sudah mencoba ini di masa lalu dan itu tidak berhasil di beberapa browser, tapi saya yakin Anda benar. Saya pasti lebih suka memindahkan konten yang ada ke dalam DocumentFragmentdaripada menggunakan innerHTMLkarena beberapa alasan: pertama, Anda menyimpan penangan acara yang ada; kedua, menyimpan dan memulihkan innerHTMLtidak dijamin untuk membuat salinan DOM sebelumnya yang identik; ketiga, Anda kemudian dapat menyimpan pilihan sebagai Rangedaripada harus mencari-cari dengan menambahkan elemen marker atau menghitung offset teks (yang harus Anda lakukan jika menggunakan innerHTML).
Tim Down
3
Memang ada flash tanpa konten (FONC?), Yang jelas akan lebih buruk jika pemrosesan konten yang di-paste membutuhkan waktu. Btw, mengapa mengekstraksi DocumentFragmentpada IE? Ini sama dengan di browser lain, kecuali jika Anda menggunakan Range dan extractContents()melakukannya, yang tidak lebih ringkas daripada alternatif dalam hal apa pun. Saya telah menerapkan contoh teknik Anda, menggunakan Rangy untuk menjaga hal-hal bagus dan seragam di seluruh browser: jsfiddle.net/bQeWC/4 .
Tim Down
1
@ Martin: Demo jsFiddle yang saya posting di komentar dapat membantu.
Tim Down
1
Tampaknya tidak berfungsi lagi di Firefox 28 (setidaknya) untuk Windows. Tidak pernah keluar dari waitforpastedatafungsi
Oliboy50
1
FYI: Edge sekarang mendukung pembacaan data dengan MIME-Type text/htmlmenggunakan W3C Clipboard API. Di masa lalu upaya seperti itu akan menimbulkan pengecualian. Jadi orang tidak lagi membutuhkan solusi / peretasan ini untuk Edge.
Jenny O'Reilly
130

Versi sederhana:

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

Menggunakan clipboardData

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

Edge, Firefox, Chrome, Safari, Opera diuji.

Document.execCommand () sudah usang sekarang.


Catatan: Ingatlah untuk memeriksa juga input / output di sisi server (seperti tag-strip PHP )

l2aelba
sumber
4
Ini bekerja dengan sangat baik, tetapi tidak ada versi IE yang memungkinkan akses ke clipboardData dari acara :( Solusi yang bagus, meskipun, ini harus lebih tinggi!
Eric Wood
1
Sepertinya Anda bisa mendapatkan data clipboard di IE dengan cara yang berbeda, jadi jika Anda mendeteksi IE, Anda dapat menggunakan data itu alih-alih fallback cepat: msdn.microsoft.com/en-us/library/ie/ms535220(v = vs.85) .aspx
Andrew
4
jawaban peramban terbaik ditemukan sejauh ini. cukup tambahkan kode untuk IE dan sempurna.
Arturo
6
Ini bekerja di IE (ah, IE manis, bertentangan)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 Langsung

Diuji pada Chrome / FF / IE11

Ada gangguan Chrome / IE yang mana browser ini menambahkan <div>elemen untuk setiap baris baru. Ada posting tentang ini di sini dan itu bisa diperbaiki dengan mengatur elemen contenteditable menjadidisplay:inline-block

Pilih beberapa HTML yang disorot dan rekatkan di sini:

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
sumber
1
Saya membutuhkan tempel sebagai fitur teks biasa. Diuji pada IE9 dan IE10 dan bekerja dengan baik. Tidak perlu disebutkan yang berfungsi di browser utama juga ... Terima kasih.
Savas Vedova
2
Kode Anda mengandung bug: if (e.originalEvent.clipboardData) dapat menyebabkan NPE karena Anda tidak tahu apakah e.originalEvent ada pada saat itu
Sebastian
15

Saya telah menulis sedikit bukti konsep untuk proposal Tim Downs di sini dengan textarea di luar layar. Dan begini kodenya:

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

Cukup salin dan tempel seluruh kode ke dalam satu file html dan coba tempel (menggunakan ctrl-v) teks dari clipboard di mana saja pada dokumen.

Saya sudah mengujinya di IE9 dan versi baru Firefox, Chrome dan Opera. Bekerja dengan cukup baik. Juga baik bahwa seseorang dapat menggunakan kombinasi tombol apa pun yang ia sukai untuk melakukan triger fungsi ini. Tentu saja jangan lupa untuk menyertakan sumber jQuery.

Jangan ragu untuk menggunakan kode ini dan jika Anda datang dengan beberapa perbaikan atau masalah, silakan kirim kembali. Perhatikan juga bahwa saya bukan pengembang Javascript sehingga saya mungkin melewatkan sesuatu (=> lakukan testign Anda sendiri).

JanM
sumber
Mac tidak menempel dengan ctrl-v, mereka menggunakan cmd-v. Jadi atur ctrlKey = 91 bukannya 17
Jeremy T
2
Atau mungkin tidak selalu 91: stackoverflow.com/questions/3834175/... Terlepas dari itu, saya cukup yakin jQuery menangani semua itu untuk Anda, cukup periksa e.ctrlKey atau e.metaKey. Saya pikir.
Jeremy T
3
e.ctrlKey atau e.metaKey adalah bagian dari JavaScript DOM, bukan jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne
2
Saya tidak berpikir ini berfungsi untuk mengklik kanan dan menempel. Banyak orang mengambil pendekatan itu.
Eric Wood
10

Berdasarkan l2aelba anwser. Ini diuji pada FF, Safari, Chrome, IE (8,9,10 dan 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);
            }
        }
    });
Tomell
sumber
Apakah ada cara untuk mempertahankan jalur baru saat menempel ke IE?
Staysee
10

Yang ini tidak menggunakan setTimeout ().

Saya telah menggunakan ini artikel bagus untuk mencapai dukungan lintas 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);
    }
}); 

Kode ini diperpanjang dengan pegangan seleksi sebelum menempel: demo

AsgarAli
sumber
+1 Saya suka yang ini lebih baik daripada Nico Burns, meskipun saya pikir masing-masing memiliki tempat sendiri.
n0nag0n
5

Untuk membersihkan teks yang disisipkan dan mengganti teks yang saat ini dipilih dengan teks yang disisipkan masalah cukup sepele:

<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
sumber
Bisakah Anda memberikan halaman demo tempat ini berfungsi? Saya sudah mencobanya dan tidak berhasil
vsync
5

Ini harus bekerja pada semua browser yang mendukung acara onpaste dan pengamat mutasi.

Solusi ini lebih dari sekadar mendapatkan teks saja, ini sebenarnya memungkinkan Anda untuk mengedit konten yang ditempelkan sebelum disisipkan ke dalam elemen.

Ia bekerja dengan menggunakan event onpaste yang dapat diedit (didukung oleh semua browser utama) pengamat mutasi (didukung oleh Chrome, Firefox dan IE11 +)

Langkah 1

Buat elemen HTML dengan contenteditable

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

Langkah 2

Dalam kode Javascript Anda, tambahkan acara berikut

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

Kita perlu mengikat pasteCallBack, karena pengamat mutasi akan dipanggil secara tidak sinkron.

langkah 3

Tambahkan fungsi berikut ke kode Anda

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

Apa yang dilakukan kode:

  1. Seseorang menyalakan acara tempel dengan menggunakan ctrl-v, konteksmenu atau cara lain
  2. Dalam acara tempel elemen baru dengan contenteditable dibuat (elemen dengan contenteditable memiliki hak istimewa yang ditingkatkan)
  3. Posisi tanda titik dari elemen target disimpan.
  4. Fokus diatur ke elemen baru
  5. Konten akan disisipkan ke elemen baru dan diberikan di DOM.
  6. Pengamat mutasi menangkap ini (ia mencatat semua perubahan pada pohon dom dan konten). Kemudian jalankan acara mutasi.
  7. Dom konten yang disisipkan akan dikloning ke dalam variabel dan kembali ke panggilan balik. Elemen sementara dihancurkan.
  8. Callback menerima DOM hasil kloning. Tanda sisipan dipulihkan. Anda dapat mengedit ini sebelum menambahkannya ke target Anda. elemen. Dalam contoh ini saya menggunakan fungsi Tim Downs untuk menyimpan / mengembalikan tanda sisipan dan menempelkan HTML ke dalam elemen.

Contoh


Terima kasih banyak kepada Tim Down. Lihat posting ini untuk jawabannya:

Dapatkan konten yang disisipkan pada dokumen pada acara tempel

Mouser
sumber
4

Solusi yang berfungsi bagi saya adalah menambahkan pendengar acara untuk menempelkan acara jika Anda menempelkan ke input teks. Karena peristiwa tempel terjadi sebelum teks dalam perubahan input, di dalam handler di tempel saya membuat fungsi yang ditangguhkan di dalamnya saya memeriksa perubahan di kotak input saya yang terjadi pada tempel:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}
Lex
sumber
2
Sayangnya, horor adalah bagian dari deskripsi pekerjaan kami;) Tapi saya setuju, ini adalah retasan dan peretasan harus HANYA digunakan ketika semua opsi lain sudah habis.
Lex
4

Ini terlalu lama untuk mengomentari jawaban Nico, yang menurut saya tidak berfungsi lagi di Firefox (per komentar), dan tidak berfungsi untuk saya di Safari seperti sebelumnya.

Pertama, sekarang Anda tampaknya dapat membaca langsung dari clipboard. Daripada kode seperti:

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

menggunakan:

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

karena Firefox memiliki typesbidang yang DOMStringListtidak diimplementasikan test.

Firefox berikutnya tidak akan mengizinkan tempel kecuali fokusnya ada di contenteditable=truebidang.

Akhirnya, Firefox tidak akan mengizinkan tempel dengan andal kecuali fokusnya ada pada textarea(atau mungkin input) yang tidak hanya contenteditable=truetetapi juga:

  • tidak display:none
  • tidak visibility:hidden
  • tidak berukuran nol

Saya mencoba untuk menyembunyikan bidang teks sehingga saya dapat membuat karya pasta melalui emulator JS VNC (yaitu akan pergi ke klien jarak jauh dan tidak ada sebenarnya textareadll untuk menempel ke). Saya menemukan mencoba menyembunyikan bidang teks di atas memberikan gejala di mana kadang-kadang bekerja, tetapi biasanya gagal pada tempel kedua (atau ketika bidang itu dihapus untuk mencegah menempelkan data yang sama dua kali) karena bidang kehilangan fokus dan tidak akan mendapatkan kembali dengan benar meskipun begitu focus(). Solusi yang saya temukan adalah menaruhnya z-order: -1000, membuatnyadisplay:none , menjadikannya sebagai 1px demi 1px, dan mengatur semua warna menjadi transparan. Yuck.

Di Safari, Anda bagian kedua dari yang di atas berlaku, yaitu Anda harus memiliki textareayang tidak display:none.

abligh
sumber
Mungkin pengembang yang bekerja pada mesin rendering browser harus memiliki halaman atau ruang pada situs dokumentasi yang dapat mereka gunakan untuk menulis dalam catatan tentang fitur yang mereka kerjakan. Misalnya, jika mereka bekerja pada fungsi tempel mereka akan menambahkan, "Tempel tidak akan berfungsi jika tampilan tidak ada, visibilitas disembunyikan atau ukurannya nol".
1,21 gigawatt
3

Pertama yang terlintas dalam pikiran adalah pastehandler lib penutupan google http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html

tApakah
sumber
yang ini tampaknya mendeteksi acara tempel dengan aman, tetapi tampaknya tidak dapat menangkap / mengembalikan konten yang disisipkan?
Alex
@Alex: Anda benar, dan ini juga hanya berfungsi dengan textareas, bukan editor teks kaya.
Tim Down
3

Solusi sederhana:

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

Ini bekerja untuk saya:

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

Anda dapat melakukan ini dengan cara ini:

gunakan plugin jQuery ini untuk acara pra & pasca tempel:

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

Sekarang Anda dapat menggunakan plugin ini ;:

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

Penjelasan

Pertama-tama set uid untuk semua elemen yang ada sebagai atribut data.

Kemudian bandingkan semua node POST PASTE event. Jadi dengan membandingkan Anda dapat mengidentifikasi yang baru dimasukkan karena mereka akan memiliki uid, lalu hapus atribut style / class / id dari elemen yang baru dibuat, sehingga Anda dapat mempertahankan pemformatan yang lebih lama.

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

Biarkan peramban menempel seperti biasa di div kontennya yang dapat diedit dan kemudian setelah tempel menukar elemen rentang yang digunakan untuk gaya teks khusus dengan teks itu sendiri. Ini sepertinya berfungsi baik di internet explorer dan browser lain yang saya coba ...

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

Solusi ini mengasumsikan bahwa Anda menjalankan jQuery dan bahwa Anda tidak ingin pemformatan teks di salah satu div yang dapat diedit konten Anda .

Sisi positifnya adalah sangat sederhana.

DaveAlger
sumber
Mengapa spanmemberi tag? Saya akan membayangkan pertanyaannya adalah tentang semua tag.
Alexis Wilke
1

Solusi ini menggantikan tag html, sederhana dan lintas-browser; periksa jsfiddle ini: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , kode inti:

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

perhatikan: Anda harus melakukan beberapa pekerjaan tentang filter xss di sisi belakang karena solusi ini tidak dapat menyaring string seperti '<< >>'

TomWan
sumber
Pengarsipan XSS di server tidak ada hubungannya dengan apakah filter JavaScript Anda berfungsi dengan baik. Peretas memintas 100% penyaringan JS Anda.
Alexis Wilke
Jangan pernah gunakan Regex untuk mem-parsing / mengubah HTML!
SubliemeSiem
0

Ini adalah kode yang ada diposting di atas tetapi saya telah memperbaruinya untuk IE, bugnya adalah ketika teks yang ada dipilih dan disisipkan tidak akan menghapus konten yang dipilih. Ini telah diperbaiki oleh kode di bawah ini

selRange.deleteContents(); 

Lihat kode lengkap di bawah ini

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