Копировать и вставлять, как ходить по тонкому льду - подробное объяснение буфера обмена

JavaScript
Копировать и вставлять, как ходить по тонкому льду - подробное объяснение буфера обмена

передняя часть Seewo ENOW

Официальный сайт компании:CVTE (Гуанчжоу CVTE)

Команда: enow team центра программных платформ Seewo для будущего образования в рамках CVTE

Автор этой статьи:

предисловие

Эта статья начинается с чистого фронтенда, поэтому она не будет затрагивать подобныеflashИли операция копирования и вставки, которая пропускает проверку безопасности браузера, например подключаемые модули, полностью основана на ограничениях безопасности в браузере и некоторых «волшебных трюках» для достижения относительно полной функции копирования-вставки.
Эта статья начнется с функции копирования и вставки самого браузера, познакомит вас с реализацией функции копирования и вставки, сравнит реализацию нескольких форматированных текстовых документов и с учетом различных ограничений браузера, как реализоватьJSON-MODELДанные и применяются к буферу обмена класса форматированного текста.
 

Важность копирования и вставки

ориентированныйGoogleа такжеnpmтрудящихся-мигрантов не могут не пониматьCVНасколько хорош Дафа, и копирование и вставка уже давно стали частью нашей повседневной работы и жизни. Фактически, для обработки текста важность функции копирования и вставки совершенно невообразима.
И в коде нередко используются элементы формы. В элементах формы, полях ввода и других носителях ввода мы можем не знать внутреннюю реализацию копирования-вставки:
Например, скопировать картинку извне и вставить в поле ввода, но добиться этого нельзя, а текст можно? Как реализовать вставку внешнего стиля текста, см.wordтот самый документ? Как копировать и вставлять изображения в форматированный текст? Как настроить нашу функцию копирования и вставки?

копипаст тройка

Здесь нам нужно сначала понять три концепции:MIME,DataTransfer,clipboardEvent:

Тип носителя (MIME)

На самом деле, когда мы находимся в элементе формы, таком какinput,textareaПолное копирование и вставка реализованы в браузере, что вызывает возможности браузера по умолчанию.
Сначала нам нужно понять, что такое MIME:
Тип мультимедиа (часто называемый многоцелевыми расширениями почты Интернета или типами MIME) — это стандарт, используемый для представления характера и формата документа, файла или потока байтов;
MIMEСтруктура на самом деле очень проста: она состоит из типа и подтипа, а середина двух строк'/'разделенная композицияtype/subtype. Пробелы не допускаются.
Общие типы MIME:text/plain,text/html,image/png,application/jsonЖдать;
Например, мы пишемscriptилиstyleПри определении:

<style type="text/css"></style>
<script type="text/javascript"></script>


Или когда вы обычно запрашиваете внутренний интерфейс:


Те, кто знаком со спецификациями интерфейса или занимался внутренними службами, должны знать, чтоcontent-typeОпределение слова end на самом деле тесно связано с анализом back-end программы, при отладке интерфейса часто появляетсяcontent-typeНесоответствия с отправленными данными, например, что нужно серверной частиapplication/jsonданные, то если переданоapplication/x-www-form-urlencodedВ формате обычно возвращается код состояния ошибки. В это время интерфейс должен бытьcontent-typeПроведите соответствующую обработку данных.
Конечно, есть и некоторые специальные реализации:Content-Type: multipart/byteranges; boundary=xxxxДля информирования браузера о том, что данные разделены на несколько частей для достижения функции, аналогичной загрузке аудио- и видеосегментов;
Другими словами, браузер хочет знать, к какому типу данных относятся ваши данные, и какой анализ или обработка загрузки ему требуется (например, анализ в медиафайл или файл документа, который обычно загружается как ресурс). .MIMEбыть информированным;
В процессе копирования и вставки, по сути, также необходимо пройтиMIMEпровести соответствующий анализ.

DataTransfer

На самом деле, это объяснение на MDN не является полным, за исключениемdrag eventsтакже доступен вpaste,copy,cutИ другие события по приобретению. Я предпочитаю документ "мобильные данные" может быть использованDataTransferопределять.
DataTransferСуществует множество свойств и методов, но большинство из них находятся вdragгенерируются или могут быть только использованы. Напримерfilesотносятся только кdragСобытие, если операция перетаскивания не включает в себя файлы перетаскивания, атрибут является пустым номером:
Поэтому мы просто сосредоточимся наitemsэто свойство.itemsдаDataобъект;
Также есть пара методов:

  • setData(формат, данные) используется для установки содержания;
  • getData(формат) получить контент;

clipBoardEvent

clipboardEventявляется общим событием буфера обмена, поддерживаемым браузером. включеныpaste、cut、copyсвязанные события;
Нам нужно только обратить внимание на эти два свойства при копировании и вставке:
type: описывает тип триггера события;
clipboard: ОдинDataTransferобъект;

Реализация браузера по умолчанию

В браузере обычно при копировании и вставке используется стандартный формат MIME, обычно используемый браузерами (из скриншотов MDN):


Например, вinputа такжеtextareaТолько получать в копировании и вставкеtext/plainизMIMEТип, это также может быть текстовый формат по умолчанию, который будет поддерживать все программное обеспечение (не сталкивался с тем, что не поддерживает этот формат).
Конечно, если вашinput typeУстановить какfileВы можете выбрать поддержку других типов файлов, здесь особо нечего обсуждать.

сцена с расширенным текстом

В форматированном тексте, в дополнение к обычному тексту, которыйtext/plainВ дополнение к этому типу, как правило, необходимо поддерживать два другихMIMEтип, соответственноtext/htmlа такжеimage/png(Здесь просто относится к копированию и вставке)
текст/html:
Например, чтобы реализовать функцию: fromwordТакие документы, как копирование и вставка фрагмента текста, требуют согласованности стиля и форматирования, что должно быть общей чертой форматированного текста;
В это время, если мы непосредственно получимtext/plainВ этом случае можно получить только соответствующую текстовую версию. Вот когда нам нужно получитьtext/htmlтип текста. Стоит отметить, что обычные текстовые редакторы (word,ppt, документы Kingsoft...), полученные данные не в стандартном формате html или содержат большое количество избыточных данных. В настоящее время нам может потребоваться взять на себя инициативу по очистке данных и оставить только те данные, которые нам нужны. . . . Как правило, после получения данных вы можете использовать обычный метод для очистки избыточных данных.ueditor、wangeditorСоответствующую обработку данных можно увидеть в обычном форматированном тексте.
изображение/png:
Скопируйте изображения, как правило, копируйте и вставляйте снаружи;
 

текущая сцена

Сначала мне нужно сделать что-то вродеPPTРеализация функции копирования и вставки в документе включает в себя копирование текста и изображений и вставку их на нашу страницу, во-вторых, нам необходимо поддерживать другие внутренне определенные элементы, и, наконец, нам нужно поддерживать кросс-таблицу или даже кросс-браузерность на нашей странице. Последовательное копирование-вставка взаимодействия;
и из-заJSONданные иhtmlДанные не подключены.Например,в обычном текстовом редакторе копирование напрямую получается в основном напрямую.htmlданные; и в текущем сценарии, поскольку использованиеMVVMПричина в рамках, мы ставим всеdomпреобразован в единыйmodelДанные, поэтому данные, которые вы копируете, должны быть обработаны отдельно и не могут быть непосредственно вставлены в любой богатый текст.
Итак столкнулся со следующими проблемами:

1. Ограничения безопасности буфера обмена браузера

Браузеры имеют строгие ограничения безопасности в отношении буфера обмена: прямое чтение содержимого буфера обмена запрещено, если только не используется предложенныйnavigator.clipboard.readText / navigator.clipboard.readДля запросов разрешений пользователи могут читать сразу после того, как они передают инициативу, но это рискованно. Прежде всего, это предложение все еще находится в стадии разработки.draftКонечно, вероятность прохождения этапа очень велика, ведь для того, чтобы заменитьdocument.execCommandСуществует, но наша программа должна быть обратно совместима. Во-вторых, если пользователь активно запрещает этот метод, с последующей операцией вставки все равно будут проблемы. Следовательно, он должен эксплуатироваться по существующим стандартам;

Google SlidesРеализовано на:

  1. Активно спрашивайте пользователя, следует ли устанавливать подключаемый модуль, и пропускайте этот уровень ограничений безопасности для подключаемого модуля;
  2. Без установки, вsafariНажмите «Вставить», и вы снова увидите всплывающую маленькую кнопку, потому чтоsafariВозможность настройки меню;

2. Дилемма настройки контекстного меню

И на самом деле, в большинстве этих сценариев контекстное меню также настраивается. Вообще говоря, мы можем напрямую вызвать контекстное меню для копирования и вставки в соответствии с поведением браузера, но если вы хотите настроить меню, вы, конечно, можете отслеживатьcontextMenuсобытий, а затем активно предотвращать поведение по умолчанию, например документацию Tencent илиGoogle SlidesСюда. Однако некоторые действия браузера скрыты или даже не могут быть активизированы: например, копирование и вставка. Некоторые веб-документы даже нажимают кнопку вставки в меню, и сразу появляется подсказка, надеясь, что пользователь может вставить напрямую с помощью сочетания клавиш, что, конечно, очень античеловечно.
Основываясь на двух вышеприведенных пунктах, вам необходимо иметь собственный набор данных памяти, предоставлять данные в буфер обмена и контекстное меню, а затем активно обновлять при необходимости.clipboard, чтобы объединить вставленные данные памяти системы и данные внутренней памяти.
Вот трюк, который вы можете использоватьdocument.execCommand('copy/cut')активно обновлятьclipboardданные в. Когда вы щелкаете правой кнопкой мыши для копирования, если вам нужно активно обновлять данные в буфер обмена, вы можете активно вызывать и получатьcut/copyброшенное событиеclipboardData, затем используйтеsetData, который полезен для объединения внутренних и внешних данных и управления потоком данных между вкладками.

private bindCopy = (e) => {
    ......
    console.error('copy');
    e.preventDefault();
    this.duplicate.attemptToCopy(e, false);
  };

// Duplicate类
/**
   * 复制/剪切
   * @param e ClipboardEvent
   * @param isCut 是否为剪切
   * 1. 主动快捷键复制粘贴
   * 2. 右键菜单点击复制(不存在clipboardData对象)
   * 3. 主动塞入自定义数据
   */
public attemptToCopy(e: ClipboardEvent | null, isCut = false) {
    this.isCutCommand = isCut;

    if (e && e?.clipboardData) {
      const clipboardData = this.updateStash();
      clipboardData && this.updateClipboard(e, clipboardData);
    } else {
      this.autoCopy();
    }
  }

  /**
   * 自动拷贝
   * 1. 支持execCommand时,相当于复制后重新走一次addEventListener('copy')
   * 这时候可以拿到e.clipboardData对象,可以执行上面的updateStash
   * 好处:可以在copy里setData,设置标志位;缺点:execCommand有风险为被废除
   * 2. 不支持时,使用writeText
   * 好处:降级处理;缺点:无法设置特殊MIME,只能在getData('text/plain')里判断
   */
  public autoCopy() {
    if (!document.execCommand(this.isCutCommand ? "cut" : 'copy')) {
      const clipboardData = this.updateStash();
      clipboardData && navigator.clipboard.writeText(JSON.stringify(clipboardData));
    }
  }

Конечно, я сделал здесь небольшую совместимость, все-такиdocument.execCommandМетод является устаревшим состоянием. курс использованияnavigator.clipboard.writeэто тоже хорошо.

3. Смерть пользовательских типов MIME

Мыclipboardто естьDataTransferВозможен прямой доступ к нашим внутренним данным,
Например, мы даем специальную метку, напримерtext/copy, то есть определяемMIMEтип, то в следующий раз мы можем напрямую передатьgetData('text/copy')Получить, разве это не хорошо?
Это как сказать стандартMIMEТип - твердая валюта.После стольких лет все регионы (производители браузеров и системного софта) ее поддерживают, а также имеют свои методы обмена (универсальныеMIMEсинтаксический анализ), в то время как наши собственные определенныеMIMEНо это цифровая валюта, возникшая из ниоткуда, она точно не признается рынком и может использоваться только внутри страны. Аналогично: в общем, это вполне осуществимо. Но это не стандарт в конце концовMIMEТип, кросс-браузерная загрузка невозможна. То есть некоторые крайние сценарии неприемлемы.
Итак, почему мы должны устанавливать свои собственныеMIMEШерстяная ткань? первый генералMIMEКромеtext/plain, остальные типы будут активно добавлять данные, необходимые для типа, напримерtext/htmlЕсли это так, он добавит соответствующие в начале и в концеxmlДанные формата, в то время как другие были упомянуты ранее, будут проходить один разMIMEпарсинг, для стандартного, но специальногоMIMEФормат может иметь некоторые специальные добавленные данные или операции синтаксического анализа.
Во-вторых, кроссбраузерность я пока только виделtext/plainа такжеtext/htmlМожно передавать данные, а остальное будет фильтроваться... Но если задать прямоtext/plainДа, все скопированные внутренние данные будут отображаться при внешнем событии вставки. Если это обычные текстовые данные, все в порядке. Если это внутренние сохраненные отформатированные данные, это заставит пользователей чувствовать себя странно.
Затем первоначальная доработка заключается в использовании специальногоMIMEтип + одинtext/html, который может анализировать внутренние данные.
Идея очень хорошая, но в этот раз вы столкнетесь с другой проблемой: для некоторых текстовых редакторов основная информация, получаемаяtext/htmlСделайте еще один уровень анализа, тогда ваши данные будут доступны другим. Для этого, если это копирование во внешнюю вставку, извините, нет хорошего способа, потому что вы хотите сохранить единообразие данных и кросс-браузерное поведение. Если это внутренний редактор, то он будет сопоставлен, а если это внутренние данные, то они будут отфильтрованы напрямую.
Конечно, могут быть некоторые особые случаи, такие как внешнее копированиеsvgкартинки, на самом делеsvgдаxmlформатированные текстовые данные, маленькиеsvgКартинка, конечно, хороша, если большая, то нет гарантии, что браузер не зависнет. тогда вам может понадобитьсяtext/plainКстати, сделайте предварительный вывод...

Конечно, если вам не нужно иметь дело с кросс-браузерностью, то вам не нужно особых проблем, просто сохраните собственный тип MIME.

Вообще говоря, когда мы получаем содержимое буфера обмена, мы сначала оцениваем внутренние данные, то есть настраиваемый тип MIME, затем тип изображения и, наконец, тип простого текста;

import { SPEC_MIME } from "../util/variable";

class PasteHelper {
  // 获取img数据
  // 网上图片直接右键复制: [text/html, image/png]
  // ppt/截图工具 [image.png]
  // ppt文字:[text/plain,text/html,text/rtf,image/png]

  // RTF: 微软下的跨文本格式
  // https://zh.wikipedia.org/wiki/RTF
  public getImgTransData(e: ClipboardEvent) {
    if (!e.clipboardData?.items?.length) {
      return false;
    };
    const transferDatas = Array.from(e.clipboardData.items);
    const isText = transferDatas.find(c => c.type === 'text/rtf');

    // 需要处理视频类的情况,返回img,但是getAsFile为null
    if (!isText) {
      const imgTransData = transferDatas.filter(c => c.kind === 'file' && c.type.indexOf('image') === 0)[0];
      if (!imgTransData) {
        return false;
      } else {
        const imgFile = imgTransData.getAsFile();
        if (imgFile) {
          return imgFile;
        }
      }
    };
    return false;
  }

  /**
   * 判断是否为内部数据
   * 降级SPEC_MIME -> htmlData(跨浏览器) -> plainText(execommand不可用,走writeText)
   * @param e ClipboardEvent
   */
  public getInnerData (e: ClipboardEvent) {
    const innerData = e.clipboardData?.getData(SPEC_MIME);
    ......
  }

  // 获取纯文本,空白字符过滤
  public getPlainText (e: ClipboardEvent) {
    // 过滤特殊字符,给予空字符串
    const reg = /[\0-\x08\x0B\f\x0E-\x1F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
    let text = e.clipboardData?.getData('text/plain') || "";
    text = text.replace(reg, " ");
    return text ?? false;
  }
}

export default new PasteHelper();

4. Ад — это совместимость платформ

Вообще говоря, если мы скопируем и вставим, мы надеемся, что сможем следоватьword、pptПодождите, пока взаимодействие с локальным приложением не станет согласованным. Однако воображение полно, а реальность очень худа.
Под несколькими платформами уровень поддержки разный, и даже при реализации получаются совершенно разные данные.
В следующей таблице показаны полученные данные, скопированные из внешнего источника и вставленные во внутренний:

внешний источник текст картина текстовое окно Аудио и видео
веб-страница (обычная) служба поддержки служба поддержки преобразовать в текст не поддерживается
google slide служба поддержки не поддерживается преобразовать в текст не поддерживается
Документация Тенсент служба поддержки не поддерживается преобразовать в текст не поддерживается
офис ppt (веб) служба поддержки не поддерживается не поддерживается не поддерживается
Документация Kingsoft (веб-версия) служба поддержки не поддерживается не поддерживается не поддерживается
офис (ppt/excel) служба поддержки служба поддержки преобразовать в текст преобразовать в изображение
wps служба поддержки не поддерживается не поддерживается не поддерживается
keynote служба поддержки служба поддержки преобразовать в текст преобразовать в изображение
numbers служба поддержки служба поддержки преобразовать в текст не поддерживается
окна (система) служба поддержки не поддерживается - не поддерживается
уос (система) служба поддержки преобразовать в текст - преобразовать в текст
макинтош (система) служба поддержки служба поддержки - преобразовать в текст

 
Google Slides, документы Tencent и т. д.webПриложение не поддерживает копирование картинок и требует отдельного разбораtext/htmlКартинки в данных, то есть необходимость парсинга строк, реально сделать;
документ Кингсофт,officeЖдатьwebПричина, по которой приложения не поддерживают текстовые поля и текст, заключается в том, что эти приложения используют внутренние протоколы, а мы обычно не ориентируемся на специальные протоколы (MIME) сделать обработку;
windowsСистема не поддерживает копирование и вставку картинок и аудио и видео.После тестирования под этой системой можно получить только обычный текст;
В такой ситуации можно только обнять бедро отца продукта и сказать: Чен Ци не может этого сделать...

5. Ахиллесова пята обработки медиафайлов

Основываясь на доверии к внутренним данным, вы можете сначала скопировать данные, сериализовать их и поместить в наш индивидуальныйMIMEВ типе будет нормально вытащить его и десериализовать в следующий раз.
Однако вначале наши продукты делали специальную обработку мультимедийных файлов (аудио, видео, картинки): при загрузке на нашу страницу они просто конвертировались вblob, сохраните его в памяти, а затем загрузите в облако для следующей синхронизации. Такой подход может в определенной степени улучшить пользовательский опыт, ведь после загрузки нет необходимости загружать в облако.
Но здесь есть недостаток: при перекрестных вкладках, если предыдущая вкладка закрыта, то она привязывается кblobСсылка не работает, потому чтоblobАдрес памяти или ссылки сохраняется на предыдущей вкладке, но если вы измените его в это время, сфера влияния станет шире.
В настоящее время может быть принято только решение для перехода на более раннюю версию: если оно было загружено в облако, напрямую получите адрес ссылки; если оно не было загружено,blobСсылка может сначала скопировать только адрес большого двоичного объекта, на новой вкладке первый проходfetchСкачайте его на текущую страницу, а затем вы можете обработать его как обычный файл. Конечно, есть две проблемы с этим: 1. После копирования, немедленно закройте текущую страницу, затемblobПамять будет выпущена, и она не может быть загружена. Учитывая эту ситуацию, вы можете загружать его только непосредственно в облако при копировании или загрузке его молча в простоях; 2. В кросс-браузере вы ничего не можете сделать, Вы можете только это формат облачных ссылок.
Google slidesто естьblobпревращениеurlметод, в то время как документы Tencent загружаются напрямую, а Yuque используетbase64Отображается в загрузке.
Конечно, некоторые люди могут сказать, что вы можете сначала преобразовать файл вbase64, но обычно у нас есть несколько медиафайлов для копирования, на этот разbase64Генерация займет время, а объем данных может превысить размер памяти буфера обмена. Ведь нет возможности передавать бинарные файлы между браузерами, поэтому этот отвратительный способ можно использовать только в первую очередь.

if (isBlobUrl(model.source)) {
  if (medias[model.hash]) {
    model.source = medias[model.hash];
  } else {
    let blob;
    if (blob = await url2blob(model.source)) {
      // 根据blobUrl重新创建当前页面的blob
      const file = await blob2File(blob, model.mediaName || model.pictureName);
      const blobUrl = await file2BlobUrl(file);
      model.source = blobUrl;
      // 缓存media数据
      this.storageData.update({
        medias: {
          [model.hash]: blobUrl
        }
      });
    } else {
      console.error('不支持的blob_URL或者跨域');
      return null;
    }
  }
}

использовать один напрямуюDataTransferобъект, а затем запихивая в него данные, после тестирования это не сработает.
Конечно, если вы понимаетеclipboardItems, вы можете почувствоватьclipboardItemsДанные могут быть вставлены в буфер обмена.
Да, вполне возможно, ноclipboardItemsОн выглядит как массив, и при использовании он также является массивом, то есть данные представляют собой массив, который поддерживает только длину 1.

6. Черная дыра на картинке

На самом деле, мне никогда не приходило в голову, что для изображений потребуется такая обработка исключений. В процессе копирования и вставки картинок вы можете не думать, что копируете «тощего человека» и в итоге получаете «толстяка», который совершенно неузнаваем;
В браузере переходимimage/pngТо, что вы получаете, всегда является одним форматом изображения, потому что браузер преобразует изображение вbitmapВот ты, так что тыimage/pngя получил толькоbitmapизblobформат, это создает две проблемы:

  • Вы не можете получить исходный формат изображения, вы можете получить его толькоpng, о формате судить невозможно;
  • bitmapПреобразование под разные платформы отличается, что может привести к увеличению размера изображения, например,20mфотографии, черезgetAsFileполученный метод, может превышать30m......существуетmacа такжеwindowСледующий тест может дать два разных результата.


еще надо пройтиinputЧтобы в полной мере использовать файловые возможности браузера.
Для получения подробной информации см.:
lists.what WG.org/Piper mail/me…

конец

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

На самом деле, полное копирование и вставка не могут быть идеальными.При различных ограничениях браузера будет много проблем, которые не могут поддерживать согласованность и могут выжить только в трещинах. Если исходное содержимое, которое необходимо скопировать и вставить, соответствуетhtmlЕсли она стандартизирована, с ней будет очень просто работать; если она такая же, как сцена, с которой я столкнулся, данные в основномJSON, то потребуется много энергии, чтобы справиться с преобразованием между данными и решить некоторые граничные проблемы.

Справочная статья