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


299

Як веб-додаток може виявити подію вставки та отримати дані, які потрібно вставити?

Я хотів би видалити вміст HTML, перш ніж текст буде вставлений у редактор розширеного тексту.

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

В ідеалі рішення має працювати у всіх сучасних браузерах (наприклад, MSIE, Gecko, Chrome та Safari).

Зауважте, що MSIE має clipboardData.getData(), але я не можу знайти подібну функціональність для інших браузерів.


Усі ці відповіді пояснюють, як отримати текстовий вміст. Отримання вмісту зображення або вмісту файлів вимагає набагато більше роботи. Можливо, ми можемо змінити заголовок на "JavaScript отримати санітарні дані тексту буфера обміну ..."
1,21 gigawatts

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+ і веб-браузерах, таких як Safari або Chrome. Останні версії TinyMCE та CKEditor використовують цю методику:

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

Зауважте, що це буде працювати лише для подій на клавіатурі, а не вставок із контексту чи редагування меню. До моменту запуску події вставки вже пізно перенаправляти карету в текстову область (принаймні, у деяких браузерах).

У тому випадку, коли вам потрібно підтримати Firefox 2, зауважте, що вам потрібно буде розмістити textarea у батьківському документі, а не в редакторі WYSIWYG-редактора iframe в цьому браузері.


1
Нічого, дякую за це! Здається, це дуже складний хакер ;-) Чи не могли б ви описати цей designMode та вибір речі трохи більше, особливо на кроці 3? Дуже дякую!
Олексій

5
У мене було жахливе відчуття, що ти це запитаєш. Як я кажу, це дуже пов'язано: я б запропонував переглянути джерело TinyMCE або CKEditor, оскільки я не маю часу окреслити всі проблеми. Якщо коротко, designModeто є булева властивість documentі робить всю сторінку редагованою, коли true. Редактори WYSIWYG зазвичай використовують рамку iframe з designModeувімкнутим для редагування. Збереження та відновлення вибору користувача здійснюється одним способом в IE, а іншим - в інших браузерах, як і вставлення вмісту в редактор. Вам потрібно отримати TextRangeв IE та Rangeв інших браузерах.
Tim Down

6
@Samuel: Ви можете виявити це за допомогою pasteподії, але, як правило, занадто пізно, щоб перенаправити пасту на інший елемент, тому цей злом не працюватиме. Резервна копія більшості редакторів полягає в тому, щоб показати діалогове вікно для користувача, яке слід вставити.
Тім Даун

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

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/

Зауважте, що це рішення використовує параметр "Текст" для 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може бути або a, DOMStringListщо перевіряється containsметодом, або рядок, що перевіряється за допомогоюindexOf методом). Якщо всі ці умови виконуються, ми продовжуємо, як у рішенні №1, за винятком "text / html" замість "text / plain". Наразі це працює в Chrome та Firefox 22+.

Якщо цей метод не підтримується (всі інші браузери), то ми

  1. Збережіть вміст елемента в a DocumentFragment
  2. Опустіть елемент
  3. Викличте waitForPastedDataфункцію

waitforpastedataфункція:

Ця функція спочатку опитує для вставлених даних (раз на 20 мс), що необхідно, оскільки воно не з’являється відразу. Коли дані з’являються, це:

  1. Зберігає внутрішнійHTML редагованого div (який тепер вставлені дані) до змінної
  2. Відновлює вміст, збережений у DocumentFragment
  3. Викликає функцію 'processPaste' з отриманими даними

processpasteфункція:

Чи довільні речі із вставленими даними. У цьому випадку ми просто попереджаємо дані, ви можете робити все, що завгодно. Ви, ймовірно, захочете запустити вкладені дані через якийсь процес санітарії даних.


Збереження та відновлення положення курсору

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

Ресурси:

Завдяки Тіму Даун запропонував використовувати DocumentFragment та abligh для введення помилки у Firefox завдяки використанню DOMStringList замість рядка для clipboardData.types


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

3
Дійсно є спалах без вмісту (FONC?), Що, очевидно, буде гірше, якщо обробка вставленого вмісту займе певний час. До речі, навіщо DocumentFragmentболі в IE? Це те саме, що і в інших браузерах, якщо ви не використовуєте діапазон і extractContents()не робите це, що ні в якому разі не є більш стислим, ніж альтернатива. Я реалізував приклад вашої техніки, використовуючи Rangy, щоб підтримувати речі красиві та рівномірні у веб-переглядачах: jsfiddle.net/bQeWC/4 .
Тім Даун

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

1
Схоже, він більше не працює на Firefox 28 (принаймні) для Windows. Це ніколи не виходить із waitforpastedataфункції
Oliboy50

1
FYI: Edge тепер підтримує зчитування даних з MIME-типу text/htmlза допомогою API буфера обміну W3C. Раніше така спроба кинула виняток. Тому більше не потрібен цей спосіб вирішення / злому для 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 не дозволяє отримати доступ до буфера обміну даними з події :( Чудове рішення, однак, це повинно бути вище!
Ерік Вуд

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

Демонстраційна демонстрація

Тестовано на 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
Ваш код містить помилку: якщо (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
Jeremy T

2
А може, це не завжди 91: stackoverflow.com/questions/3834175/… Незважаючи на те, я впевнений, що jQuery обробляє все це для вас, просто перевірте e.ctrlKey або e.metaKey, на мою думку.
Джеремі Т

3
e.ctrlKey або e.metaKey є частиною JavaScript DOM, а не 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);
    }
}); 

Цей код розширюється ручкою вибору перед вставкою: демо


+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 та спостерігача за мутацією.

Це рішення виходить на крок за рамками отримання лише тексту, воно фактично дозволяє редагувати вставлений вміст, перш ніж його вставити в елемент.

Це працює за допомогою вмістоподібної події onpaste (підтримується всіма основними браузерами), спостерігачів за мутацією (підтримується Chrome, Firefox та IE11 +)

крок 1

Створіть 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, контекстуальне меню або інші засоби
  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
Жах, на жаль, є частиною нашої посадової інструкції;) Але я погоджуюся, це хакер і хаки слід використовувати ТІЛЬКИ, коли всі інші варіанти вичерпані.
Лекс

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 не дозволить вставити, якщо фокус не знаходиться в a contenteditable=true полі.

Нарешті, Firefox не дозволить надійно вставити, якщо фокус не знаходиться в textarea(або, можливо, вхідному), який є не тільки, contenteditable=trueале й:

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

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

Що стосується Safari, ви стосуєтесь другої частини вищезазначеного, тобто вам потрібно мати таку, textareaяка не є display:none.


Можливо, розробники, які працюють на двигунах візуалізації браузера, повинні мати сторінку або простір на сайтах документації, які вони можуть використовувати для написання приміток про функції, над якими вони працюють. Наприклад, якщо вони працювали над функцією вставки, вони додали б: "Вставити не буде працювати, якщо відображення немає, видимість прихована або розмір дорівнює нулю".
1,21 гігаватт

3

Перше, що спадає на думку, це пастовий інструмент для закриття губ google http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html


ця здається, що безпечно виявляє подію вставки, але, здається, не в змозі зловити / повернути вставлений вміст?
Олексій

@Alex: ти прав, і це також працює лише з текстовими текстами, а не з текстовими редакторами.
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

Просто нехай браузер вставить, як зазвичай, у розділ, який можна редагувати вміст, а потім після вставки поміняти будь-які елементи прольоту, які використовуються для користувацьких стилів тексту, із самим текстом. Здається, це працює нормально в 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 на сервері не має нічого спільного з тим, чи добре спрацьовує ваш фільтр 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.