Когда вы работаете на кровоточащем краю, иногда вас могут порезать. Технология не стабильна, все глючит и может не соответствовать стандарту, но событие onPaste не является передовой технологией. Фактически, это было вокруг с IE5. Так почему же такой беспорядок?
В последнее время я потратил много времени на работу с событием вставки, работая над Hopper . То, что я обнаружил, было потрясающим отсутствием единообразия. Кажется, что у каждого браузера есть своя реализация события (и все они не очень хороши). Я хотел пройтись по нескольким реализациям и дать вам представление о том, на что обращать внимание при использовании этого нестандартного события.
Самая простая и наиболее полная реализация — это Google Chrome (все примеры предполагают, что мы используем jQuery, чтобы уменьшить многословность):
$( 'body' ).bind( 'paste', function( evt ) { var items = evt.originalEvent.clipboardData.items , paste; // Items have a "kind" (string, file) and a MIME type console.log( 'First item "kind":', items[0].kind ); console.log( 'First item MIME type:', items[0].type ); // If a user pastes image data, there are 2 items: the file name (at index 0) and the file (at index 1) // but if the user pastes plain text or HTML, index 0 is the data with markup and index 1 is the plain, unadorned text. if( items[0].kind === 'string' && items[1].kind === 'file' && items[1].type.match( /^image/ ) ) { // If the user copied a file from Finder (OS X) and pasted it in the window, this is the result. This is also the result if a user takes a screenshot and pastes it. // Unfortunately, you can't copy & paste a file from the desktop. It just returns the file's icon image data & filename (on OS X). item = items[0]; } else if( items[0].kind === 'string' && items[1].kind === 'string' ) { // Get the plain text item = items[1]; } // From here, you can use a FileReader object to read the item with item.getAsFile(), or evt.originalEvent.clipboardData.getData(item.type) to get the plain text. Confused yet? } );
Вау. И вышеупомянутое даже не всесторонне; Я все еще нахожу множество крайних случаев, которые возвращают результаты не в виде простого текста, когда нам нужен простой текст.
Firefox, с другой стороны, имеет совершенно неутешительную реализацию. Вы не можете извлечь вставленный контент из события, и, в отличие от Chrome, где вы можете привязать что-либо, событие, кажется, работает только при привязке к полю, которое «обычно» принимает событие вставки, например поле ввода или текстовое поле.
var $pasteField = $( '#pasteField' ); function onPaste( evt, retries ) { // No clipboardData object, so we fake it and just return the field's content, when we receive it. var text = $pasteField.val(); $pasteField.val( '' ); // reset the field clipboardObj = { getData: function() { return text; } }; items = [ { kind: 'string', type: 'text' } ]; // If we didn't find any content on this attempt, and we haven't tried > 3 times, // try again in 100 ms to find content in the textarea. if( !text.length && ( retries < 3 || typeof retries === 'undefined' ) ) { if( typeof retries === 'undefined' ) retries = 0; setTimeout( function() { onPaste( evt, ++retries ); }, 100 ); return true; } // Now you finally have the item's content, with an API similar to that of Chrome -- at least, for plain text. } $pasteField.bind( 'paste', onPaste );
Вы заметите, что мы используем таймер и неоднократно пытаемся получить содержимое текстовой области. Это связано с тем, что событие вставки не запускается синхронно с содержимым, фактически отображаемым в текстовой области, и может потребоваться несколько попыток, пока это не станет так.
А как насчет IE?
// We need a textarea for IE, too: <textarea id="pasteField"></textarea> $( '#pasteField' ).bind( 'paste', function( evt ) { // In true Microsoft form, the type to get plain text is Text with a captial T. console.log( window.clipboardData.getData( 'Text' ) ); } );
Довольно простой. Примерно с IE5. Не очень универсальный. Но, по крайней мере, мы можем получить данные.
И последнее, но не менее важное, Safari, который имеет самую странную реализацию из всех:
// Safe to say...textarea is a good idea: <textarea id="pasteField"></textarea> $( '#pasteField' ).on( 'paste', function( evt ) { // Safari has a weird "types" list that we need to loop through and no "items" array. var i = 0, items = [], item, key = 'text/plain', kind = 'string'; while (i < evt.originalEvent.clipboardData.types.length) { var key = evt.originalEvent.clipboardData.types[i]; if( !key.match( /(text\/)|(plain-text)/i ) ) { kind = 'file'; } items.push( { kind: kind, type: key } ); i++; } // Let's just get content of the first item. item = items[0]; console.log( evt.originalEvent.clipboardData.getData( item.type ) ); } );
Это даже не касается различий между ОС (OS X и Windows) и того, как они обрабатывают метаданные в содержимом вставки.
Итак, как нам согласовать всю эту информацию и разнообразие реализаций? Кажется, самым безопасным решением является наличие закадрового или скрытого текстового поля, в которое можно при необходимости поместить фокус пользователя и соответствующим образом связать все события вставки. Затем объедините описанные выше методы (я оставлю читателю в качестве упражнения объединить их все в кросс-браузерную реализацию).
Вам также нужно будет протестировать различные варианты вставки — элементы, вставленные из Word, браузера и других редакторов форматированного текста, все реагируют по-разному и могут возвращать разные метаданные / разметку.
Самое замечательное в том, что мы можем захватывать вставку контента от пользователей и находить новые, креативные способы улучшения пользовательского опыта. Худшая часть — это бесчисленные реализации события вставки и данных, которые оно возвращает.
Постскриптум: Недавно мне сообщил веб-разработчик Джоэл Бесада, что, несмотря на отсутствие объекта clipboardData в Firefox, вы все равно можете получать изображения из буфера обмена . Спасибо, Джоэл! Я обнаружил, что вариант этой техники также работает в IE, что довольно интересно.