JavaScriptは貼り付けイベントでクリップボードデータを取得します(クロスブラウザー)


299

Webアプリケーションはどのようにして貼り付けイベントを検出し、貼り付けるデータを取得できますか?

テキストをリッチテキストエディターに貼り付ける前に、HTMLコンテンツを削除したいと思います。

後で貼り付けられた後のテキストのクリーニングは機能しますが、問題は以前のすべてのフォーマットが失われることです。たとえば、エディタで文章を書いて太字にできますが、新しいテキストを貼り付けると、すべての書式が失われます。貼り付けられたテキストのみをクリーンアップし、以前の書式設定はそのままにしておきます。

理想的には、このソリューションは最新のすべてのブラウザー(MSIE、Gecko、Chrome、Safariなど)で機能する必要があります。

MSIEにはclipboardData.getData()がありますが、他のブラウザで同様の機能を見つけることができなかったことに注意してください。


これらすべての回答は、テキストコンテンツを取得する方法を説明しています。画像コンテンツまたはファイルコンテンツを取得するには、さらに多くの作業が必要です。タイトルを「JavaScript get sanitized text clipboard data ...」に変更できるかもしれません
1.21ギガワット

1
ニコが言ったように:event.clipboardData.getData('Text')私のために働いた。
Andre Elrico

document.addEventListener('paste'...私にとってはうまくいきましたが、ユーザーがページの他の場所に貼り付けられるようにしたい場合、競合が発生しました。次にを試しましたmyCanvasElement.addEventListener('paste'...が、うまくいきませんでした。結局、私myCanvasElement.parentElement.addEventListener('paste'...はうまくいったと思いました。
ライアン

回答:


149

この回答を書いてから状況は変わりました。Firefoxのバージョン22でのサポートが追加されたため、すべての主要なブラウザーが貼り付けイベントでのクリップボードデータへのアクセスをサポートするようになりました。例については、Nico Burnsの回答を参照してください。

以前は、これは一般的にクロスブラウザの方法では不可能でした。理想は、貼り付けられたコンテンツをpasteイベント経由で取得できるようにすることです。これは最近のブラウザーでは可能ですが、一部の古いブラウザー(特にFirefox <22)ではできません。

古いブラウザーをサポートする必要がある場合、できることはかなり複雑で、Firefox 2 +、IE 5.5+、およびSafariやChromeなどのWebKitブラウザーで機能するちょっとしたハックです。TinyMCEとCKEditorの両方の最近のバージョンでは、この手法を使用しています。

  1. keypressイベントハンドラーを使用してctrl-v / shift-insイベントを検出する
  2. そのハンドラーで、現在のユーザー選択を保存し、textarea要素を画面外(たとえば-1000px)にドキュメントに追加し、offにしてtextarea designModeを呼び出すfocus()と、キャレットが移動し、効果的に貼り付けがリダイレクトされます
  3. イベントハンドラーで非常に短いタイマー(たとえば1ミリ秒)を設定して、textarea値を格納する別の関数を呼び出し、ドキュメントからtextareaを削除し、designModeオンに戻し、ユーザー選択を復元してテキストを貼り付けます。

これはキーボード貼り付けイベントでのみ機能し、コンテキストメニューや編集メニューからの貼り付けには機能しないことに注意してください。pasteイベントが発生する頃には、キャレットをtextareaにリダイレクトするには遅すぎます(少なくとも一部のブラウザでは)。

万一、Firefox 2をサポートする必要がある場合は、そのブラウザーでWYSIWYGエディターのiframeのドキュメントではなく、親ドキュメントにtextareaを配置する必要があることに注意してください。


1
わあ、ありがとう!非常に洗練されたハックのようですが;-)特にステップ3で、designModeとselectionについてもう少し詳しく説明していただけますか?どうもありがとう!
アレックス

5
私はあなたがそれを尋ねるだろうという恐ろしい気持ちを持っていました。私が言うように、それはかなり複雑です:関係するすべての問題の概要を説明する時間がないので、TinyMCEまたはCKEditorのソースを確認することをお勧めします。簡単に説明するdesignModeと、のブール型プロパティでdocument、の場合はページ全体を編集可能にしtrueます。WYSIWYGエディターは通常designMode、編集可能なペインとしてonのiframeを使用します。ユーザー選択の保存と復元は、コンテンツをエディターに貼り付けるのと同じように、IEと他のブラウザーで別の方法で行われます。TextRangeIEとRange他のブラウザでを取得する必要があります。
Tim Down

6
@Samuel:pasteイベントを使用してそれを検出できますが、貼り付けを別の要素にリダイレクトするのには一般的に遅すぎるため、このハックは機能しません。ほとんどのエディターのフォールバックは、ユーザーが貼り付けるダイアログを表示することです。
ティムダウン

6
これに関するいくつかの情報:Firefoxでは、pasteイベント内の別の要素にフォーカスを移動することはできませんが、要素の内容をクリアして(後で復元できるように変数に保存して)おくことができます。このコンテナーがdiv(おそらくiframeそれでも機能する)である場合は、通常のdomメソッドを使用して貼り付けたコンテンツを循環するか、を使用して文字列として取得できますinnerHTML。その後、の以前のコンテンツを復元し、div好きなコンテンツを挿入できます。ああ、そしてあなたは上記と同じタイマーハックを使わなければなりません。TinyMCEがこれを行わないことに驚いています...
Nico Burns

8
@ResistDesign:私は同意しません-それは賢明なAPIの欠如を埋め合わせる上品で複雑な方法です。一部のブラウザでは制限された方法で可能である、貼り付けイベントから直接貼り付けられたコンテンツを取得できるようにした方がよいでしょう。
Tim Down

318

解決策#1(プレーンテキストのみ、Firefox 22以降が必要)

IE6 +、FF 22 +、Chrome、Safari、Edgeで動作します(IE9 +でのみテストされていますが、下位バージョンでも動作するはずです)

HTMLまたはFirefox <= 22の貼り付けのサポートが必要な場合は、ソリューション#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/

このソリューションでは、関数にパラメーター 'Text'を使用していますがgetData、これは非標準です。ただし、執筆時点ではすべてのブラウザで動作します。


ソリューション#2(HTMLおよびFirefox <= 22で動作)

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/

説明

onpasteイベントがdivありhandlePaste:それに取り付けられており、単一の引数を渡された関数eventペーストイベントのオブジェクトを。私たちにとって特に興味深いのは、clipboardDataIE以外のブラウザでクリップボードにアクセスできるようにするこのイベントのプロパティです。IEでは同等ですがwindow.clipboardData、APIは少し異なります。

以下のリソースセクションを参照してください。


handlepaste機能:

この関数には2つのブランチがあります。

1つ目は、その存在を確認し、そのプロパティに 'text / html'が含まれているevent.clipboardDataかどうかを確認しますtypes(メソッドを使用して確認される、またはメソッドを使用して確認される文字列のtypesいずれか)。これらの条件がすべて満たされた場合は、ソリューション#1と同様に処理します。ただし、「text / plain」ではなく「text / html」を使用します。これは現在、ChromeおよびFirefox 22以降で動作します。DOMStringListcontainsindexOf

このメソッドがサポートされていない場合(他のすべてのブラウザ)、次に

  1. 要素のコンテンツを DocumentFragment
  2. 要素を空にする
  3. waitForPastedData関数を呼び出す

waitforpastedata機能:

この関数はまず、ペーストされたデータをポーリングします(20ミリ秒に1回)。これは、すぐには表示されないために必要です。データが表示されたら:

  1. 編集可能なdiv(現在は貼り付けられたデータ)のinnerHTMLを変数に保存します
  2. DocumentFragmentに保存されたコンテンツを復元します
  3. 取得したデータを使用して「processPaste」関数を呼び出します

processpaste機能:

貼り付けたデータで任意のことを行います。この場合、データに警告するだけで、好きなことができます。おそらく、貼り付けたデータを何らかのデータ消毒プロセスで実行する必要があります。


カーソル位置の保存と復元

実際の状況では、前に選択を保存し、後で復元したいと思うでしょう(contentEditable <div>にカーソル位置を設定します)。次に、ユーザーが貼り付け操作を開始したときにカーソルがあった位置に貼り付けたデータを挿入できます。

リソース:

DocumentFragmentの使用を提案するTim Downに感謝し、clipboardData.typesの文字列の代わりにDOMStringListを使用することによりFirefoxでエラーをキャッチする


4
面白い。私は過去にこれを試したことがあると思いました、そしてそれはいくつかのブラウザではうまくいかなかったが、あなたが正しいと確信しています。いくつかの理由から、既存のコンテンツをDocumentFragmentを使用するよりもに移動するinnerHTMLことをお勧めします。innerHTML次に、保存と復元では、以前のDOMと同じコピーが作成されるとは限りません。3番目に、Rangeマーカー要素を追加したり、テキストオフセットを計算したりするのではなく、選択を保存できます(これは、を使用した場合に行う必要があることですinnerHTML)。
ティムダウン、

3
確かにコンテンツのフラッシュがありません(FONC?)。これは、貼り付けられたコンテンツの処理に時間がかかる場合は明らかに悪化します。ところで、なぜDocumentFragmentIEを苦労して抽出するのですか?Rangeを使用extractContents()してそれを行う場合を除いて、他のブラウザーと同じです。これは、いずれの場合でも代替よりも簡潔ではありません。私はあなたのテクニックの例を実装しました。Rangyを使用して、ブラウザー間で物事をうまく均一に保ちます:jsfiddle.net/bQeWC/4
Tim Down

1
@Martin:コメントで投稿したjsFiddleデモが役立つかもしれません。
Tim Down

1
Windows版のFirefox 28(少なくとも)では動作しないようです。それはwaitforpastedata機能から外れることはありません
Oliboy50

1
参考:Edgeはtext/html、W3CクリップボードAPIを使用したMIMEタイプのデータの読み取りをサポートしています。以前は、このような試みは例外をスローしていました。そのため、Edgeのこの回避策/ハックはもう必要ありません。
ジェニー・オライリー

130

シンプルなバージョン:

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

使用する clipboardData

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

Edge、Firefox、Chrome、Safari、Operaでテスト済み。

⚠Document.execCommand ()は廃止されました


注:サーバーサイドでの入出力もチェックすることを忘れないでください(PHPストリップタグなど)。


4
これは非常にうまく機能しますが、IE
Eric Wood

1
IEで別の方法でクリップボードデータにアクセスできるようですが、IEを検出した場合、プロンプトフォールバックの代わりにそのデータを使用できます:msdn.microsoft.com/en-us/library/ie/ms535220(v = vs.85).aspx
Andrew

4
これまでに見つかったブラウザ間の最良の回答。IEとその完璧なコードを追加するだけです。
Arturo

6
これはIEで動作します(ああ、甘い、逆のIE)window.clipboardData.getData('Text');
Benjineer 2015年

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

26

ライブデモ

Chrome / FF / IE11でテスト済み

Chrome / IEの煩わしさは、これらのブラウザーが<div>新しい行ごとに要素を追加することです。そここれについての記事で、ここでは、設定することで固定することができのcontentEditableする要素をdisplay:inline-block

強調表示されたHTMLをいくつか選択して、ここに貼り付けます。

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
プレーンテキストとして貼り付ける機能が必要でした。IE9とIE10でテストされ、正常に動作します。言うまでもなく、これは主要なブラウザでも動作します...ありがとう。
Savas Vedova

2
コードにバグが含まれています:if(e.originalEvent.clipboardData)は、その時点でe.originalEventが存在するかどうかわからないため、NPEを引き起こす可能性があります
Sebastian

15

ここでは、画面外のテキスト領域を使用して、ティムダウンズの提案のコンセプトの小さな証明を作成しました。そしてここにコードがあります:

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

コード全体を1つのhtmlファイルにコピーして貼り付け、ドキュメントの任意の場所にあるクリップボードからテキストを(ctrl-vを使用して)貼り付けてみてください。

IE9とFirefox、Chrome、Operaの新しいバージョンでテストしました。かなりうまくいきます。また、この機能を実行するために彼が好むキーの組み合わせを使用できるのも良いことです。もちろん、jQueryソースを含めることを忘れないでください。

このコードを自由に使用してください。改善や問題が発生した場合は、投稿してください。また、私はJavaScript開発者ではないため、何かを見落とした可能性があることに注意してください(=>独自のテストを実行してください)。


Macはctrl-vで貼り付けません。cmd-vを使用します。したがって、ctrlKey = 91を17ではなく91に設定します
Jeremy T

2
それともそれは常に91ではありません:stackoverflow.com/questions/3834175/...は かかわらず、私はあなたのために、ちょうどe.ctrlKeyまたはe.metaKey私が思うチェックし、すべてのことをかなり確信してjQueryのハンドルです。
ジェレミーT

3
e.ctrlKeyまたはe.metaKeyは、jQueryではなくJavaScript DOMの一部です:developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne

2
これは右クリックと貼り付けでは機能しないと思います。多くの人がそのアプローチをとっています。
Eric Wood

10

l2aelba anwserに基づいてます。これはFF、Safari、Chrome、IE(8、9、10、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);
            }
        }
    });

IEに貼り付けるときに新しい行を保持する方法はありますか?
ステイシー2014年

10

これはsetTimeout()を使用しません。

この素晴らしい記事を使用して、クロスブラウザーサポートを実現しました。

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

このコードは貼り付け前に選択ハンドルで拡張されています: デモ


+1私はこれをニコ・バーンズよりも好きですが、それぞれに独自の場所があると思います。
n0nag0n 2013年

5

以下のために貼り付けたテキストを清掃し、貼り付けたテキストで現在選択されているテキストを置き換える事は非常に簡単です:

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

これが機能するデモページを提供できますか?私は試してみましたが、
うまくいき

5

これは、onpasteイベントとミューテーションオブザーバーをサポートするすべてのブラウザーで機能します。

このソリューションは、テキストのみを取得するだけではなく、実際に要素に貼り付ける前に、貼り付けたコンテンツを編集できるようにします。

contenteditable、onpasteイベント(すべての主要なブラウザーでサポート)およびミューテーションオブザーバー(Chrome、Firefox、IE11 +でサポート)を使用して機能します。

ステップ1

contenteditableでHTML要素を作成する

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

ステップ2

JavaScriptコードに次のイベントを追加します

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

ミューテーションオブザーバーは非同期で呼び出されるため、pasteCallBackをバインドする必要があります。

ステップ3

次の関数をコードに追加します

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

コードの機能:

  1. 誰かがctrl-v、contextmenu、またはその他の手段を使用して貼り付けイベントを起動します
  2. 貼り付けイベントでは、contenteditableを持つ新しい要素が作成されます(contenteditableを持つ要素には昇格された特権があります)
  3. ターゲット要素のキャレット位置が保存されます。
  4. フォーカスは新しい要素に設定されます
  5. コンテンツが新しい要素に貼り付けられ、DOMにレンダリングされます。
  6. ミューテーションオブザーバーはこれをキャッチします(domツリーとコンテンツへのすべての変更を登録します)。次に、mutationイベントを発生させます。
  7. 貼り付けられたコンテンツのdomが変数に複製され、コールバックに返されます。一時的な要素は破棄されます。
  8. コールバックは複製されたDOMを受け取ります。キャレットが復元されます。これをターゲットに追加する前に編集できます。素子。この例では、キャレットの保存/復元と要素へのHTMLの貼り付けにTim Downs関数を使用しています。


Tim Downに感謝し ます。回答はこの投稿を参照してください。

貼り付けイベントでドキュメントに貼り付けられたコンテンツを取得します


4

私にとってうまくいく解決策は、テキスト入力に貼り付けている場合、貼り付けイベントにイベントリスナーを追加することです。入力イベントのテキストが変更される前に貼り付けイベントが発生するため、貼り付けハンドラー内で、貼り付け時に発生した入力ボックスの変更をチェックする遅延関数を作成します。

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

2
ホラーは残念ながら私たちの仕事の説明の一部です;)しかし私は同意します、これはハックであり、ハックは他のすべてのオプションを使い果たした場合にのみ使用されるべきです。
Lex

4

これは、Nicoの回答に対するコメントには長すぎたため、Firefoxでは(コメントごとに)機能するとは思われず、Safariでもそのままでは機能しませんでした。

まず、クリップボードから直接読み取ることができるようになりました。次のようなコードではなく:

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

使用する:

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にはを実装していないtypesフィールドがあるためです。DOMStringListtest

次のFirefoxは、フォーカスがcontenteditable=trueフィールドにない限り、貼り付けを許可しません。

最後に、Firefoxは確実に貼り付けを許可しません、フォーカスがtextarea(またはおそらく入力)にある場合を除いて、ませんcontenteditable=true

  • ない display:none
  • ない visibility:hidden
  • サイズがゼロではない

テキストフィールドを非表示にして、JS VNCエミュレーターを介して貼り付けを実行できるようにしました(つまり、リモートクライアントtextareaに送られ、実際に貼り付けるものなどありませんでした)。上記のテキストフィールドを非表示にしようとすると、ときどき動作するという症状が出ましたが、通常は2番目の貼り付けに失敗しました(またはフィールドがクリアされて同じデータが2回貼り付けられないようにした場合)。それにもかかわらずfocus()。私が思いついた解決策はz-order: -1000、それをに置いて作ることでしたdisplay:none 1px x 1pxにし、すべての色を透明に設定することでした。ああ。

サファリでは、あなたの上記の2番目の部分は、つまり、あなたが持っている必要があり、適用textareaされていないがdisplay:none


たぶん、ブラウザのレンダリングエンジンで作業する開発者は、作業している機能に関するメモを書くために使用できるドキュメントサイトにページまたはスペースが必要です。たとえば、彼らが追加するペースト機能に取り組んだ場合、「表示が表示されない、表示が非表示、またはサイズがゼロの場合、ペーストは機能しません」。
1.21ギガワット

3

最初に頭に浮かぶのは、Googleのクロージャライブラリのペーストハンドラです http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html


これは貼り付けイベントを安全に検出しているようですが、貼り付けられたコンテンツをキャッチ/返すことができないようです?
Alex

@Alex:正解です。これは、リッチテキストエディターではなく、textareasでのみ機能します。
Tim Down

3

簡単な解決策:

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

2

これは私のために働きました:

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

この方法でこれを行うことができます:

このjQueryプラグインを貼り付け前および貼り付けイベントに使用します。

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

これでこのプラグインを使用できます;:

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

説明

最初に、既存のすべての要素のuidをデータ属性として設定します。

次に、すべてのノードのPOST PASTEイベントを比較します。比較することで、uidがあるために新しく挿入されたものを識別し、新しく作成された要素からstyle / class / id属性を削除するだけで、古いフォーマットを維持できます。



1

ブラウザーを通常どおりコンテンツの編集可能なdivに貼り付け、貼り付け後、カスタムテキストスタイルに使用されるすべてのスパン要素をテキスト自体と入れ替えます。これはInternet Explorerや他のブラウザで問題なく動作するようです...

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

このソリューションは、jQueryを実行していて、コンテンツの編集可能なdivでテキストの書式設定を望まないこと前提としています。

プラス面は、それが非常にシンプルであるということです。


spanタグを付ける理由 問題はすべてのタグに関するものだったと思います。
Alexis Wilke、

1

このソリューションはhtmlタグを置き換えるものであり、シンプルでクロスブラウザです。このjsfiddleを確認してください:http ://jsfiddle.net/tomwan/cbp1u2cx/1/ 、コアコード:

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

注意:このソリューションでは '<< >>'のような文字列をフィルタリングできないため、裏面のxssフィルターについていくつかの作業を行う必要があります。


サーバー上のXSSファイルリングは、JavaScriptフィルターが適切に機能するかどうかには関係ありません。ハッカーはとにかくJSフィルタリングの100%をバイパスします。
Alexis Wilke、

正規表現を使用してHTMLを解析/変換しないでください!
SubliemeSiem

0

これは上記に掲載された既存のコードですが、IE向けに更新しました。既存のテキストを選択して貼り付けると、選択したコンテンツが削除されないというバグがありました。これは以下のコードで修正されました

selRange.deleteContents(); 

以下の完全なコードを参照してください

$('[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);
    }        
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.