JavaScript получает данные из буфера обмена при вставке события (кросс-браузер)


299

Как веб-приложение может обнаружить событие вставки и извлечь данные для вставки?

Я хотел бы удалить содержимое HTML, прежде чем текст будет вставлен в текстовый редактор.

Очистка текста после последующей вставки работает, но проблема в том, что все предыдущее форматирование потеряно. Например, я могу написать предложение в редакторе и сделать его жирным, но когда я вставляю новый текст, все форматирование теряется. Я хочу очистить только вставленный текст и оставить любое предыдущее форматирование без изменений.

В идеале решение должно работать во всех современных браузерах (например, MSIE, Gecko, Chrome и Safari).

Обратите внимание, что MSIE имеет clipboardData.getData(), но я не мог найти аналогичные функции для других браузеров.


Все эти ответы объясняют, как получить текстовое содержание. Получение изображения или содержимого файла требует гораздо больше работы. Может быть, мы можем изменить заголовок на «JavaScript получить очищенные данные буфера обмена текста ...»
1.21 гигаватт

1
как нико сказал: event.clipboardData.getData('Text')работал для меня.
Андре Элрико

document.addEventListener('paste'...работал для меня, но вызывал конфликты, если пользователь хотел иметь возможность вставлять в другом месте на странице. Затем я попытался myCanvasElement.addEventListener('paste'..., но это не сработало. В конце концов я разобрался, myCanvasElement.parentElement.addEventListener('paste'...работал.
Райан

Ответы:


149

С момента написания этого ответа ситуация изменилась: теперь, когда Firefox добавил поддержку в версии 22, все основные браузеры теперь поддерживают доступ к данным буфера обмена в событии вставки. Посмотрите ответ Нико Бернса для примера.

В прошлом это не было возможно вообще кросс-браузерным способом. Идеально было бы иметь возможность получать вставленный контент через pasteсобытие, что возможно в недавних браузерах, но не в некоторых старых браузерах (в частности, Firefox <22).

Когда вам нужно поддерживать более старые браузеры, то, что вы можете сделать, довольно сложно, и что-то вроде хака, который будет работать в браузерах Firefox 2+, IE 5.5+ и WebKit, таких как Safari или Chrome. Последние версии TinyMCE и CKEditor используют эту технику:

  1. Обнаружение события ctrl-v / shift-ins с помощью обработчика события нажатия клавиши
  2. В этом обработчике сохраните текущее выделение пользователя, добавьте к документу текстовый элемент вне экрана (скажем, слева - 1000 пикселей), designModeвыключите и вызовите focus()текстовое поле, перемещая каретку и эффективно перенаправляя вставку.
  3. Установите очень короткий таймер (скажем, 1 миллисекунду) в обработчике событий для вызова другой функции, которая хранит значение текстовой области, удаляет текстовую область из документа, designModeснова включает , восстанавливает выбор пользователя и вставляет текст в.

Обратите внимание, что это будет работать только для событий вставки с клавиатуры, но не для вставки из контекстного меню или меню редактирования. К тому времени, когда происходит событие вставки, уже слишком поздно перенаправлять курсор в текстовую область (по крайней мере, в некоторых браузерах).

В маловероятном случае, когда вам потребуется поддержка Firefox 2, обратите внимание, что вам нужно поместить текстовую область в родительский документ, а не документ iframe редактора WYSIWYG в этом браузере.


1
Вау, спасибо за это! Кажется, что это очень сложный хак ;-) Не могли бы вы описать этот designMode и выбор немного больше, особенно на шаге 3? Большое спасибо!
Алекс

5
У меня было ужасное чувство, что ты спросишь это. Как я уже сказал, это довольно сложно: я бы посоветовал взглянуть на источник TinyMCE или CKEditor, так как у меня нет времени на то, чтобы обрисовать все связанные проблемы. Вкратце, designModeэто логическое свойство documentи делает всю страницу редактируемой, когда true. Редакторы WYSIWYG обычно используют iframe с designModeon в качестве редактируемой панели. Сохранение и восстановление выбора пользователя выполняется одним способом в IE, а другим - в других браузерах, как и вставка содержимого в редактор. Вам нужно получить TextRangeв IE и Rangeв других браузерах.
Тим Даун

6
@ Сэмюэль: Вы можете обнаружить это, используя pasteсобытие, но к тому времени уже слишком поздно, чтобы перенаправить вставку в другой элемент, так что этот хак не будет работать. В большинстве редакторов запасной вариант заключается в отображении диалогового окна, в которое пользователь может вставить данные.
Тим Даун

6
Немного больше информации об этом: Firefox не позволит вам переместить фокус на другой элемент в pasteсобытии, однако он позволит вам очистить содержимое элемента (и сохранить его в переменной, чтобы вы могли восстановить его позже). Если этот контейнер является div(он, вероятно, iframeтоже работает), то вы можете циклически перемещать вставленный контент, используя обычные методы dom, или получить его в виде строки, используя innerHTML. Затем вы можете восстановить предыдущее содержимое divи вставить любое содержимое. О, и вы должны использовать тот же взлом таймера, что и выше. Я удивлен, что TinyMCE не делает этого ...
Нико Бернс

8
@ResistDesign: я не согласен - это не элегантный и сложный способ восполнить отсутствие разумного API. Было бы лучше иметь возможность получать вставленный контент непосредственно из события вставки, что ограниченным образом возможно в некоторых браузерах .
Тим Даун

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/

Обратите внимание, что это решение использует параметр «Текст» для 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объект для события вставки. Особый интерес для нас представляет clipboardDataсвойство этого события, которое обеспечивает доступ к буферу обмена в не-браузерах. В IE аналогичный window.clipboardData, хотя у него немного другой API.

Смотрите раздел ресурсов ниже.


handlepasteФункция:

Эта функция имеет две ветви.

Первый проверяет наличие event.clipboardDataи проверяет, typesсодержит ли его свойство 'text / html' (это typesможет быть либо DOMStringListпроверяемый с помощью containsметода, либо строка, проверяемая с помощью этого indexOfметода). Если все эти условия выполнены, то мы поступаем так же, как в решении № 1, за исключением «text / html» вместо «text / plain». В настоящее время это работает в Chrome и Firefox 22+.

Если этот метод не поддерживается (все остальные браузеры), то мы

  1. Сохраните содержимое элемента в DocumentFragment
  2. Очистить элемент
  3. Вызвать waitForPastedDataфункцию

waitforpastedataФункция:

Эта функция сначала запрашивает вставленные данные (один раз в 20 мс), что необходимо, поскольку они не отображаются сразу. Когда появились данные это:

  1. Сохраняет innerHTML редактируемого div (который теперь является вставленными данными) в переменную
  2. Восстанавливает содержимое, сохраненное в DocumentFragment
  3. Вызывает функцию processPaste с полученными данными.

processpasteФункция:

Делает произвольные вещи с вставленными данными. В этом случае мы просто оповещаем данные, вы можете делать все что угодно. Возможно, вы захотите запустить вставленные данные через какой-то процесс очистки данных.


Сохранение и восстановление позиции курсора

В реальной ситуации вы, вероятно, захотите сохранить выделение раньше, а затем восстановить его ( установите позицию курсора на contentEditable <div> ). Затем вы можете вставить вставленные данные в положение, в котором находился курсор, когда пользователь инициировал действие вставки.

Ресурсы:

Спасибо Тиму Дауну за предложение использовать DocumentFragment и облегчить обнаружение ошибки в Firefox из-за использования DOMStringList вместо строки для clipboardData.types


4
Интересный. Я думал, что попробовал это в прошлом, и это не сработало в каком-то браузере, но я уверен, что вы правы. Я бы определенно предпочел переместить существующий контент в DocumentFragmentскорее, чем использовать innerHTMLпо нескольким причинам: во-первых, вы сохраните любые существующие обработчики событий; во-вторых, сохранение и восстановление innerHTMLне гарантируют создание идентичной копии предыдущего DOM; в-третьих, вы можете затем сохранить выделение, Rangeвместо того, чтобы возиться с добавлением элементов маркера или вычислением смещения текста (что вы должны были бы сделать, если бы использовали innerHTML).
Тим Даун

3
На самом деле вспышка отсутствует (FONC?), Что, очевидно, будет хуже, если обработка вставленного содержимого займет некоторое время. Кстати, почему извлечение DocumentFragmentболи в IE? Это то же самое, что и в других браузерах, если только вы не используете Range и не extractContents()делаете этого, что в любом случае не более кратко, чем альтернатива. Я реализовал пример вашей техники, используя Rangy, чтобы все было хорошо и единообразно в разных браузерах: jsfiddle.net/bQeWC/4 .
Тим Даун

1
@Martin: демонстрация jsFiddle, которую я разместил в комментариях, может помочь.
Тим Даун

1
Кажется, что он больше не работает на Firefox 28 (по крайней мере) для Windows. Она никогда не выходит из waitforpastedataфункции
Oliboy50

1
К вашему сведению: Edge теперь поддерживает чтение данных с MIME-Type text/htmlс использованием W3C Clipboard API. В прошлом такая попытка была бы исключением. Так что больше не нужен этот обходной путь / хак для 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-теги strip )


4
Это работает очень хорошо, но ни одна версия IE не разрешает доступ к clipboardData из события :( Отличное решение, хотя, это должно быть выше!
Эрик Вуд

1
Похоже, что вы можете получить доступ к данным буфера обмена в IE иным способом, поэтому, если вы обнаружите IE, вы можете использовать эти данные вместо быстрого отката
Андрей

4
лучший кросс-браузерный ответ найден до сих пор. просто добавьте код для IE и его идеально.
Артуро

6
Это работает в IE (ах, сладкий, вопреки 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

Live Demo

Проверено на 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 и прекрасно работает. Излишне упоминать, что это работает и в основных браузерах ... Спасибо.
Савас Ведова

2
Ваш код содержит ошибку: if (e.originalEvent.clipboardData) может вызвать NPE, поскольку вы не знаете, существует ли e.originalEvent в этот момент
Себастьян

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>

Просто скопируйте и вставьте весь код в один HTML-файл и попробуйте вставить (используя Ctrl-V) текст из буфера обмена в любом месте документа.

Я протестировал его в IE9 и новых версиях Firefox, Chrome и Opera. Работает довольно хорошо. Также хорошо, что можно использовать любую комбинацию клавиш, которую он предпочитает, чтобы активировать эту функцию. Конечно, не забудьте включить исходники jQuery.

Не стесняйтесь использовать этот код, и если у вас появятся какие-либо улучшения или проблемы, пожалуйста, отправьте их обратно. Также обратите внимание, что я не являюсь разработчиком Javascript, поэтому, возможно, я что-то пропустил (=> сделайте свое собственное тестирование).


Маки не вставляют ctrl-v, они используют cmd-v. Поэтому установите ctrlKey = 91 вместо 17
Джереми Т

2
Или, может быть, это не всегда 91: stackoverflow.com/questions/3834175/… Несмотря на это, я вполне уверен, что jQuery справится со всем этим для вас, просто проверьте e.ctrlKey или e.metaKey, я думаю.
Джереми Т

3
e.ctrlKey или e.metaKey является частью DOM JavaScript, а не jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne

2
Я не думаю, что это работает для щелчка правой кнопкой мыши и вставки. Многие люди используют этот подход.
Эрик Вуд

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?
Пребывание

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

Этот код дополнен дескриптором выбора перед вставкой: demo


+1 Мне нравится этот больше, чем Нико Бернс, хотя я думаю, что у каждого есть свое место.
n0nag0n

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

Можете ли вы предоставить демо-страницу, где это работает? Я попробовал это, и это не работает
vsync

5

Это должно работать во всех браузерах, которые поддерживают событие onpaste и наблюдателя мутаций.

Это решение выходит за рамки получения только текста, фактически позволяет редактировать вставленный контент до его вставки в элемент.

Он работает с использованием contenteditable, события onpaste (поддерживается всеми основными браузерами) и наблюдателей мутаций (поддерживается Chrome, Firefox и IE11 +)

шаг 1

Создать HTML-элемент с contenteditable

<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. Наблюдатель мутаций ловит это (он регистрирует все изменения в дереве и содержимом). Затем запускается событие мутации.
  7. Дом вставленного содержимого клонируется в переменную и возвращается к обратному вызову. Временный элемент уничтожен.
  8. Обратный вызов получает клонированный DOM. Каретка восстановлена. Вы можете изменить это, прежде чем добавить его к своей цели. элемент. В этом примере я использую функции Тима Даунса для сохранения / восстановления каретки и вставки HTML в элемент.

пример


Большое спасибо Тиму Дауну. Смотрите этот пост для ответа:

Получить вставленный контент в документе при вставке события


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

Это было слишком долго для комментария к ответу Нико, который, я думаю, больше не работает в 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поле, которое DOMStringListне реализуется test.

Следующий Firefox не разрешит вставку, если фокус не находится в contenteditable=trueполе.

Наконец, Firefox не позволит надежно вставлять, если фокус находится не на textarea(или, возможно, на входе), который не только, contenteditable=trueно и:

  • не display:none
  • не visibility:hidden
  • не нулевого размера

Я пытался скрыть текстовое поле, чтобы я мог заставить вставку работать через эмулятор JS VNC (то есть он собирался на удаленном клиенте, и на самом деле textareaвставлять в него некуда ). Я обнаружил, что попытка скрыть текстовое поле в приведенном выше примере приводила к появлению симптомов, при которых оно иногда срабатывало, но, как правило, не удавалось при второй вставке (или когда поле очищалось, чтобы предотвратить вставку одних и тех же данных дважды), поскольку поле теряло фокус и не восстанавливалось должным образом это несмотря на focus(). Решение, которое я придумал, состояло в том, чтобы поставить его z-order: -1000, сделать его display:none, сделать его размером 1 на 1 пиксель и установить все цвета на прозрачные. Тьфу.

На Safari к вам относится вторая часть вышеперечисленного, т.е. вам нужно иметь то, textareaчего нет display:none.


Возможно, разработчики, работающие с механизмами рендеринга браузера, должны иметь страницу или место на сайтах документации, которые они могут использовать, чтобы писать в заметках о функциях, над которыми они работают. Например, если они работали над функцией вставки, которую они добавили бы, «вставка не будет работать, если отображение отсутствует, видимость скрыта или размер равен нулю».
1.21 гигаватт

3

Первое, что приходит на ум, - это программа-обработчик закрывающей библиотеки Google http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html.


кажется, что это безопасно обнаруживает событие вставки, но кажется, что он не может перехватить / вернуть вставленный контент?
Алекс

@ Алекс: вы правы, и это также работает только с текстовыми редакторами, а не с текстовыми редакторами.
Тим Даун

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

Explaination

Сначала установите uid для всех существующих элементов в качестве атрибута данных.

Затем сравните все узлы события POST PASTE. Таким образом, сравнивая, вы можете идентифицировать только что вставленный, потому что у них будет uid, а затем просто удалить атрибут style / class / id из вновь созданных элементов, чтобы вы могли сохранить старое форматирование.



1

Просто позвольте браузеру вставлять как обычно в его редактируемый div содержимого, а затем после вставки поменяйте местами любые элементы span, используемые для пользовательских стилей текста, с самим текстом. Кажется, это работает нормально в Internet Explorer и других браузерах, которые я пробовал ...

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

Это решение предполагает, что вы работаете с jQuery, и вам не нужно форматировать текст в любом редактируемом элементе содержимого .

Плюс в том, что это супер просто.


Почему spanтег? Я думаю, что вопрос был обо всех тегах.
Алексис Вилке

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 filering на сервере не имеет ничего общего с тем, хорошо ли работает ваш JavaScript-фильтр. В любом случае, хакеры обходят 100% фильтрации JS.
Алексис Уилке

Никогда не используйте Regex для анализа / преобразования 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);
    }        
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.