JavaScript nhận dữ liệu clipboard về sự kiện dán (Trình duyệt chéo)


299

Làm thế nào một ứng dụng web có thể phát hiện một sự kiện dán và lấy dữ liệu được dán?

Tôi muốn xóa nội dung HTML trước khi văn bản được dán vào trình soạn thảo văn bản phong phú.

Làm sạch văn bản sau khi được dán sau đó hoạt động, nhưng vấn đề là tất cả các định dạng trước đó bị mất. Ví dụ, tôi có thể viết một câu trong trình chỉnh sửa và in đậm, nhưng khi tôi dán văn bản mới, tất cả định dạng sẽ bị mất. Tôi muốn làm sạch chỉ văn bản được dán và không để lại bất kỳ định dạng nào trước đó.

Lý tưởng nhất, giải pháp nên hoạt động trên tất cả các trình duyệt hiện đại (ví dụ: MSIE, Gecko, Chrome và Safari).

Lưu ý rằng MSIE có clipboardData.getData(), nhưng tôi không thể tìm thấy chức năng tương tự cho các trình duyệt khác.


Tất cả những câu trả lời giải thích làm thế nào để có được nội dung văn bản. Lấy nội dung hình ảnh hoặc nội dung tập tin đòi hỏi nhiều công việc hơn. Có lẽ chúng ta có thể thay đổi tiêu đề thành "JavaScript lấy dữ liệu bảng tạm văn bản được khử trùng ..."
1,21 gigawatt

1
như nico đã nói: event.clipboardData.getData('Text')làm việc cho tôi
Andre Elrico

document.addEventListener('paste'...làm việc cho tôi nhưng gây ra xung đột nếu người dùng muốn có thể dán ở nơi khác trên trang. Sau đó, tôi đã cố gắng myCanvasElement.addEventListener('paste'..., nhưng điều đó đã không làm việc. Cuối cùng, tôi đã tìm ra myCanvasElement.parentElement.addEventListener('paste'...làm việc.
Ryan

Câu trả lời:


149

Tình hình đã thay đổi kể từ khi viết câu trả lời này: bây giờ Firefox đã thêm hỗ trợ trong phiên bản 22, tất cả các trình duyệt chính hiện hỗ trợ truy cập dữ liệu clipboard trong một sự kiện dán. Xem câu trả lời của Nico Burns cho một ví dụ.

Trong quá khứ, điều này thường không khả thi theo cách đa trình duyệt. Lý tưởng là có thể có được nội dung được dán thông qua pastesự kiện này, điều này có thể có trong các trình duyệt gần đây nhưng không có trong một số trình duyệt cũ hơn (đặc biệt là Firefox <22).

Khi bạn cần hỗ trợ các trình duyệt cũ hơn, những gì bạn có thể làm có liên quan và một chút hack sẽ hoạt động trong các trình duyệt Firefox 2+, IE 5.5+ và WebKit như Safari hoặc Chrome. Các phiên bản gần đây của cả TinyMCE và CKEditor đều sử dụng kỹ thuật này:

  1. Phát hiện sự kiện ctrl-v / shift-in bằng trình xử lý sự kiện nhấn phím
  2. Trong trình xử lý đó, lưu lựa chọn người dùng hiện tại, thêm phần tử textarea ngoài màn hình (giả sử ở bên trái -1000px) vào tài liệu, designModetắt và gọi focus()trên textarea, do đó di chuyển dấu mũ và chuyển hướng hiệu quả dán
  3. Đặt bộ hẹn giờ rất ngắn (giả sử 1 mili giây) trong trình xử lý sự kiện để gọi một chức năng khác lưu trữ giá trị textarea, xóa textarea khỏi tài liệu, bật designModelại, khôi phục lựa chọn của người dùng và dán văn bản vào.

Lưu ý rằng điều này sẽ chỉ hoạt động cho các sự kiện dán bàn phím và không dán từ ngữ cảnh hoặc chỉnh sửa menu. Vào thời điểm sự kiện dán diễn ra, đã quá muộn để chuyển hướng dấu mũ vào vùng văn bản (ít nhất là trong một số trình duyệt).

Trong trường hợp không chắc là bạn cần hỗ trợ Firefox 2, lưu ý rằng bạn sẽ cần đặt textarea vào tài liệu gốc thay vì tài liệu iframe của trình soạn thảo WYSIWYG trong trình duyệt đó.


1
Wow, cảm ơn vì điều đó! Có vẻ như là một hack rất tinh vi mặc dù ;-) Bạn có thể vui lòng mô tả designMode và lựa chọn đó một chút nữa, đặc biệt là trong bước 3? Cảm ơn rất nhiều!
Alex

5
Tôi đã có một cảm giác khủng khiếp bạn hỏi điều đó. Như tôi nói, nó khá liên quan: Tôi khuyên bạn nên xem nguồn của TinyMCE hoặc CKEditor, vì tôi không có thời gian để phác thảo tất cả các vấn đề liên quan. Tóm lại, mặc dù, designModelà một thuộc tính Boolean của documentvà làm cho toàn bộ trang có thể chỉnh sửa khi true. Các trình soạn thảo WYSIWYG thường sử dụng iframe với khung có designModethể chỉnh sửa. Lưu và khôi phục lựa chọn người dùng được thực hiện theo một cách trong IE và một cách khác trong các trình duyệt khác, như dán nội dung vào trình chỉnh sửa. Bạn cần có được một TextRangeIE và một Rangetrong các trình duyệt khác.
Tim Down

6
@Samuel: Bạn có thể phát hiện pastesự kiện bằng cách sử dụng sự kiện nhưng thường thì đã quá muộn để chuyển hướng dán vào một yếu tố khác, vì vậy bản hack này sẽ không hoạt động. Dự phòng trong hầu hết các trình soạn thảo là hiển thị hộp thoại để người dùng dán vào.
Tim Down

6
Một số thông tin khác về điều này: Firefox sẽ không cho phép bạn chuyển trọng tâm sang một yếu tố khác trong pastesự kiện, tuy nhiên nó sẽ cho phép bạn xóa nội dung của thành phần đó (và lưu nó vào một biến để bạn có thể khôi phục lại sau). Nếu thùng chứa này là một div(nó có thể cũng hoạt động cho một cái iframequá) thì bạn có thể chuyển qua nội dung được dán bằng các phương thức dom bình thường hoặc lấy nó làm chuỗi sử dụng innerHTML. Sau đó, bạn có thể khôi phục nội dung trước đó divvà chèn bất kỳ nội dung nào bạn thích. Ồ, và bạn phải sử dụng hack hẹn giờ giống như trên. Tôi ngạc nhiên TinyMCE không làm điều này ...
Nico Burns

8
@ResistDesign: Tôi không đồng ý - đó là một cách không phù hợp và phức tạp để bù đắp cho việc thiếu API hợp lý. Sẽ tốt hơn nếu có thể lấy nội dung được dán trực tiếp từ sự kiện dán, điều này có thể theo một cách hạn chế trong một số trình duyệt .
Tim Down

318

Giải pháp số 1 (Chỉ văn bản thuần túy và yêu cầu Firefox 22+)

Hoạt động cho IE6 +, FF 22+, Chrome, Safari, Edge (Chỉ được thử nghiệm trong IE9 +, nhưng nên hoạt động cho các phiên bản thấp hơn)

Nếu bạn cần hỗ trợ để dán HTML hoặc Firefox <= 22, hãy xem Giải pháp # 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);

Mã thông báo: https://jsfiddle.net/swL8ftLs/12/

Lưu ý rằng giải pháp này sử dụng tham số 'Văn bản' cho getDatachức năng, không chuẩn. Tuy nhiên, nó hoạt động trong tất cả các trình duyệt tại thời điểm viết.


Giải pháp số 2 (HTML và hoạt động cho Firefox <= 22)

Đã thử nghiệm trong 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/

Giải trình

Sự onpastekiện của hàm divhandlePastechức năng gắn liền với nó và truyền một đối số duy nhất: eventđối tượng cho sự kiện dán. Quan tâm đặc biệt đối với chúng tôi là clipboardDatatài sản của sự kiện này cho phép truy cập clipboard trong các trình duyệt không có nghĩa là. Trong IE tương đương window.clipboardData, mặc dù điều này có API hơi khác.

Xem phần tài nguyên dưới đây.


Các handlepastechức năng:

Hàm này có hai nhánh.

Lần đầu tiên kiểm tra sự tồn tại của event.clipboardDatavà kiểm tra xem thuộc typestính của nó có chứa 'text / html' không ( typescó thể là một DOMStringListtrong đó được kiểm tra bằng containsphương thức hoặc một chuỗi được kiểm tra bằng indexOfphương thức). Nếu tất cả các điều kiện này được đáp ứng, thì chúng tôi sẽ tiến hành như trong giải pháp số 1, ngoại trừ với 'text / html' thay vì 'text / plain'. Điều này hiện đang hoạt động trong Chrome và Firefox 22+.

Nếu phương pháp này không được hỗ trợ (tất cả các trình duyệt khác), thì chúng tôi

  1. Lưu nội dung của phần tử vào một DocumentFragment
  2. Làm trống phần tử
  3. Gọi waitForPastedDatahàm

Các waitforpastedatachức năng:

Chức năng này lần đầu tiên thăm dò dữ liệu đã dán (một lần trong 20ms), điều này là cần thiết bởi vì nó không xuất hiện ngay lập tức. Khi dữ liệu đã xuất hiện:

  1. Lưu phần bên trong của div có thể chỉnh sửa (hiện là dữ liệu đã dán) vào một biến
  2. Khôi phục nội dung được lưu trong DocumentFragment
  3. Gọi hàm 'processPaste' với dữ liệu được truy xuất

Các processpastechức năng:

Có những thứ tùy ý với dữ liệu dán. Trong trường hợp này, chúng tôi chỉ cảnh báo dữ liệu, bạn có thể làm bất cứ điều gì bạn muốn. Bạn có thể sẽ muốn chạy dữ liệu đã dán thông qua một số loại quy trình vệ sinh dữ liệu.


Lưu và khôi phục vị trí con trỏ

Trong một tình huống bão hòa thực sự, có lẽ bạn sẽ muốn lưu lựa chọn trước đó và khôi phục nó sau đó ( Đặt vị trí con trỏ trên contentEditable <div> ). Sau đó, bạn có thể chèn dữ liệu đã dán vào vị trí con trỏ đang ở khi người dùng bắt đầu hành động dán.

Tài nguyên:

Cảm ơn Tim Down đã đề xuất sử dụng DocumentFragment và bỏ qua việc bắt lỗi trong Firefox do sử dụng DOMStringList thay vì một chuỗi cho clipboardData.types


4
Hấp dẫn. Tôi nghĩ rằng tôi đã thử điều này trong quá khứ và nó đã không hoạt động trong một số trình duyệt, nhưng tôi chắc chắn rằng bạn đã đúng. Tôi chắc chắn sẽ thích chuyển nội dung hiện có thành một DocumentFragmentthay vì sử dụng innerHTMLvì một số lý do: đầu tiên, bạn giữ bất kỳ trình xử lý sự kiện hiện có nào; thứ hai, lưu và khôi phục innerHTMLkhông được đảm bảo để tạo một bản sao giống hệt của DOM trước đó; thứ ba, sau đó bạn có thể lưu lựa chọn Rangethay vì phải loay hoay với việc thêm các yếu tố đánh dấu hoặc tính toán bù đắp văn bản (đó là những gì bạn phải làm nếu bạn sử dụng innerHTML).
Tim Down

3
Thực sự có một đèn flash không có nội dung (FONC?), Điều này rõ ràng sẽ tồi tệ hơn nếu việc xử lý nội dung được dán mất một thời gian. Btw, tại sao trích xuất DocumentFragmentmột nỗi đau trong IE? Nó giống như trong các trình duyệt khác, trừ khi bạn sử dụng Phạm vi và extractContents()để thực hiện, điều này không ngắn gọn hơn so với giải pháp thay thế trong mọi trường hợp. Tôi đã triển khai một ví dụ về kỹ thuật của bạn, sử dụng Rangy để giữ mọi thứ tốt đẹp và thống nhất trên các trình duyệt: jsfiddle.net/bQeWC/4 .
Tim Down

1
@Martin: Bản demo jsFiddle tôi đã đăng trong các bình luận có thể giúp ích.
Tim Down

1
Có vẻ như nó không hoạt động nữa trên Firefox 28 (ít nhất là) cho Windows. Nó không bao giờ tắt waitforpastedatachức năng
Oliboy50

1
FYI: Edge hiện hỗ trợ đọc dữ liệu với MIME-Type text/htmlbằng API Clipboard W3C. Trong quá khứ, một nỗ lực như vậy sẽ ném một ngoại lệ. Vì vậy, người ta không còn cần cách giải quyết / hack này cho Edge.
Jenny O'Reilly

130

Phiên bản đơn giản:

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

Sử dụng clipboardData

Bản trình diễn: http://jsbin.com/nozifexasu/edit?js,output

Đã thử nghiệm Edge, Firefox, Chrome, Safari, Opera.

Document.execCommand () là lỗi thời bây giờ.


Lưu ý: Hãy nhớ kiểm tra đầu vào / đầu ra ở phía máy chủ (như thẻ dải PHP )


4
Điều này hoạt động rất tốt, nhưng không có phiên bản IE nào cho phép truy cập vào clipboardData từ sự kiện :( Giải pháp tuyệt vời, tuy nhiên, điều này sẽ cao hơn!
Eric Wood

1
Dường như bạn có thể truy cập dữ liệu clipboard trong IE theo một cách khác, vì vậy nếu bạn phát hiện ra IE, bạn có thể sử dụng dữ liệu đó thay vì dự phòng nhắc nhở: msdn.microsoft.com/en-us/l Library / i / ms535220 (v = so với 85) .aspx
Andrew

4
câu trả lời trình duyệt chéo tốt nhất được tìm thấy cho đến nay. chỉ cần thêm mã cho IE và nó hoàn hảo.
Arturo

6
Điều này hoạt động trong IE (ah, ngọt ngào, trái ngược với IE)window.clipboardData.getData('Text');
Stewineer

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

Bản thử trực tiếp

Đã thử nghiệm trên Chrome / FF / IE11

Có một điều khó chịu của Chrome / IE đó là các trình duyệt này thêm <div>yếu tố cho mỗi dòng mới. Có một bài về vấn đề này ở đây và nó có thể được cố định bằng cách thiết lập contenteditable yếu tố để đượcdisplay:inline-block

Chọn một số HTML được tô sáng và dán nó vào đây:

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
Tôi cần một dán như tính năng văn bản đơn giản. Đã thử nghiệm trên IE9 và IE10 và hoạt động rất tốt. Không cần phải đề cập rằng nó cũng hoạt động trên các trình duyệt lớn ... Cảm ơn.
Savas Vedova

2
Mã của bạn có lỗi: if (e.origenEvent.clipboardData) có thể gây ra NPE như bạn không biết nếu e.origenEvent tồn tại vào thời điểm đó
Sebastian

15

Tôi đã viết một bằng chứng nhỏ về khái niệm cho đề xuất của Tim Downs ở đây với textarea ngoài màn hình. Và đây là đoạn mã:

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

Chỉ cần sao chép và dán toàn bộ mã vào một tệp html và cố gắng dán (sử dụng ctrl-v) văn bản từ bảng tạm bất cứ nơi nào trên tài liệu.

Tôi đã thử nghiệm nó trong IE9 và các phiên bản mới của Firefox, Chrome và Opera. Hoạt động khá tốt. Ngoài ra, thật tốt khi người ta có thể sử dụng bất kỳ tổ hợp phím nào mà anh ta thích để thực hiện chức năng này. Tất nhiên đừng quên bao gồm các nguồn jQuery.

Vui lòng sử dụng mã này và nếu bạn đi kèm với một số cải tiến hoặc vấn đề xin vui lòng gửi lại. Cũng lưu ý rằng tôi không phải là nhà phát triển Javascript nên tôi có thể đã bỏ lỡ điều gì đó (=> làm chứng thực của riêng bạn).


Máy Mac không dán bằng ctrl-v, họ sử dụng cmd-v. Vì vậy, đặt ctrlKey = 91 thay vì 17
Jeremy T

2
Hoặc có thể không phải lúc nào cũng là 91: stackoverflow.com/questions/3834175/ Bất kể, tôi khá chắc chắn jQuery xử lý tất cả những điều đó cho bạn, chỉ cần kiểm tra e.ctrlKey hoặc e.metaKey.
Jeremy T

3
e.ctrlKey hoặc e.metaKey là một phần của DOM DOM, không phải jQuery: developer.mozilla.org/en-US/docs/Web/API/PalEvent
rvighne

2
Tôi không nghĩ rằng nó hoạt động để nhấp chuột phải và dán. Rất nhiều người thực hiện phương pháp đó.
Eric Wood

10

Dựa trên l2aelba anwser. Điều này đã được thử nghiệm trên FF, Safari, Chrome, IE (8,9,10 và 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);
            }
        }
    });

Có cách nào để bảo tồn các dòng mới khi dán vào IE không?
Ở lại

10

Cái này không sử dụng bất kỳ setTimeout ().

Tôi đã sử dụng này bài viết tuyệt vời để đạt được hỗ trợ trình duyệt chéo.

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

Mã này được mở rộng với xử lý lựa chọn trước khi dán: demo


+1 Tôi thích cái này hơn so với Nico Burns, mặc dù tôi nghĩ mỗi người có một vị trí riêng.
n0nag0n

5

Để làm sạch văn bản đã dánthay thế văn bản hiện được chọn bằng văn bản đã dán , vấn đề này khá nhỏ:

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

Bạn có thể cung cấp một trang demo nơi điều này hoạt động? Tôi đã thử nó và nó không hoạt động
vsync

5

Điều này sẽ hoạt động trên tất cả các trình duyệt hỗ trợ sự kiện onpaste và người quan sát đột biến.

Giải pháp này đi một bước ngoài việc chỉ nhận văn bản, nó thực sự cho phép bạn chỉnh sửa nội dung được dán trước khi nó được dán vào một yếu tố.

Nó hoạt động bằng cách sử dụng nội dung, sự kiện onpaste (được hỗ trợ bởi tất cả các trình duyệt chính) vi quan sát đột biến (được Chrome, Firefox và IE11 + hỗ trợ)

bước 1

Tạo một phần tử HTML với nội dung

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

bước 2

Trong mã Javascript của bạn thêm sự kiện sau

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

Chúng ta cần liên kết pasteCallBack, vì trình quan sát đột biến sẽ được gọi là không đồng bộ.

bước 3

Thêm chức năng sau vào mã của bạn

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

Những gì mã làm:

  1. Ai đó kích hoạt sự kiện dán bằng cách sử dụng ctrl-v, bối cảnh hoặc các phương tiện khác
  2. Trong sự kiện dán, một phần tử mới có thể tạo nội dung được tạo ra (một phần tử có thể di chuyển được có đặc quyền nâng cao)
  3. Vị trí dấu mũ của phần tử đích được lưu.
  4. Trọng tâm được đặt thành phần tử mới
  5. Nội dung được dán vào phần tử mới và được hiển thị trong DOM.
  6. Người quan sát đột biến nắm bắt được điều này (nó đăng ký tất cả các thay đổi đối với cây dom và nội dung). Sau đó, bắn sự kiện đột biến.
  7. Sự thống trị của nội dung được dán sẽ được nhân bản thành một biến và quay trở lại cuộc gọi lại. Các yếu tố tạm thời bị phá hủy.
  8. Cuộc gọi lại nhận được DOM nhân bản. Các dấu mũ được khôi phục. Bạn có thể chỉnh sửa nó trước khi bạn thêm nó vào mục tiêu của bạn. thành phần. Trong ví dụ này, tôi đang sử dụng các hàm Tim Downs để lưu / khôi phục dấu mũ và dán HTML vào phần tử.

Thí dụ


Rất cám ơn Tim Down Xem bài đăng này để biết câu trả lời:

Nhận nội dung được dán trên tài liệu về sự kiện dán


4

Giải pháp phù hợp với tôi là thêm trình lắng nghe sự kiện để dán sự kiện nếu bạn đang dán vào một kiểu nhập văn bản. Vì sự kiện dán xảy ra trước khi văn bản thay đổi đầu vào, bên trong trình xử lý dán của tôi, tôi tạo một chức năng hoãn lại bên trong mà tôi kiểm tra các thay đổi trong hộp nhập của mình xảy ra khi dán:

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

2
Thật không may, kinh dị là một phần trong mô tả công việc của chúng tôi;) Nhưng tôi đồng ý, đây là một hack và hack nên được sử dụng CHỈ khi tất cả các tùy chọn khác đã hết.
Lex

4

Điều này quá dài cho một nhận xét về câu trả lời của Nico, điều mà tôi không nghĩ là nó hoạt động trên Firefox nữa (theo các bình luận) và cũng không hoạt động với tôi trên Safari.

Đầu tiên, bây giờ bạn dường như có thể đọc trực tiếp từ bảng ghi tạm. Thay vì mã như:

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

sử dụng:

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

bởi vì Firefox có một typeslĩnh vực DOMStringListkhông thực hiện test.

Firefox tiếp theo sẽ không cho phép dán trừ khi tiêu điểm nằm trong một contenteditable=truetrường.

Cuối cùng, Firefox sẽ không cho phép dán một cách đáng tin cậy trừ khi tiêu điểm nằm trong textarea(hoặc có lẽ là đầu vào) không chỉ: contenteditable=truemà còn:

  • không phải display:none
  • không phải visibility:hidden
  • không có kích thước bằng không

Tôi đã cố gắng ẩn trường văn bản để tôi có thể thực hiện công việc dán trên trình giả lập JS VNC (tức là nó sẽ đến một máy khách từ xa và thực sự không có textareavv để dán vào). Tôi thấy việc cố gắng ẩn trường văn bản ở trên đã gây ra các triệu chứng đôi khi nó hoạt động, nhưng thường thất bại ở lần dán thứ hai (hoặc khi trường bị xóa để tránh dán cùng một dữ liệu hai lần) vì trường bị mất tiêu điểm và sẽ không lấy lại được đúng cách nó mặc dù focus(). Giải pháp tôi đưa ra là đặt nó vào z-order: -1000, làm cho nó display:none, làm cho nó thành 1px x 1px và đặt tất cả các màu thành trong suốt. Kinh quá.

Trên Safari, bạn áp dụng phần thứ hai ở trên, tức là bạn cần phải có phần textareakhông display:none.


Có lẽ các nhà phát triển làm việc trên các công cụ kết xuất trình duyệt nên có một trang hoặc không gian trên các trang tài liệu mà họ có thể sử dụng để viết ghi chú về các tính năng mà họ làm việc. Ví dụ: nếu họ làm việc với chức năng dán họ sẽ thêm, "Dán sẽ không hoạt động nếu hiển thị là không có, khả năng hiển thị bị ẩn hoặc kích thước bằng không".
1,21 gigawatt

3

Đầu tiên mà tôi nghĩ đến là pastehandler của Google lib lib http: // clenses-l Library.googlecode.com/svn/trunk/clenses/goog/demos/pastehandler.html


cái này có vẻ như phát hiện một sự kiện dán một cách an toàn, nhưng dường như không thể bắt / trả lại nội dung đã dán?
Alex

@Alex: bạn đã đúng và điều này cũng chỉ hoạt động với textareas, không phải trình soạn thảo văn bản phong phú.
Tim Down

3

Giải pháp đơn giản:

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

2

Điều này làm việc cho tôi:

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

Bạn có thể làm điều này theo cách này:

sử dụng plugin jQuery này cho các sự kiện dán trước & sau:

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

Bây giờ bạn có thể sử dụng plugin này;:

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

Giải thích

Đầu tiên đặt uid cho tất cả các thành phần hiện có làm thuộc tính dữ liệu.

Sau đó so sánh tất cả các nút POST PASTE sự kiện. Vì vậy, bằng cách so sánh bạn có thể xác định cái mới được chèn bởi vì chúng sẽ có uid, sau đó chỉ cần xóa thuộc tính style / class / id khỏi các phần tử mới được tạo, để bạn có thể giữ định dạng cũ hơn.


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

1

Chỉ cần để trình duyệt dán như bình thường trong div có thể chỉnh sửa nội dung của nó và sau đó sau khi dán, hoán đổi bất kỳ thành phần span nào được sử dụng cho kiểu văn bản tùy chỉnh với chính văn bản. Điều này có vẻ hoạt động tốt trong trình duyệt internet explorer và các trình duyệt khác mà tôi đã thử ...

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

Giải pháp này giả định rằng bạn đang chạy jQuerybạn không muốn định dạng văn bản trong bất kỳ div nào có thể chỉnh sửa nội dung của bạn .

Mặt tích cực là nó siêu đơn giản.


Tại sao lại spangắn thẻ? Tôi sẽ tưởng tượng câu hỏi là về tất cả các thẻ.
Alexis Wilke

1

Giải pháp này là thay thế thẻ html, nó đơn giản và nhiều trình duyệt; kiểm tra jsfiddle này: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , mã lõi:

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

Lưu ý: bạn nên thực hiện một số công việc về bộ lọc xss ở phía sau vì giải pháp này không thể lọc các chuỗi như '<< >>'


Quay phim XSS trên máy chủ không liên quan gì đến việc bộ lọc JavaScript của bạn có hoạt động tốt hay không. Tin tặc bỏ qua 100% bộ lọc JS của bạn.
Alexis Wilke

Không bao giờ sử dụng Regex để phân tích / chuyển đổi HTML!
SubliemeSiem

0

Đây là một mã hiện có được đăng ở trên nhưng tôi đã cập nhật nó cho IE, lỗi là khi văn bản hiện tại được chọn và dán sẽ không xóa nội dung được chọn. Điều này đã được sửa bởi mã dưới đây

selRange.deleteContents(); 

Xem mã hoàn chỉnh bên dưới

$('[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);
    }        
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.