JavaScript, yapıştırma olayında pano verilerini al (Çapraz tarayıcı)


300

Bir web uygulaması bir yapıştırma olayını nasıl tespit edebilir ve yapıştırılacak verileri nasıl alabilir?

Metin zengin bir metin düzenleyicisine yapıştırılmadan önce HTML içeriğini kaldırmak istiyorum.

Daha sonra yapıştırıldıktan sonra metni temizlemek çalışır, ancak sorun önceki tüm biçimlendirmelerin kaybolmasıdır. Örneğin, editörde bir cümle yazabilir ve kalın kılabilirim, ancak yeni metin yapıştırdığımda tüm biçimlendirme kaybolur. Yalnızca yapıştırılan metni temizlemek ve önceki biçimlendirmelere dokunmak istemiyorum.

İdeal olarak, çözüm tüm modern tarayıcılarda (ör. MSIE, Gecko, Chrome ve Safari) çalışmalıdır.

MSIE'nin olduğunu clipboardData.getData(), ancak diğer tarayıcılar için benzer işlevler bulamadığımı unutmayın.


Tüm bu cevaplar metin içeriğinin nasıl alınacağını açıklar. Görüntü içeriği veya dosya içeriği almak çok daha fazla çalışma gerektirir. Belki de başlığı "JavaScript sanitized text clipboard data olsun ..." olarak değiştirebiliriz
1.21 gigawatts

1
nico'nun dediği gibi: event.clipboardData.getData('Text')benim için çalıştı.
Andre Elrico

document.addEventListener('paste'...benim için çalıştı ancak bir kullanıcı sayfanın başka bir yerine yapıştırabilmek istediğinde çakışmalara neden oldu. Sonra denedim myCanvasElement.addEventListener('paste'..., ama işe yaramadı. Sonunda myCanvasElement.parentElement.addEventListener('paste'...işe yaradığını anladım .
Ryan

Yanıtlar:


150

Bu yanıtı yazdıktan sonra durum değişti: Firefox sürüm 22'de destek eklediğine göre, tüm büyük tarayıcılar artık bir yapıştırma olayında pano verilerine erişmeyi destekliyor. Örnek için Nico Burns'ün cevabına bakınız .

Geçmişte bu genellikle tarayıcılar arası bir şekilde mümkün değildi. İdeal olan, yapıştırılan içeriği son tarayıcılarda mümkün olan ancak bazı eski tarayıcılarda (özellikle Firefox <22) mümkün olmayan birpaste etkinlikle elde edebilmek olacaktır .

Eski tarayıcıları desteklemeniz gerektiğinde, yapabileceğiniz şey oldukça karmaşıktır ve Firefox 2+, IE 5.5+ ve Safari veya Chrome gibi WebKit tarayıcılarında çalışacak bir kesmek. TinyMCE ve CKEditor'un son sürümleri bu tekniği kullanır:

  1. Bir tuşa basma olay işleyicisi kullanarak ctrl-v / shift-ins olayını algılama
  2. Bu işleyicide, geçerli kullanıcı seçimini kaydedin, belgeye ekran dışında bir textarea öğesi ekleyin (örneğin -1000 piksel) , textarea'yı designModekapatın ve çağırın focus(), böylece caret'i hareket ettirin ve macunu etkili bir şekilde yönlendirin
  3. Olay işleyicisine, textarea değerini depolayan, textarea'yı belgeden kaldıran, designModetekrar açılan, kullanıcı seçimini geri yükleyen ve metni yapıştıran başka bir işlevi çağırmak için olay işleyicisine çok kısa bir zamanlayıcı (1 milisaniye) ayarlayın .

Bunun yalnızca klavye yapıştırma olayları için çalışacağını ve bağlam veya düzenleme menülerindeki pastaların çalışmadığını unutmayın. Yapıştır olayı başladığında, imleci metin alanına yönlendirmek için çok geç (en azından bazı tarayıcılarda).

Olası bir olayda, Firefox 2'yi desteklemeniz gerektiğinde, textarea'yı WYSIWYG düzenleyicisi iframe'in belgesi o tarayıcıya değil de ana belgeye yerleştirmeniz gerektiğini unutmayın.


1
Vay canına, bunun için teşekkürler! Gerçi çok gelişmiş bir kesmek gibi görünüyor ;-) Özellikle 3. adımda, designMode ve seçim şeyi biraz daha açıklayabilir misiniz? Çok teşekkürler!
Alex

5
Bunu soracağın korkunç bir his vardı. Dediğim gibi, oldukça ilgili: TinyMCE veya CKEditor kaynağına bakmanızı öneririm, çünkü ilgili tüm konuları özetlemek için zamanım yok. Kısaca designMode, bir Boolean özelliği documentve ne zaman tüm sayfayı düzenlenebilir yapar true. WYSIWYG editörleri genellikle designModedüzenlenebilir bölme olarak açık olan bir iframe kullanır . Kullanıcı seçiminin kaydedilmesi ve geri yüklenmesi, içerik düzenleyiciye yapıştırıldığı gibi IE'de bir şekilde ve diğer tarayıcılarda başka bir şekilde yapılır. TextRangeIE'de bir Rangeve diğer tarayıcılarda bir almanız gerekir .
Tim Down

6
@Samuel: pasteOlayı kullanarak tespit edebilirsiniz, ancak macunu başka bir öğeye yönlendirmek için genellikle çok geç, bu yüzden bu kesmek işe yaramaz. Çoğu editördeki geri dönüş, kullanıcının yapıştıracağı bir iletişim kutusu göstermektir.
Tim Down

6
pasteBununla ilgili daha fazla bilgi: Firefox, odağı etkinlikteki başka bir öğeye taşımanıza izin vermez , ancak öğenin içeriğini temizlemenize (ve daha sonra geri yükleyebilmeniz için bir değişkene kaydetmenize) izin verir. Bu kapsayıcı bir ise div(muhtemelen iframeçok işe yarıyorsa), daha sonra normal dom yöntemlerini kullanarak yapıştırılan içerik arasında dolaşabilir veya kullanarak bir dize olarak alabilirsiniz innerHTML. Ardından, öğesinin önceki içeriğini geri yükleyebilir ve istediğiniz içeriği divekleyebilirsiniz. Oh, ve yukarıdaki ile aynı zamanlayıcı kesmek kullanmak zorunda. TinyMCE bunu yapmazsa şaşırdım ...
Nico Burns

8
@ResistDesign: Kabul etmiyorum - makul bir API eksikliğini telafi etmek için uygun olmayan ve karmaşık bir yol. Yapıştırılan içeriği doğrudan yapıştır etkinliğinden alabilmek daha iyi olur, bu da bazı tarayıcılarda sınırlı bir şekilde mümkündür .
Tim Down

318

Çözüm # 1 (Yalnızca Düz Metin ve Firefox 22+ gerekir)

IE6 +, FF 22+, Chrome, Safari, Edge için çalışır (Yalnızca IE9 + 'da test edilmiştir, ancak daha düşük sürümlerde çalışmalıdır)

HTML veya Firefox <= 22 yapıştırmak için desteğe ihtiyacınız varsa bkz. Çözüm # 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/

Bu çözümün getDatastandart olmayan işlev için 'Metin' parametresini kullandığını unutmayın . Ancak, yazma sırasında tüm tarayıcılarda çalışır.


Çözüm # 2 (HTML ve Firefox <= 22 için çalışır)

IE6 +, FF 3.5+, Chrome, Safari, Edge'de test edildi

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/

açıklama

onpasteOlay divvardır handlePaste: kendisine bağlı ve tek bir argüman geçirilen işlevi eventmacunu etkinlik için nesne. Bizi özellikle ilgilendiren clipboardDatabu olayın özelliği, yani tarayıcı olmayan tarayıcılarda pano erişimine olanak tanır. IE'de eşdeğerwindow.clipboardData bunun biraz farklı bir API'sı olmasına rağmen.

Aşağıdaki kaynaklar bölümüne bakın.


handlepastefonksiyon:

Bu fonksiyonun iki dalı vardır.

İlk olup olmadığını kontrol eder event.clipboardData, bu ister ve kontrol typesniteliği metin / html 'içerir ( typesolabilir ya da bir DOMStringListile kontrol edilir containsyöntem veya kontrol edilir, bir dizi indexOfyöntem). Bu koşulların tümü yerine getirilirse, 'text / plain' yerine 'text / html' dışında # 1 numaralı çözümde olduğu gibi ilerliyoruz. Bu şu anda Chrome ve Firefox 22+ sürümlerinde çalışmaktadır.

Bu yöntem desteklenmiyorsa (diğer tüm tarayıcılar),

  1. Öğenin içeriğini bir DocumentFragment
  2. Elemanı boşaltın
  3. waitForPastedDataİşlevi çağır

waitforpastedatafonksiyon:

Bu işlev önce yapıştırılan veriler için (her 20 ms'de bir) yoklar; bu, hemen görünmediği için gereklidir. Veriler göründüğünde:

  1. Düzenlenebilir div'in (şimdi yapıştırılan verilerdir) innerHTML'sini bir değişkene kaydeder
  2. DocumentFragment'a kaydedilen içeriği geri yükler
  3. Alınan verilerle 'processPaste' işlevini çağırır

processpastefonksiyon:

Yapıştırılan verilerle keyfi şeyler yapar. Bu durumda sadece verileri uyarırız, istediğinizi yapabilirsiniz. Yapıştırılan verileri muhtemelen bir tür veri temizleme işlemi ile çalıştırmak isteyeceksiniz.


İmleç konumunu kaydetme ve geri yükleme

Gerçek bir durumda, muhtemelen daha önce seçimi kaydetmek ve daha sonra geri yüklemek istersiniz ( contentEditable <div> öğesinde imleç konumunu ayarlayın ). Daha sonra yapıştırılan verileri, kullanıcı yapıştırma işlemini başlattığında imlecin bulunduğu konuma ekleyebilirsiniz.

Kaynaklar:

DocumentFragment kullanımını öneren Tim Down'a ve clipboardData.types için dize yerine DOMStringList kullanımı nedeniyle Firefox'ta bir hata yakalamak için abligh'e teşekkürler


4
İlginç. Bunu geçmişte denediğimi sanıyordum ve bazı tarayıcılarda işe yaramadı, ama eminim haklısınız. Kesinlikle mevcut içeriği çeşitli nedenlerden ötürü DocumentFragmentkullanmak yerine bir yere taşımayı tercih ederim innerHTML: Birincisi, mevcut olay işleyicilerini saklarsınız; ikinci olarak, kaydetme ve geri yükleme innerHTMLişleminin önceki DOM'un aynı kopyasını oluşturması garanti edilmez; üçüncüsü, daha sonra seçimi, Rangeişaretçi öğeleri eklemek veya metin ofsetleri hesaplamak (eğer kullanırsanız yapmanız gerekecek olan) ile etrafta dolaşmak yerine seçimi kaydedebilirsiniz innerHTML.
Tim Down

3
Gerçekten, yapıştırılmamış içeriğin işlenmesi biraz zaman alırsa daha da kötü olacak hiçbir içerik flaşı (FONC?) Vardır. Btw, neden DocumentFragmentIE'de bir acı çekiyor? Bir Aralık kullanmazsanız ve extractContents()yapmazsanız , diğer tarayıcılardakiyle aynıdır , bu da her durumda alternatiften daha kısa değildir. Tarayıcılar arasında işleri güzel ve tekdüze tutmak için Rangy kullanarak tekniğinizin bir örneğini uyguladım: jsfiddle.net/bQeWC/4 .
Tim Down

1
@Martin: Yorumlarda yayınladığım jsFiddle demosu yardımcı olabilir.
Tim Down

1
Görünüşe göre artık Windows için Firefox 28'de (en azından) çalışmıyor. Asla waitforpastedataişlevden
çıkmaz

1
FYI: Edge artık text/htmlW3C Clipboard API'sini kullanarak MIME-Type ile veri okumayı destekliyor . Geçmişte böyle bir girişim bir istisna atardı. Yani artık Edge için bu geçici çözüm / saldırıya gerek yok.
Jenny O'Reilly

130

Basit sürüm:

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

kullanma clipboardData

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

Edge, Firefox, Chrome, Safari, Opera test edildi.

Document.execCommand () artık kullanılmıyor .


Not: Giriş / çıkışı sunucu tarafında da kontrol etmeyi unutmayın ( PHP şerit etiketleri gibi )


4
Gerçekten de bu çalışır, ancak IE hiçbir sürümü olay :( Büyük çözeltisinden clipboardData erişime izin verir, ama, bu yüksek olmalıdır!
Eric Wood

1
Görünüşe göre panoya verilere IE'de farklı bir yoldan ulaşabilirsiniz, bu yüzden IE tespit ederseniz, bu istemi geri dönüş yerine kullanabilirsiniz: msdn.microsoft.com/en-us/library/ie/ms535220(v = vs.85) .aspx
Andrew

4
Şimdiye kadar bulunan en iyi çapraz tarayıcı cevabı. Sadece IE ve mükemmel kodunu ekleyin.
Arturo

6
Bu IE'de çalışıyor (ah, tatlı, aksine 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

Canlı Demo

Chrome / FF / IE11'de test edildi

Bu tarayıcıların <div>her yeni satır için öğe eklediği bir Chrome / IE sıkıntısı var . Burada bununla ilgili bir yazı var ve içerik düzenlenebilir olarak düzeltilebilir. olmak elemanıdisplay:inline-block

Vurgulanan bir HTML seçin ve buraya yapıştırın:

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
Düz metin özelliği olarak yapıştırmaya ihtiyacım vardı. IE9 ve IE10 üzerinde test edildi ve harika çalışıyor. Tabii ki büyük tarayıcılarda da çalışır ... Teşekkürler.
Savas Vedova

2
Kodunuz bir hata içeriyorsa: (e.originalEvent.clipboardData), e.originalEvent'in o noktada olup olmadığını bilmediğiniz için bir NPE'ye neden olabilirse
Sebastian

15

Tim Downs önerisi için ekran dışı metinle birlikte küçük bir konsept kanıtı yazdım. Ve işte 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>

Tüm kodu kopyalayıp bir html dosyasına yapıştırın ve (ctrl-v kullanarak) metni belgeden herhangi bir yere yapıştırmayı deneyin.

IE9 ve Firefox, Chrome ve Opera'nın yeni sürümlerinde test ettim. Oldukça iyi çalışıyor. Ayrıca, bu işlevi tetiklemek için tercih ettiği herhangi bir tuş kombinasyonunu kullanabilmesi de iyidir. Elbette jQuery kaynaklarını eklemeyi unutmayın.

Bu kodu kullanmaktan çekinmeyin ve bazı iyileştirmeler veya sorunlarla karşılaşırsanız lütfen bunları geri gönderin. Ayrıca bir Javascript geliştiricisi olduğumu unutmayın, bu yüzden bir şey kaçırmış olabilirsiniz (=> kendi ifadenizi yapın).


Mac'ler ctrl-v ile yapışmaz, cmd-v kullanır. Yani 17 yerine ctrlKey = 91 ayarını yap
Jeremy T

2
Ya da belki de her zaman 91 değildir: stackoverflow.com/questions/3834175/… Ne olursa olsun, jQuery sizin için tüm bunları işlediğinden eminim, sadece e.ctrlKey veya e.metaKey'i kontrol edin.
Jeremy T

3
e.ctrlKey veya e.metaKey jQuery'nin değil, JavaScript DOM'un bir parçasıdır: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne

2
Bunun sağ tıklama ve yapıştırma için işe yaramadığını düşünüyorum. Birçok insan bu yaklaşımı benimser.
Eric Wood

10

L2aelba anwser tabanlı . Bu, FF, Safari, Chrome, IE (8,9,10 ve 11) üzerinde test edildi

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

IE'ye yapıştırırken yeni satırları korumanın bir yolu var mı?
14'te Staysee

10

Bu herhangi bir setTimeout () kullanmaz.

Bu harika makaleyi çapraz tarayıcı desteği elde etmek için kullandım .

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

Bu kod, yapıştırmadan önce seçim tutamacı ile genişletilmiştir: demo


+1 Her birinin kendi yeri olduğunu düşünsem de Nico Burns'den daha çok hoşuma gitti.
n0nag0n

5

İçin yapıştırılan metni temizlik ve yapıştırılan metin ile seçili metni değiştirme meselesi hayli abes:

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

Bunun işe yaradığı bir demo sayfası sağlayabilir misiniz? Ben denedim ve işe yaramıyor
vsync

5

Bu, onpaste olayını ve mutasyon gözlemcisini destekleyen tüm tarayıcılarda çalışmalıdır.

Bu çözüm yalnızca metni almanın ötesine geçiyor, aslında yapıştırılan içeriği bir öğeye yapıştırılmadan önce düzenlemenize izin veriyor.

İçerik düzeltilebilir, onpaste etkinliği (tüm büyük tarayıcılar tarafından desteklenir) ve mutasyon gözlemcileri (Chrome, Firefox ve IE11 + tarafından desteklenir) kullanılarak çalışır

Aşama 1

İçeriği düzenlenebilir bir HTML öğesi oluşturma

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

Adım 2

Javascript kodunuza aşağıdaki etkinliği ekleyin

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

Mutasyon gözlemcisi asenkron olarak çağırılacağı için pasteCallBack'i bağlamamız gerekiyor.

Aşama 3

Kodunuza aşağıdaki işlevi ekleyin

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

Kod ne yapar:

  1. Birisi macun olayını ctrl-v, contextmenu veya başka araçlar kullanarak tetikler
  2. Paste etkinliğinde, contenteditable özelliğine sahip yeni bir öğe oluşturulur (contenteditable özelliğine sahip bir öğe ayrıcalıklara sahiptir)
  3. Hedef elemanın düzeltme konumu kaydedilir.
  4. Odak yeni öğeye ayarlandı
  5. İçerik yeni öğeye yapıştırılır ve DOM'da oluşturulur.
  6. Mutasyon gözlemcisi bunu yakalar (dom ağacındaki ve içeriğindeki tüm değişiklikleri kaydeder). Sonra mutasyon olayını ateşler.
  7. Yapıştırılan içeriğin domu bir değişkene kopyalanır ve geri aramaya döner. Geçici eleman yok edilir.
  8. Geri arama klonlanmış DOM'u alır. Düzeltme işareti geri yüklendi. Hedefinize eklemeden önce bunu düzenleyebilirsiniz. öğesi. Bu örnekte, düzeltmeyi kaydetmek / geri yüklemek ve öğeye HTML yapıştırma için Tim Downs işlevlerini kullanıyorum.

Misal


Tim Down'a çok teşekkürler Cevap için bu bakın:

Yapıştırılan içeriği belgeye yapıştır etkinliğinde alma


4

Benim için çalışan bir çözüm, bir metin girişine yapıştırıyorsanız olayı yapıştırmak için olay dinleyicisi eklemektir. Yapıştır olayı, girişteki metin değişmeden önce gerçekleştiği için, yapıştırma işleyicimin içinde, içinde yapıştırımda olan giriş kutumdaki değişiklikleri kontrol ettiğim bir ertelenmiş işlev oluşturuyorum:

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

2
Korku maalesef iş tanımımızın bir parçası;) Ama katılıyorum, bu bir hack ve kesmek sadece SADECE diğer seçenekler tükendiğinde kullanılmalıdır.
Lex

4

Bu, artık Firefox'ta (yorum başına) işe yaradığını düşünmediğim ve Safari'de benim için olduğu gibi çalışmadığı için Nico'nun cevabı hakkında bir yorum yapmak için çok uzundu.

İlk olarak, şimdi doğrudan panodan okuyabiliyor gibi görünüyorsunuz. Gibi kod yerine:

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

kullanın:

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

Firefox çünkü typesbir olan bir alan DOMStringListuygulamıyor hangi test.

Sonraki Firefox, odak bir contenteditable=truealanda olmadığı sürece yapıştırmaya izin vermez .

Son olarak, odak sadece değil aynı zamanda bir de (veya belki de girdi) olmadığı sürece Firefox yapıştırmaya güvenilir bir şekilde izin vermez :textareacontenteditable=true

  • değil display:none
  • değil visibility:hidden
  • sıfır boyutlu değil

Ben bir JS VNC öykünücüsü üzerinde yapıştırıcı çalışması yapabilmek için metin alanını gizlemeye çalışıyordum (yani uzak bir istemciye gidiyordu ve textareaiçine yapıştırmak için aslında hiçbir şey yoktu ). Yukarıdaki metin alanını gizlemeye çalışırken, bazen çalıştığı yerlerde semptomlar verdi, ancak alanın odak kaybını kaybettiği ve düzgün bir şekilde geri kazanamayacağı için genellikle ikinci macunda (veya alanın aynı verileri iki kez yapışmasını önlemek için temizlendiğinde) başarısız oldu. rağmen focus(). Geldiğim çözüm, koymak z-order: -1000, yapmak display:none, 1px x 1px yapmak ve tüm renkleri şeffaf hale getirmekti. Yuck.

Safari'de yukarıdakilerin ikinci kısmı geçerlidir, yani textareaolmayan bir parçanız olması gerekir display:none.


Belki de tarayıcı oluşturma motorlarında çalışan geliştiricilerin, üzerinde çalıştıkları özellikler hakkında notlar yazmak için kullanabilecekleri belge sitelerinde bir sayfa veya alan olmalıdır. Örneğin, ekleyecekleri yapıştırma işlevi üzerinde çalıştılarsa, "görüntü yoksa, görünürlük gizlenir veya boyut sıfırdır" yapıştırma çalışmaz.
1.21 gigawatts


3

Basit çözüm:

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

2

Bu benim için çalıştı:

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

Bunu şu şekilde yapabilirsiniz:

yapıştırma öncesi ve sonrası olaylar için bu jQuery eklentisini kullanın:

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

Şimdi bu eklentiyi kullanabilirsiniz ;:

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

Açıklama

Öncelikle veri özelliği olarak mevcut tüm öğeler için bir uid ayarlayın.

Ardından, POST PASTE olayını tüm düğümlerle karşılaştırın. Karşılaştırma yaparak, yeni eklenen bir tanesini tanımlayabilirsiniz, çünkü bir kullanıcı kimliğine sahip olacaklar, daha sonra yeni oluşturulan öğelerden style / class / id niteliğini kaldırın, böylece eski biçimlendirmenizi koruyabilirsiniz.


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

1

Sadece tarayıcının içerik düzenlenebilir div'de her zamanki gibi yapışmasına izin verin ve ardından yapıştırma işleminden sonra özel metin stilleri için kullanılan yayılma elemanlarını metnin kendisiyle değiştirin. Bu, internet explorer ve denediğim diğer tarayıcılarda iyi çalışıyor gibi görünüyor ...

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

Bu çözüm, jQuery çalıştırdığınızı ve içerik düzenlenebilir div'larınızın hiçbirinde metin biçimlendirmesi istemediğinizi varsayar .

Artı tarafı süper basit olmasıdır.


Neden spanetiketlenmeli? Sorunun tüm etiketlerle ilgili olduğunu hayal ederdim.
Alexis Wilke

1

Bu çözüm html etiketinin yerini alır, basit ve çapraz tarayıcıdır; şu jsfiddle'ı kontrol edin: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , çekirdek kodu:

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

Dikkat: Bu çözüm '<< >>' gibi dizeleri filtreleyemediğinden, arka taraftaki xss filtresi hakkında biraz çalışmalısınız.


Sunucudaki XSS dosyalama işleminin JavaScript filtrenizin iyi bir iş yapıp yapmadığıyla ilgisi yoktur. Bilgisayar korsanları JS filtrelemenizin% 100'ünü yine de atlıyor.
Alexis Wilke

HTML'yi ayrıştırmak / dönüştürmek için asla Regex kullanmayın!
SubliemeSiem

0

Bu yukarıda yayınlanan mevcut bir kod ama IE için güncelledim, hata mevcut metin seçildiğinde ve yapıştırılan seçilen içeriği silmez. Bu aşağıdaki kod ile giderildi

selRange.deleteContents(); 

Aşağıdaki kodun tamamına bakın

$('[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);
    }        
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.