Использование Service Worker - Интерфейсы веб API

Использование Service Worker
============================

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

Предпосылки появления Service Workers
-------------------------------------

Одной из важнейших проблем, от которой страдали пользователи веб-приложений, была работа в условиях потери связи. Лучшее в мире веб-приложение оставит ужасное впечатление от использования, если вы не сможете его загрузить. Предпринималось много попыток создания технологий, которые бы решили эту проблему, и если верить страницам нашего Форума, некоторые из вопросов были решены. Но всё же наиважнейшей проблемой по-прежнему является отсутствие хорошего механизма для управления кешем ресурсов и настраиваемыми сетевыми запросами.

Предыдущей попыткой была технология AppCache, казавшаяся хорошей идеей, потому как позволяла действительно просто указывать ресурсы для кеширования. Однако, эта технология допускает много предположений о том, что вы пытаетесь сделать, и затем с грохотом ломается, когда ваше приложение работает не в точности с этими допущениями. Чтобы получить больше информации по этой теме, прочитайте (неудачно названную, но хорошо написанную) статью Джейка Арчибальда Application Cache is a Douchebag.

**Примечание:** Начиная с Firefox 44, когда используется AppCache для предоставления странице поддержки офлайн-режима, в консоли разработчика отображается предупреждение о том, что нужно использовать технологию Service Workers (Firefox bug 1204581.)

Технология Service Workers должна в итоге решить озвученные выше вопросы. Синтаксис Service Worker более сложен, чем тот же AppCache, но взамен вы можете посредством JavaScript контролировать AppCache-подразумеваемое поведение с высокой степенью детализации, что позволяет вам решить описанную проблему и многие другие. Используя Service Worker, вы можете без труда получить приложение, использующее в первую очередь кешированные ресурсы, предоставляя тем самым поведение по умолчанию в автономном режиме, до того как будет получено по сети больше данных (такой подход называется Offline First). Так обычно работают нативные приложения, что часто является причиной выбора пользователя в их пользу.

Что нужно настроить, чтобы поиграть с Service Worker
----------------------------------------------------

Многие функции Service Worker теперь включены по умолчанию в новых браузерах, поддерживающих эту технологию. Однако, если вы обнаружите, что демонстрационный код не работает в вашей версии браузера, вам может понадобиться их включить:

* **Firefox Nightly**: Перейдите в раздел `about:config` и установите параметр `dom.serviceWorkers.enabled` в значение true; затем перезапустите браузер.
* **Chrome Canary**: Перейдите в раздел `chrome://flags` и включите `experimental-web-platform-features`; перезапустите браузер (заметьте, что некоторые функции теперь включены по умолчанию в браузере Chrome.)
* **Opera**: Перейдите в раздел `opera://flags` и включите `Support for ServiceWorker`; перезапустите браузер.
* **Microsoft Edge**: Перейдите в раздел `about:flags` и поставьте галочку `Enable service workers`; перезапустите браузер.

Также вам необходимо предоставлять ваш код по протоколу HTTPS — Service Worker требует этого по соображениям безопасности. По этой причине GitHub — хороший выбор для экспериментов, поскольку он поддерживает протокол HTTPS по умолчанию. Для облегчения локальной разработки браузеры считают `localhost` также безопасным origin.

Базовая архитектура
-------------------

Чтобы сделать базовую настройку Service Worker, как правило, нужно пройти следующие шаги:

1. URL сервис-воркера опрашивается и регистрируется посредством вызова метода `ServiceWorkerContainer.register()`.
2. Если регистрация прошла успешно, то сервис-воркер начинает работать внутри `ServiceWorkerGlobalScope` (en-US); это, по сути, особый вид контекста воркера, работающий вне главного потока браузера, без доступа к DOM.
3. Теперь сервис-воркер может обрабатывать события.
4. Установка сервис-воркера начинается после того, как все контролируемые им страницы закешированы и доступны для последующего использования. Событие `install` всегда посылается первым воркеру (оно может быть использовано для запуска начальной загрузки данных в IndexedDB, для кеширования ресурсов). Данный этап сродни процедуре установки нативного или FirefoxOS-приложения — все делается доступным для использования в офлайн-режиме.
5. Как только обработчик события `oninstall` завершит свою работу, сервис-воркер считается установленным.
6. Далее следует активация. После того как воркер установлен, он получает событие `onactivate`, которое обычно используется для очистки ресурсов, задействованных в предыдущей версии скрипта сервис-воркера.
7. Сервис-воркер здесь может контролировать страницы, но только в случае, если те открыты после успешного вызова `register()`. То есть документ может начать жизнь с сервис-воркером или даже без него и продолжать нормально работать. Поэтому документы должны быть перезагружены, чтобы действительно быть подконтрольными сервис-воркеру.

![](/ru/docs/Web/API/Service_Worker_API/Using_Service_Workers/sw-lifecycle.png)

Следующий рисунок кратко показывает доступные события Service Worker:

![install, activate, message, fetch, sync, push](/ru/docs/Web/API/Service_Worker_API/Using_Service_Workers/sw-events.png)

### Промисы

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

Промисы могут делать много всего, но сейчас вам достаточно знать, что если что-то возвращает промис, то вы можете дописать вызов метода `.then()`, передав ему функцию на случай удачного выполнения, и вызов метода `.catch()`, если хотите передать функцию при неуспешном выполнении.

Давайте сравним структуру традиционного синхронного колбэка с его промис-эквивалентом.

#### sync

```js
try {
  const value = myFunction();
  console.log(value);
} catch (err) {
  console.log(err);
}
```

#### async

```js
myFunction()
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.log(err);
  });
```

В первом примере код, идущий за вызовом функции `myFunction()`, будет ждать завершения вызова и возврата значения. Во втором примере `myFunction()` возвращает промис для `value`, в этом случае, последующий код сможет выполняться, не дожидаясь завершения основной работы функции. Когда промис разрешится, код, переданный методу `then`, будет выполнен асинхронно.

А вот вам реальный пример: что, если мы хотим загружать изображения динамически, к тому же мы желаем удостовериться, что изображения загрузились до того, как они будут показаны? То, что мы хотим сделать, является стандартной задачей, но она всё же может доставить головной боли. Мы можем использовать `.onload`, чтобы показать изображение только после загрузки, но что делать с событиями, которые могут произойти до того, как мы начнём их слушать? Мы могли бы использовать `.complete`, но оно все ещё ненадёжно, да и что делать с повторяющимися изображениями? И наконец все это работает синхронно, блокируя главный поток.

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

**Примечание:** Реальные реализации сервис-воркеров скорее всего будут использовать `onfetch`, а не устаревающий XMLHttpRequest API. Эти возможности не используются здесь, так что можете сосредоточиться на изучении промисов.

```js
const imgLoad = (url) => {
  return new Promise((resolve, reject) => {
    var request = new XMLHttpRequest();
    request.open("GET", url);
    request.responseType = "blob";

    request.onload = () => {
      if (request.status == 200) {
        resolve(request.response);
      } else {
        reject(
          Error(
            "Image didn't load successfully; error code:" + request.statusText,
          ),
        );
      }
    };

    request.onerror = () => {
      reject(Error("There was a network error."));
    };

    request.send();
  });
};
```

Мы возвращаем новый промис, созданный конструктором `Promise()`, который в качестве аргумента принимает функцию с параметрами `resolve` и `reject`. Где-то внутри функции мы должны определить случаи, при которых промис должен быть разрешён или отклонён, — в нашем случае, в зависимости от того, вернулся ли статус 200 ОК или нет, будут вызваны `resolve` в случае успеха или `reject` при неудаче. Последующее содержимое этой функции — вполне стандартное XHR-наполнение, поэтому на данный момент не стоит о нем волноваться.

Вызывая функцию `imgLoad()`, мы ожидаемо передаём в качестве параметра url изображения, которое хотим загрузить, но далее код немного отличается:

```js
let body = document.querySelector("body");
let myImage = new Image();

imgLoad("myLittleVader.jpg").then(
  (response) => {
    var imageURL = window.URL.createObjectURL(response);
    myImage.src = imageURL;
    body.appendChild(myImage);
  },
  (Error) => {
    console.log(Error);
  },
);
```

После вызова функции мы "цепляем" к ней вызов промис-метода `then()`, которому в качестве параметров передаём две функции - первая будет вызвана в случае выполнения промиса, созданного вызовом функции `imgLoad()`, вторая функция будет вызвана в случае отклонения этого промиса. В случае выполнения мы показываем изображение в элементе `myImage`, который прикрепляем к body (аргументом является `request.response`, помещённый в промис-методе `resolve`); в случае отклонения промиса в консоли будет отображено сообщение об ошибке.

Все это происходит асинхронно.

**Примечание:** вы можете также объединять вызов нескольких промис-методов в одну цепочку, как в этом примере:
 `myPromise().then(success, failure).then(success).catch(failure);`

**Примечание:** вы можете получить гораздо больше информации о промисах, прочитав превосходную статью Джейка Арчибальда (Jake Archibald's) JavaScript Promises: there and back again.

Демонстрация Service Workers
----------------------------

Чтобы продемонстрировать только базовые моменты регистрации и установки сервис-воркеров, мы создали простое демо-приложение, названое sw-test. Это простая галерея изображений "Star wars Lego". Оно использует промис-функции, чтобы прочитать из JSON-объекта и загрузить, используя технологию Ajax, изображения, находящиеся далее нижнего края страницы, до того как они будут показаны. В приложении также ещё регистрируется, устанавливается и активируется сервис-воркер, и, в случае если браузер поддерживает спецификацию Service Worker, запрашиваемые ресурсы будут закешированы, и приложение будет работать в офлайн-режиме!

![](/ru/docs/Web/API/Service_Worker_API/Using_Service_Workers/demo-screenshot.png)

Вы можете посмотреть исходный код на GitHub, а также этот живой пример. Единственное, что мы тут рассмотрим, это промис (смотрите app.js строки 22-47), модифицированная версия того, о котором вы читали выше в разделе Тестовая демонстрация промисов. Разница в следующем:

1. Ранее мы передавали параметром лишь URL изображения, которое мы хотели загрузить. Теперь же, мы передаём JSON-фрагмент, содержащий все данные для изображения (посмотрите, как это выглядит в image-list.js). Это сделано потому, что все данные для выполнения каждого промиса должны быть переданы ему, так как он выполняется асинхронно. Если же вы передали лишь URL, а чуть позже попытались получить доступ к другим атрибутам в JSON-фрагменте внутри цикла `for()`, это бы не сработало, так как этот промис не был бы выполнен во время текущей итерации цикла (это синхронный процесс).
2. Теперь мы выполняем промис с параметром-массивом, так как дальше мы хотим сделать загруженные данные изображения доступными для разрешающей функции, одновременно с именем файла, данными авторства и замещающим текстом (см. app.js строки 31-34). Промисы будут выполняться со всего одним аргументом, поэтому, если вы хотите выполнить их с несколькими параметрами, вы должны использовать массив/объект.
3. Затем, чтобы получить доступ к выполненным значениям промисов, мы обращаемся к ним так, как было задумано (смотрите app.js строки 60-64). По началу это может выглядеть немного странно, но именно так и работают промисы.
Погружение в Service Worker
---------------------------

Итак, переходим к Service Worker!

### Регистрация воркеров

Ниже представлен первый блок кода файла app.js. Это точка входа в Service Worker.

```js
if ("serviceWorker" in navigator) {
  navigator.serviceWorker
    .register("./sw-test/sw.js", { scope: "./sw-test/" })
    .then((reg) => {
      // регистрация сработала
      console.log("Registration succeeded. Scope is " + reg.scope);
    })
    .catch((error) => {
      // регистрация прошла неудачно
      console.log("Registration failed with " + error);
    });
}
```

1. Внешний условный блок выполняет проверку на поддержку Service Worker, чтобы убедиться что технология доступна, до того как начать регистрацию.
2. Далее, чтобы зарегистрировать сервис-воркера для этого сайта, мы используем функцию `ServiceWorkerContainer.register()`. Сервис-воркер представляет собой JavaScript-файл приложения (обратите внимание, что URL указывается относительно "корня", а не места расположения JS-файла, регистрирующего сервис-воркер).
3. Параметр scope - не обязателен, он может быть использован для указания подмножества контента, которое вы хотите отдать под контроль сервис-воркера. В нашем случае, мы указали `'./sw-test/'`. Если вы не укажете его, то будет использовано значение по умолчанию; мы же указали его только в целях иллюстрации.
4. Метод `.then()` был использован для обработки успешной регистрации. Если промис разрешится успешно, то код, переданный этому методу, будет выполнен.
5. Ну и наконец, в конец нашего промиса мы добавляем функцию `.catch()`, которая будет выполнена в случае, если промис будет отклонён.

Предыдущий код регистрирует сервис-воркера, который работает в worker-контексте, и следовательно, не имеет доступа к DOM. Затем вы запускаете код в сервис-воркере, вне ваших страниц, чтобы контролировать их загрузку.

Один сервис-воркер может контролировать несколько страниц. Каждый раз, когда загружается страница, находящаяся в пределах области видимости, сервис-воркер будет установлен на ней и начнёт работу. Поэтому будьте осторожны с применением глобальных переменных в скриптах сервис-воркеров, потому как у каждой страницы нет своего уникального экземпляра сервис-воркера.

**Примечание:** Сервис-воркеры функционально похожи на прокси-серверы, они позволяют модифицировать запросы и ответы, замещая записями из собственного кеша, и многое другое.

**Примечание:** Есть одна очень хорошая особенность при работе с сервис-воркерами. В случае, если вы используете функциональность проверки поддержки Service Worker, то приложение в браузерах, не имеющих поддержки, продолжат нормально работать с ожидаемым поведением. Кроме того, если браузер поддерживает только AppCache, то будет использована эта технология. В случае, если браузер поддерживает и AppCache и Service Worker, то будет использована Service Worker.

#### Почему мой сервис-воркер не прошёл регистрацию?

Это может произойти по следующим причинам:

1. Приложение загружено не по протоколу HTTPS.
2. Путь к сервис-воркеру указан некорректно — он должен быть написан относительно origin запроса, а не вашей корневой директории с приложением. В нашем примере воркер расположен в `https://mdn.github.io/sw-test/sw.js`, корневая папка — `https://mdn.github.io/sw-test/`. Но в качестве пути к сервис-воркеру нужно указывать `/sw-test/sw.js`, а не `/sw.js`.
3. Origin сервис-воркера отличается от origin вашего приложения. Это также запрещено.

![](/ru/docs/Web/API/Service_Worker_API/Using_Service_Workers/important-notes.png)

Также обратите внимание:

* В сервис-воркер будут попадать только те запросы, которые соответствуют его области видимости.
* Максимальная область видимость сервис-воркера равна его location.
* Если ваш сервис-воркер работает на клиенте, которому был передан заголовок `Service-Worker-Allowed`, вы можете указать список максимальных областей видимости для этих воркеров.
### Установка и активация: заполнение кеша

После того как ваш сервис-воркер будет зарегистрирован, браузер может попробовать установить его и активировать на странице/сайте.

Событие install возникает после того как установка успешно завершится. Это событие используется главным образом для того, чтобы заполнить кеш браузера ресурсами, необходимыми для успешного запуска в офлайн-режиме. Для этого используется новый API хранилища Service Worker — `cache` — глобальный для всех сервис-воркеров, который позволяет нам хранить результаты запросов, используя в качестве ключа для их получения сами запросы. Этот API работает аналогично стандартному кешу браузера, но только для вашего домена. Данные в кеше сохраняются до тех пор, пока вы сами не решите их удалить — вы имеете полный контроль.

**Примечание:** Cache API поддерживается не всеми браузерами (смотрите раздел Browser support чтобы получить больше информации). Если вы хотите сейчас использовать эту технологию, то можете рассмотреть возможность использования полифила, который доступен в Google's Topeka demo, или можете хранить ресурсы в IndexedDB.

Давайте начнём этот раздел посмотрев на фрагмент кода ниже — это первый блок кода, который вы увидите в нашем сервис-воркере:

```js
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("v1").then((cache) => {
      return cache.addAll([
        "./sw-test/",
        "./sw-test/index.html",
        "./sw-test/style.css",
        "./sw-test/app.js",
        "./sw-test/image-list.js",
        "./sw-test/star-wars-logo.jpg",
        "./sw-test/gallery/",
        "./sw-test/gallery/bountyHunters.jpg",
        "./sw-test/gallery/myLittleVader.jpg",
        "./sw-test/gallery/snowTroopers.jpg",
      ]);
    }),
  );
});
```

1. Здесь мы добавляем обработчик события `install` к сервис-воркеру (отныне `self`), и затем вызываем метод `ExtendableEvent.waitUntil()` объекта события. Такая конструкция гарантирует, что сервис-воркер не будет установлен, пока код, переданный внутри `waitUntil()`, не завершится с успехом.
2. Внутри `waitUntil()` мы используем метод caches.open(), чтобы создать новый кеш, который назовём `v1`, это будет первая версия кеша ресурсов. Этот метод возвращает промис для созданного кеша; когда он выполнится, у объекта созданного кеша мы вызовем метод `addAll()`, который в качестве параметра ожидает получить массив origin-относительных URL всех ресурсов, которые мы хотим хранить в кеше.
3. Если промис будет отклонён, то установка будет завершена неудачно, и воркер ничего не сделает. Это хорошо, потому как вы можете исправить свой код и затем попробовать провести регистрацию в следующий раз.
4. После успешной установки сервис-воркер активируется. Этот момент не очень важен при первоначальной установке/активации сервис-воркера, в то же время он имеет большое значение, когда происходит обновление воркера (смотрите раздел Обновление вашего сервис-воркера, находящийся ниже).

**Примечание:** localStorage (en-US) работает схожим образом, но в синхронном режиме, поэтому запрещён в сервис-воркерах.

**Примечание:** При необходимости хранить данные в сервис-воркерах вы можете использовать IndexedDB.

### Настраиваемые ответы на запросы

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

![](/ru/docs/Web/API/Service_Worker_API/Using_Service_Workers/sw-fetch.png)

Событие `fetch` возникает каждый раз, когда запрашиваются любые подконтрольные сервис-воркеру ресурсы, к которым относятся документы из области видимости и другие ресурсы, связанные с этими документами (например, если в index.html происходит кросс-доменный запрос для загрузки изображения, то он тоже попадёт в сервис-воркер).

Вы можете подключить к сервис-воркеру обработчик события `fetch` и внутри него на объекте события вызвать метод `respondWith()`, чтобы заменить ответы и показать собственную "магию".

```js
self.addEventListener("fetch", (event) => {
  event
    .respondWith
    // магия происходит здесь
    ();
});
```

Для начала, на каждый сетевой запрос мы можем отдать в ответ ресурс, чей url соответствует запросу:

```js
self.addEventListener("fetch", (event) => {
  event.respondWith(caches.match(event.request));
});
```

`caches.match(event.request)` позволяет нам проверять сетевой запрос ресурса на соответствие какому-либо доступному в кеше ресурсу, если такой ресурс имеется. Соответствие проверяется по url и изменяемым заголовкам.

Давайте рассмотрим несколько других вариантов реализации нашей магии (чтобы получить больше информации об интерфейсах `Request` и `Response` смотрите документацию к Fetch API.)

1. Конструктор ``Response()`` позволяет вам создавать собственные ответы. В данном случае, мы всего лишь возвращаем простую текстовую строку:

```js
new Response("Hello from your friendly neighbourhood service worker!");
```

В этом более сложном объекте Response показано, как вы можете передать набор заголовков в свой ответ, эмулируя стандартный HTTP-ответ. Здесь мы просто сообщаем браузеру, чем является содержимое ответа:

```js
new Response(
  "<p>Hello from your friendly neighbourhood service worker!</p>",
  {
    headers: { "Content-Type": "text/html" },
  },
);
```

2. Если совпадение не было найдено в кеше, вы можете попросить браузер `загрузить` (en-US) тот же ресурс, чтобы получить новый файл через обычную сеть, если она доступна:

```js
fetch(event.request);
```

3. Если информация, соответствующая запросу, в кеше не найдена, а также сеть не доступна, то вы можете просто ответить на запрос какой-либо страницей по умолчанию, которая хранится в кеше, используя `match()`:

```js
caches.match("./fallback.html");
```

4. Вы можете получить больше информации о каждом запросе, используя для этого свойства объекта `Request`, который можно получить как свойство объекта `FetchEvent`:

```js
event.request.url;
event.request.method;
event.request.headers;
event.request.body;
```

Восстановление неудачных запросов
---------------------------------

Итак, `caches.match(event.request)` отработает как нужно только в том случае, если в кеше сервис-воркера будет найдено соответствие запросу. Но что произойдёт, если такого соответствия не будет найдено? Если мы не предоставим никакого механизма обработки такой ситуации, то промис выполнится со значением `undefined` и мы не получим никакого значения.

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

```js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    }),
  );
});
```

Если промис будет отклонён, функция `catch()` вернёт обычный сетевой запрос к внешнему ресурсу. Это значит, что, если сеть доступна, то ресурс просто загрузится с сервера.

Если же мы были достаточно умны, то мы не стали бы просто возвращать сетевой запрос, а сохранили бы его результат в кеше, чтобы иметь возможность получить его в офлайн-режиме. В случае с нашим демо-приложением "Star Wars gallery", это означает, что, если в галерею будет добавлено ещё одно изображение, то оно будет получено и сохранено в кеше:

```js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((resp) => {
      return (
        resp ||
        fetch(event.request).then((response) => {
          return caches.open("v1").then((cache) => {
            cache.put(event.request, response.clone());
            return response;
          });
        })
      );
    }),
  );
});
```

Здесь мы возвращаем обычный сетевой запрос, который возвращён вызовом `fetch(event.request);` этот запрос также является промисом. Когда промис разрешится, мы получим кеш вызвав `caches.open('v1');` этот метод также возвращает промис. Когда разрешится уже второй промис, будет использован вызов `cache.put()`, чтобы поместить ресурс в кеш. Ресурс получен через `event.request`, а ответ — через клонирование `response.clone()`. Клон помещается в кеш, а оригинальный ответ передаётся браузеру, который передаёт его странице, которая запросила ресурс.

Почему? Потому что потоки запроса и ответа могут быть прочитаны только единожды. Чтобы ответ был получен браузером и сохранён в кеше, нам нужно клонировать его. Так оригинальный объект отправится браузеру, а клон будет закеширован. Оба они будут прочитаны единожды.

У нас все ещё остаётся единственная проблема - если на какой-либо запрос в кеше не будет найдено соответствие, и в этот момент сеть не доступна, то наш запрос завершится неудачно. Давайте реализуем запасной вариант по умолчанию, при котором пользователь, в описанном случае, будет получать хоть что-нибудь:

```js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches
      .match(event.request)
      .then((resp) => {
        return (
          resp ||
          fetch(event.request).then((response) => {
            let responseClone = response.clone();
            caches.open("v1").then((cache) => {
              cache.put(event.request, responseClone);
            });

            return response;
          })
        );
      })
      .catch(() => {
        return caches.match("./sw-test/gallery/myLittleVader.jpg");
      }),
  );
});
```

Здесь мы решили обрабатывать только картинки, потому что единственные запросы, которые могут не удастся — это загрузка новых картинок, так как все остальное было закешировано во время обработки события `install`, которое мы обсуждали ранее.

Обновление вашего сервис-воркера
--------------------------------

Если после того, как ваш сервис-воркер был установлен, стала доступна его новая версия, при обновлении или загрузке страницы она будет установлена в фоновом режиме, но не будет активирована. Она будет активирована, лишь когда не останется ни одной страницы, использующей старую версию сервис-воркера.

Вы можете обновить обработчик события install в новой версии сервис-воркера, чтобы получить примерно такое (обратите внимание на номер новой версии):

```js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v2').then((cache) => {
      return cache.addAll([
        './sw-test/',
        './sw-test/index.html',
        './sw-test/style.css',
        './sw-test/app.js',
        './sw-test/image-list.js',

        …

        // подключение прочих ресурсов для новой версии...
      ]);
    })
  );
});
```

В то время, как этот код работает, предыдущая версия также доступна. Новая версия устанавливается в фоновом режиме. Мы можем работать с версией кеша v2, в то время как версия v1 не будет разрушена.

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

### Удаление старого кеша

Вы можете обрабатывать событие activate. Оно обычно используется в случае, если нужно выполнить такие действия, которые бы нарушили работу воркеров предыдущей версии, если они все ещё работают со старым кешем. Также это событие полезно использовать для того, чтобы удалить ненужные данные, чтобы освободить место, занимаемое на диске, потому что каждый браузер имеет жёсткие ограничения на размер хранилища кеша, которое доступно для использования сервис-воркерами.

Promise, переданный в `waitUntil()`, заблокирует другие события до своего завершения, поэтому можно быть уверенным, что процесс очистки закончится раньше, чем выполнится первое событие `fetch` на основе нового кеша.

```js
self.addEventListener("activate", (event) => {
  var cacheKeeplist = ["v2"];

  event.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(
        keyList.map((key) => {
          if (cacheKeeplist.indexOf(key) === -1) {
            return caches.delete(key);
          }
        }),
      );
    }),
  );
});
```

Инструменты разработчика
------------------------

В браузере Chrome есть раздел `chrome://inspect/#service-workers`, где можно увидеть активность сервис-воркеров и их хранилища. Также есть раздел `chrome://serviceworker-internals`, в котором можно получить более детальную информацию, запустить, остановить и отладить процессы сервис-воркеров. В будущем ожидается поддержка эмуляции различного качества сетевых соединений и их отсутствия, что будет весьма кстати.

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

* Вы можете пройти в раздел
 about:debugging
 , чтобы посмотреть зарегистрированные сервис-воркеры и обновить или удалить их.
* Во время тестирования вы можете снять ограничение HTTPS, поставив флажок "Enable Service Workers over HTTP (when toolbox is open)" в настройках Firefox Developer Tools.
* Кнопка "Забыть", включаемая в настройках «Персонализация» в Firefox, позволяет удалить все сервис-воркеры и очистить их кеши.

**Примечание:** вы можете держать своё приложение на `http://localhost` (например, используя `me@localhost:/my/app$ python -m SimpleHTTPServer`) для локальной разработки. См. Security considerations

Смотрите также
--------------

* The Service Worker Cookbook
* Готов ли ServiceWorker для использования?
* Скачать Service Workers 101 cheatsheet.
* Promises
* Using web workers (en-US)


HTMLTableElement.insertRow() - Интерфейсы веб API

HTMLTableElement.insertRow()
============================

Метод **`HTMLTableElement.insertRow()`**`добавляет новую строку в таблицу и возвращает на неё ссылку.`

Синтаксис
---------
```

var row = HTMLTableElement.insertRow(optional index = -1);
```

* `HTMLTableElement` — ссылка на *HTML table* элемент.
* `index` — индекс новой строки
* `row` присваивается ссылка на новую строку. Ссылка на HTMLTableRowElement (en-US).
 Если значение `index` равно -1 или количеству строк, то строка добавляется как последняя. Если значение `index` превышает количество строк, *выбрасывается* исключение IndexSizeError. Если аргумент index пропущен, он равен значению по умолчанию — -1.
* Если таблица содержит несколько элементов `tbody`, то, по умолчанию, новая строка будет добавлена в последний элемент `tbody`. Добавить строку в определённый элемент `tbody`:
 `var specific_tbody=document.getElementById(tbody_id); var row=specific_tbody.insertRow(index)`
Пример
------

```html
<table id="TableA">
  <tr>
    <td>Old top row</td>
  </tr>
</table>
<script type="text/javascript">
 function addRow(tableID) {
 // Get a reference to the table
 var tableRef = document.getElementById(tableID);

 // Insert a row in the table at row index 0
 var newRow = tableRef.insertRow(0);

 // Insert a cell in the row at index 0
 var newCell = newRow.insertCell(0);

 // Append a text node to the cell
 var newText = document.createTextNode("New top row");
 newCell.appendChild(newText);
 }

 // Call addRow() with the ID of a table
 addRow("TableA");
</script>
```

Чтобы быть валидным HTML документом, элемент TR должен содержать хотя бы один TD элемент.

Обратите внимание, что `insertRow` добавляет строку непосредственно в таблицу и возвращает ссылку на эту строку. Строку не нужно добавлять отдельно, как в случае с методом `document.createElement()`, для создания нового TR элемента.

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-table-insertrow-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* `HTMLTableRowElement.insertCell()` (en-US)


BroadcastChannel.postMessage() - Интерфейсы веб API

BroadcastChannel.postMessage()
==============================

Baseline 2022
-------------

Newly availableSince March 2022, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.

* Learn more
* See full compatibility
* Report feedback
**`BroadcastChannel.postMessage()`** отправляет сообщение, которое может быть любым `объектом`, каждому обработчику в контексте браузера с тем же origin. Сообщение передаётся в виде события `message` к каждому `BroadcastChannel`, привязанному к данному каналу.

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------
```

var str = channel.postMessage(object);
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-broadcastchannel-postmessage-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* `BroadcastChannel`, интерфейс, к которому относится **`postMessage`**.


Locating DOM elements using selectors - Интерфейсы веб API

Locating DOM elements using selectors
=====================================

Selectors API предоставляет методы, с помощью которых можно быстро и просто получить доступ к узлам `Element` из DOM путём сопоставления с набором селекторов. Это намного быстрее, чем прошлые техники, где надо было, например, использовать цикл в JS-коде, чтобы найти конкретные элементы.

Интерфейс NodeSelector (The NodeSelector interface)
---------------------------------------------------

Эта спецификация добавляет два новых метода к любым объектам, реализующим интерфейс `Document` (en-US), `DocumentFragment` (en-US), или `Element`:

`querySelector`

Возвращает первый совпадающий узел `Element` внутри поддерева. Если совпадающих узлов нет, будет возвращён `null`.

`querySelectorAll`

Возвращает `NodeList` (en-US), содержащий все подходящие узлы `Element` внутри поддерева узлов. Или возвращает пустой `NodeList`, если совпадений не найдено.

**Примечание:** `NodeList` (en-US), возвращаемый методом `querySelectorAll()`, не настоящий. Этот список отличается от других методов поиска DOM, которые возвращают настоящие (живые) узлы.

Вы можете найти примеры и детали, прочитав документацию для методов `querySelector()` (en-US) и `querySelectorAll()` (en-US), а также в статье Code snippets for querySelector.

Selectors
---------

Селекторные методы принимают один или больше селекторов, разделённых запятыми, чтобы определить, какие элементы должны быть возвращены. Например, чтобы все параграфы в документе, которые имеют классы `warning` или `note`, можно сделать следующее:
```

var special = document.querySelectorAll( "p.warning, p.note" );
```

Также можно искать по ID. Например:
```

var el = document.querySelector( "#main, #basic, #exclamation" );
```

После выполнения кода выше, `el` будет содержать первый элемент в документе, чей ID `main, basic` или `exclamation`

Вы можете использовать любые CSS-селекторы в методах `querySelector(), querySelectorAll()`

See also
--------

* Selectors API
* `element.querySelector` (en-US)
* `element.querySelectorAll` (en-US)
* `document.querySelector` (en-US)
* `document.querySelectorAll` (en-US)
* Code snippets for querySelector


performance.now() - Интерфейсы веб API

performance.now()
=================

Краткое описание
----------------

Метод **`performance.now()`** возвращает временную метку `DOMHighResTimeStamp`, измеряемую в миллисекундах, с точностью до одной тысячной миллисекунды.

Значение, представленное типом `DOMHighResTimeStamp`, изменяется в зависимости от контекста. Стоит иметь ввиду следующее:

* в разделяемых или выделенных потоках выполнения, началом эпохи считается момент начала работы потока.
* вне потоков выполнения, или в выделенных потоках, созданных в контексте `Window`, началом эпохи считается значение свойства `PerformanceTiming.navigationStart` (en-US).
* в выделенных потоках выполнения, созданных из другого потока, началом эпохи устанавливается начало эпохи этого самого потока.
Синтаксис
---------
```

t = performance.now();
```

Пример
------

```js
var t0 = performance.now();
doSomething();
var t1 = performance.now();
console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
```

В отличие от других временны́х типов данных, доступных в JavaScript (таких как `Date.now` (en-US)), метки времени, возвращённые из `performance.now(),` не ограничены кратностью в одну миллисекунду, а представляют значение времени как число с плавающей запятой, с точностью до микросекунд.

Также, в противоположность `Date.now`, значение, возвращаемое из `performance.now()`, всегда возрастает с постоянной скоростью, независимо от системного времени (которое может быть установлено вручную или изменено программами наподобие NTP).

Спецификации
------------

| Specification |
| --- |
| High Resolution Time # dom-performance-now |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* When milliseconds are not enough: performance.now() от HTML5 Rocks.


History.pushState() - Интерфейсы веб API

History.pushState()
===================

В HTML документе метод **`history.pushState()`** добавляет новое состояние в историю браузера

Синтаксис
---------
```

history.pushState(state, title[, url])
```

### Параметры

`state`

Объект состояния – это JavaScript-объект, связанный с новой записью в истории, созданной `pushState()`. Всякий раз, когда пользователь переходит к новому состоянию, происходит событие `popstate`, а свойство `state` этого события содержит копию объекта состояния с записями истории.

Объект состояния может быть чем угодно, что может быть сериализовано. Поскольку Firefox сохраняет объекты состояния на диске пользователя, чтобы их можно было восстановить после перезапуска браузера, мы накладываем ограничение в 640 тысяч символов на сериализованное представление объекта состояния. Если вы передаёте объект состояния, чьё сериализованное представление больше этого значения, метод `pushState()` выдаст исключение. Если вам нужно хранилище большего размера, следует рассмотреть использование `sessionStorage` и/или `localStorage`.

`title`

Заголовок - все браузеры, кроме Safari, на данный момент игнорируют этот параметр, но могут начать использовать в будущем. Ввиду будущих изменений метода, безопасным решением является передача пустой строки. В качестве альтернативы вы можете передать короткий заголовок для состояния, в которое переходите.

`url` Необязательный

Через этот параметр передаётся URL-адрес новой записи в истории. Обратите внимание, что браузер не будет пытаться загрузить данный URL сразу после вызова `pushState()`, но может попытаться сделать это позже, например, после того, как пользователь перезапустит браузер. Новый URL-адрес не обязан быть абсолютным; если он относительный, то определяется относительно текущего URL. Новый URL должен вести на тот же домен, протокол и порт, иначе `pushState()` выдаст исключение. Данный параметр не является обязательным; если он не указан, будет использоваться URL текущего документа.

Описание
--------

Вызов `pushState()` в некоторой степени похож на установку `window.location = "#foo"`, поскольку они оба также создают и активируют ещё одну запись в истории, связанную с текущим документом.

Но у `pushState()` есть несколько преимуществ:

* Новый URL может быть любым в пределах того же домена, порта и протокола, что и текущий адрес. Тогда как настройка `window.location` оставляет вас на том же `document` лишь в том случае, если вы меняете только хэш
* Менять URL не обязательно. Тогда как настройка `window.location = "#foo";` создаёт новую запись в истории, только если текущий хеш не `#foo`
* С новой записью в истории можно связать любые данные. В подходе, основанном на хеше, все соответствующие данные нужно кодировать в короткую строку
* Если заголовок `title` впоследствии используется браузерами, эти данные могут быть использованы (независимо от, скажем, хеша).

Обратите внимание, что `pushState()` никогда не вызывает событие `hashchange`, даже если новый URL отличается от старого только хешем.

Примеры
-------

Создание новой записи истории браузера, задавая *state*, *title*, и *url*.

### JavaScript
```

const state = { 'page_id': 1, 'user_id': 5 }
const title = ''
const url = 'hello-world.html'

history.pushState(state, title, url)
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-history-pushstate-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Работа с History API


History.length - Интерфейсы веб API

History.length
==============

**`History.length`** — это свойство объекта History, доступное только для чтения, которое возвращает число (Integer), обозначающее количество элементов в истории сессии, в том числе для загруженной страницы. Например, если открыть страницу в новой вкладке это свойство вернёт 1.

Синтаксис
---------
```

length = history.length;
```

Примеры
-------

```js
var result = window.history.length; // Вернёт размер истории текущей сессии
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-history-length-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* `History`


History.state - Интерфейсы веб API

History.state
=============

Свойство **`History.state`** возвращает значение последнего состояния стека истории (history stack). Этим способом можно проверить состояние не дожидаясь события `popstate (en-US)`.Синтаксис
---------
```

const currentState = history.state
```

### Значение

Последнее состояние стека истории. Значение равно `null` до тех пор, пока не будет применён метод `pushState()` или `replaceState()`.

Примеры
-------

Код, приведённый ниже, выводит в консоль значение `history.state` до и после применения метода `pushState()`, который записывает значение в историю.

```js
// Выведет null потому что мы пока не изменяли стек истории
console.log(`History.state before pushState: ${history.state}`);

// Теперь запишем какое-нибудь значение в стек
history.pushState({ name: "Example" }, "pushState example", "page3.html");

// Выведет значение history.state, которое было записано в предыдущей строке
console.log(`History.state after pushState: ${history.state}`);
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-history-state-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Working with the History API


History.go() - Интерфейсы веб API

History.go()
============

Метод **`History.go()`** выполняет переход на определённую страницу в истории текущей сессии. С его помощью можно перемещаться как вперёд, так и назад, в зависимости от значения параметра.

Данный метод является асинхронным. Добавьте обработчик для события `popstate (en-US)`, чтобы определить, когда переход закончен.

Синтаксис
---------
```

history.go([delta])
```

### Параметры

`delta` Необязательный

Относительное положение в истории, куда вы хотите переместиться. Отрицательные значения перемещают назад, а положительные — вперёд. Например, `history.go(2)` перемещает вперёд на 2 страницы, а `history.go(-2)` перемещает на 2 страницы назад. Если метод вызван без параметра или же `delta` равна 0, результат выполнения будет такой же, как у `location.reload()`.

Примеры
-------

Перемещение на 1 страницу назад (аналог вызова `back()` (en-US)):

```js
history.go(-1);
```

Перемещение на страницу вперёд, как `forward()` (en-US):

```js
history.go(1);
```

Перемещение на 2 страницы вперёд:

```js
history.go(2);
```

Перемещение на 2 страницы назад:

```js
history.go(-2);
```

Также, оба этих вызова перезагрузят страницу:

```js
history.go();
history.go(0);
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-history-go-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `History`
* `back()` (en-US)
* `forward()` (en-US)
* `popstate (en-US)` event
* Working with the History API


History.replaceState() - Интерфейсы веб API

History.replaceState()
======================

Метод **`History.replaceState()`** изменяет текущую запись в истории, заменяя её на значения `stateObj`, `title` и `URL`, передаваемые в параметрах метода. Данный метод особенно полезен, когда вы хотите обновить объект состояния или URL текущей записи в истории в ответ на какое-то действие пользователя.

Синтаксис
---------
```

history.replaceState(stateObj, title, [url])
```

### Параметры

`stateObj`

Объект состояния – это JavaScript-объект, связанный с записью в истории, переданной в метод `replaceState()`. Объект состояния может быть `null`.

`title`

Заголовок - все браузеры, кроме Safari, на данный момент игнорируют этот параметр, но могут начать использовать в будущем. Ввиду будущих изменений метода, безопасным решением является передача пустой строки. В качестве альтернативы вы можете передать короткий заголовок для состоянии, в которое переходите.

`url` Необязательный

URL-адрес записи в истории. Новый URL должен вести на тот же домен, протокол и порт, иначе pushState() выдаст исключение.

Примеры
-------

Предположим, на странице `http://mozilla.org/foo.html` выполняется следующий JavaScript-код:
```

let stateObj = { foo: "bar" }
history.pushState(stateObj, "page 2", "bar.html")
```

Объяснение этих двух строк можно найти в приведённом выше разделе *пример метода pushState()*.

Далее, предположим, на странице `http://mozilla.org/bar.html` выполняется JavaScript-код:
```

history.replaceState(stateObj, "page 3", "bar2.html")
```

Это приведёт к тому, что в URL-строке отобразится адрес `http://mozilla.org/bar2.html`, но браузер не станет сразу загружать `bar2.html` и даже не станет проверять наличие этой страницы `bar2.html`.

Теперь предположим, что пользователь переходит по адресу `http://www.microsoft.com`, а затем нажимает на кнопку **"Назад"**. В этом случае в URL-строке отобразится `http://mozilla.org/bar2.html`. Если же пользователь снова нажмёт на кнопку **"Назад"**, в URL-строке отобразится `http://mozilla.org/foo.html` и полностью обойдёт `bar.html`.

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-history-replacestate-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Selection.toString() - Интерфейсы веб API

Selection.toString()
====================

**Экспериментальная возможность:** **Это экспериментальная технология**
Так как спецификация этой технологии ещё не стабилизировалась, смотрите таблицу совместимости по поводу использования в различных браузерах. Также заметьте, что синтаксис и поведение экспериментальной технологии может измениться в будущих версиях браузеров, вслед за изменениями спецификации.

Метод **`Selection.toString()`** возвращает строку, содержащуюся в выделенном объекте, т.е. выделенный текст.

Syntax
------
```

str = sel.toString()
```

### Return value

* *`str`* — это строка, содержащаяся в выделении.
### Parameters

*None (отсутствуют).*

Description
-----------

Этот метод возвращает выделенный текст.

В JavaScript, этот метод вызывается автоматически, когда функция, которой он передаётся, требует строку:

```js
alert(window.getSelection()); // What is called
alert(window.getSelection().toString()); // What is actually being effectively called.
```

Specifications
--------------

| Specification |
| --- |
| Selection API # dom-selection-stringifier |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Selection` (en-US), the interface it belongs to.


Selection.getRangeAt() - Интерфейсы веб API

Selection.getRangeAt()
======================

**Экспериментальная возможность:** **Это экспериментальная технология**
Так как спецификация этой технологии ещё не стабилизировалась, смотрите таблицу совместимости по поводу использования в различных браузерах. Также заметьте, что синтаксис и поведение экспериментальной технологии может измениться в будущих версиях браузеров, вслед за изменениями спецификации.

Метод **`Selection.getRangeAt()`** возвращает объект диапазона, который представляет один из диапазонов, выделенного на данный момент.

Синтаксис
---------
```

range = sel.getRangeAt(index)
```

### Параметры

*`range`*

Объект `Range`, который будет возвращён.

*`index`*

Индекс диапазона, начинающийся с нуля, который необходимо вернуть. Отрицательное значение или значение выше или равное `Selection.rangeCount` (en-US) приведёт к ошибке.

Примеры
-------

```js
var ranges = [];

sel = window.getSelection();

for (var i = 0; i < sel.rangeCount; i++) {
  ranges[i] = sel.getRangeAt(i);
}
/\* Каждый элемент в массиве диапазонов является
 \* объектом диапазона, который представляет
 \* один из диапазонов текущего выделения \*/
```

Спецификации
------------

| Specification |
| --- |
| Selection API # dom-selection-getrangeat |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* `Selection` (en-US), интерфейс, к которому принадлежит метод.
* Tree Selection (для метода `getRangeAt()` в интерфейсе `nsITreeSelection`)


Drag Operations - Интерфейсы веб API

Drag Operations
===============

Ниже описаны шаги, которые происходят при drag and drop операции.

**Примечание:** Drag операции описываются в документе, используя `DataTransfer` интерфейс. Этот документ не использует не`DataTransferItem` (en-US) интерфейс, не`DataTransferItemList` (en-US) интерфейс.

`draggable` атрибуты
--------------------

На веб-странице, в некоторых случаях используется поведение drag (перетаскивания) по умолчанию. Включая выделенный текст, изображения и ссылки. Когда изображение или ссылка переносятся, URL изображения или ссылки устанавливается в качестве данных drag и перетаскивание начинается. Для других элементов, они должны быть частью выделения для выполнения перетаскивания по умолчанию. Чтобы увидеть это в действии, выделите область веб-страницы, а затем нажмите и удерживайте кнопку мыши и перетащите выделение. Появится специфичный для ОС рендеринг выделенного фрагмента и будет следовать за указателем мыши при перетаскивании. Однако это поведение является только drag поведением по умолчанию, если нет обработчиков, определяющих данные для перетаскивания.

В HTML, кроме поведения по умолчанию изображений, ссылок и выделенных областей, никакие другие элементы по умолчанию не переносятся.

Для перетаскивания других HTML-элементов, должны быть выполнены три пункта :

1. Установить `[`draggable`](/ru/docs/Web/HTML/Global_attributes#draggable)="true"` на элемент, который вы хотите сделать перетаскиваемым.
2. Добавить обработчик события ``dragstart (en-US)``.
3. Установить данные перетаскивания (en-US) в обработчик выше.

Вот пример, который позволяет перетаскивать часть содержимого.

```html
<p
 draggable="true"
 ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
  This text <strong>may</strong> be dragged.
</p>
```

Атрибут `[`draggable`](/ru/docs/Web/HTML/Global_attributes#draggable)` установлен в `"true"`, т.о. этот элемент становится перетаскиваемым. Если этот атрибут был опущен или установлен в `"false"`, то элемент не может быть перенесён, и вместо этого будет выбран текст.

Атрибут `[`draggable`](/ru/docs/Web/HTML/Global_attributes#draggable)` может быть использован для любого элемента, включая изображения и ссылки. Однако, для последних двух, значение по умолчанию - `true`, т.о. вы можете только использовать атрибут `[`draggable`](/ru/docs/Web/HTML/Global_attributes#draggable)` со значением `false` для отключение перетаскивания этих элементов.

**Примечание:** Когда элемент становится перетаскиваемыми, текст или другие элементы в нем больше не могут быть выбраны обычным способом, щёлкая и перетаскивая мышью. Вместо этого пользователь должен удерживать клавишу `Alt` чтобы выбрать текст с помощью мыши или клавиатуры.

Начало операции перетаскивания
------------------------------

В примере, обработчик добавлен для события `dragstart (en-US)` с использованием атрибута``ondragstart``.

```html
<p
 draggable="true"
 ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
  This text <strong>may</strong> be dragged.
</p>
```

Когда пользователь начинает перетаскивание, запускается событие `dragstart (en-US)`.

В этом примере обработчик `dragstart (en-US)` добавлен к самому перемещаемом элементу. Однако, вы можете слушать более высокого предка, так как событие перетаскивание всплывает вверх как и большинство событий.

Внутри события `dragstart (en-US)`, вы можете указать **drag данные**, **изображение отклика**, **drag-эффекты**, все это описано ниже. Однако, обязательны только **drag данные**. (Изображение и drag-эффекты по умолчанию, подходят в большинстве ситуаций)

Drag-данные
-----------

Все `drag events` (en-US) имеют свойство, называемое`dataTransfer` (en-US), которое содержит drag-данные (`dataTransfer` это `DataTransfer` object).

Когда происходит перетаскивание, данные должны быть связаны с перетаскиванием, которое определяет, что перетаскивается. Например, при перетаскивании выделенного текста в текстовое поле данные, связанные с элементом данных перетаскивания, являются самим текстом. Аналогично, при перетаскивании ссылки на веб-странице элемент данных перетаскивания является URL-адресом ссылки.

`drag data` содержит два параметра, **тип** (или формат) данных, и **значение** данных. Формат это строковый тип (такой как `text/plain` (en-US) текстовых данных), значение - строка текста. Когда начинается перетаскивание, вы добавляете данные, предоставляя тип и данные. Во время перетаскивания в обработчике события для событий ``dragenter (en-US)`` и ``dragover (en-US)`` , вы используете типы данных перетаскиваемых данных, чтобы проверить, разрешено ли удаление. Например, цель drop, которая принимает ссылки, будет проверять тип `text/uri-list` (en-US). В течение события drop, обработчик будет получать данные тащат и вставить его на место.

Свойство `drag data's` `types` (en-US) возвращает список MIME-типов `DOMString`, таких как `text/plain` (en-US) или `image/jpeg` (en-US). Вы также можете создавать свои собственные типы. Большинство основные используемых типов описаны в Recommended Drag Types (en-US).

Перетаскивание может включать элементы данных нескольких различных типов. Это позволяет предоставлять данные в более специфических типах, часто пользовательских, но по предоставляет резервные данные для drop, которые не поддерживают более специфические типы. Как правило, наименее специфичным типом будут обычные текстовые данные, использующие тип `text/plain` (en-US). Эти данные будут простым текстовым представлением.

Установка элементов drag-данных `dataTransfer` (en-US), используя метод `setData()` (en-US). Требуется два аргумента: тип данных и значение данных. Например:

```js
event.dataTransfer.setData("text/plain", "Text to drag");
```

Здесь, значение - "Text to drag", формат - `text/plain` (en-US).

Вы можете предусмотреть данные в нескольких форматах. Сделаем это, вызовем метод `setData()` (en-US) несколько раз с различными форматами. Вы должны вызывать его с форматами от большей специфичности к меньшей.

```js
const dt = event.dataTransfer;
dt.setData("application/x.bookmark", bookmarkString);
dt.setData("text/uri-list", "https://www.mozilla.org");
dt.setData("text/plain", "https://www.mozilla.org");
```

Добавлены данные трёх различных форматов. Первый тип - `application/x.bookmark`, пользовательский тип.Другие приложения не поддерживают данный тип, но вы можете использовать пользовательский тип для перетаскивания между областями в одном приложение или на одной странице.

Предоставляя данные и в других типах, мы также можем поддерживать перетаскивание в другие приложения в менее специфичных формах. Тип `application/x.bookmark` может предоставлять данные с более подробной информацией для использования в приложении, в то время как другие типы могут включать только один URL-адрес или текстовую версию.

Обратите внимание, что и `text/uri-list` (en-US) и `text/plain` (en-US) содержат одни и те же данные в этом примере. Это часто бывает так, но это не обязательно.

Если вы попытаетесь добавить данные дважды с тем же форматом, новые данные заменят старые данные, но в той же позиции в списке типов, что и старые данные.

Вы можете очистить данные, используя метод `clearData()` (en-US), который принимает один аргумент: тип данных для удаления.

```js
event.dataTransfer.clearData("text/uri-list");
```

Аргумент `type` в методе `clearData()` (en-US) опционален. Если `type` не указан, данные, связанные со всеми типами, удаляются. Если перетаскивание не содержит элементов данных перетаскивания или все элементы были впоследствии очищены, то перетаскивание не произойдёт.

Настройка изображения отклика drag
----------------------------------

Когда происходит перетаскивание, полупрозрачное изображение генерируется из цели перетаскивания (событие "`dragstart (en-US)`" элемента срабатывает), и следует за указателем пользователя во время перетаскивания. Это изображение создаётся автоматически, поэтому вам не нужно создавать его самостоятельно. Однако вы можете использовать `setDragImage()` (en-US) для задания пользовательского изображения отклика перетаскивания.

```js
event.dataTransfer.setDragImage(image, xOffset, yOffset);
```

Необходимы три аргумента. Первый - это ссылка на изображение. Эта ссылка обычно относится к элементу `<img>`, но также может относиться к элементу `<canvas>` или любому другому элементу. Изображение отклика будет генерироваться из того, как изображение выглядит на экране, для изображений они будут нарисованы в их первоначальном размере. Второй и третий аргументы метода `setDragImage()` (en-US) - это смещения, в которых изображение должно появляться относительно указателя мыши.

Также можно использовать изображения и canvas, которых нет в документе. Этот метод полезен при рисовании пользовательских изображений перетаскивания с помощью элемента canvas, как показано в следующем примере:

```js
function dragWithCustomImage(event) {
  const canvas = document.createElement("canvas");
  canvas.width = canvas.height = 50;

  const ctx = canvas.getContext("2d");
  ctx.lineWidth = 4;
  ctx.moveTo(0, 0);
  ctx.lineTo(50, 50);
  ctx.moveTo(0, 50);
  ctx.lineTo(50, 0);
  ctx.stroke();

  const dt = event.dataTransfer;
  dt.setData("text/plain", "Data to Drag");
  dt.setDragImage(canvas, 25, 25);
}
```

В этом примере мы делаем один canvas перетаскивания. Поскольку размер холста составляет 50×50 пикселей, мы используем смещение половины этого (25), чтобы изображение было центрировано на указателе мыши.

Drag эффекты
------------

При перетаскивании можно выполнить несколько операций. Операция `copy` используется для указания на то, что перетаскиваемые данные будут скопированы из текущего местоположения в место перетаскивания. Операция `move` используется для указания на то, что перетаскиваемые данные будут перемещены, а операция `link` используется для указания на то, что между исходным и удаляемым местоположениями будет создана некоторая форма связи или соединения.

Вы можете указать, какая из трёх операций разрешена для источника перетаскивания, установив свойство `effectAllowed` (en-US) в обработчике событий``dragstart (en-US)``.

```js
event.dataTransfer.effectAllowed = "copy";
```

В этом примере разрешена только **копия**.

Вы можете комбинировать значения различными способами:

`none`

никакая операция не разрешена

`copy`

только `copy`

`move`

только `move`

`link`

только `link`

`copyMove`

только `copy` или `move`

`copyLink`

только `copy` или `link`

`linkMove`

только `link` или `move`

`all`

только `copy`, `move`, или `link`

uninitialized

Значение по умолчанию `all`.

Обратите внимание, что эти значения должны использоваться так, как указано выше. Например, установка свойства `effectAllowed` (en-US) на `copyMove` позволяет выполнять операцию копирования или перемещения, но не позволяет пользователю выполнять операцию связывания. Если вы не измените свойство `effectAllowed` (en-US), то любая операция разрешена, как и со значением '`all`'. Поэтому вам не нужно настраивать это свойство, если вы не хотите исключить определённые типы.

Во время операции перетаскивания, обработчик для событий ``dragenter (en-US)`` или ``dragover (en-US)`` может проверить свойство `effectAllowed` (en-US) , какие операции разрешены. Связанное свойство, `dropEffect` (en-US), должно быть установлено в пределах одного из этих событий, чтобы указать, какая единственная операция должна быть выполнена. Допустимые значения для `dropEffect` (en-US) - `none`, `copy`, `move`, или `link`. Комбинированные значения для этого свойства не используются.

С событиями ``dragenter (en-US)`` и ``dragover (en-US)``, свойство `dropEffect` (en-US) инициализируется в соответствии с запросом пользователя. Пользователь может изменить желаемый эффект, нажав клавиши-модификаторы. Хотя точные используемые клавиши различаются в зависимости от платформы, обычно клавиши `Shift` и `Control` используются для переключения между копированием, перемещением и связыванием. Указатель мыши изменится, чтобы указать, какая операция требуется. Например, для `copy` курсор может появиться со знаком плюс рядом с ним.

Вы можете изменять свойство `dropEffect` (en-US) во время событий ``dragenter (en-US)`` или ``dragover (en-US)``, например, определённая drop-цель поддерживает только определённые операции. Вы можете изменить свойство `dropEffect` (en-US), чтобы переопределить действие пользователя, и обеспечить выполнение специфичной операции перетаскивания при её наступлении. Обратите внимание, что этот эффект должен быть указан в списке свойств `effectAllowed` (en-US). В противном случае ему будет присвоено другое допустимое значение.

```js
event.dataTransfer.dropEffect = "copy";
```

В этом примере выполняется эффект копирования.

Вы можете использовать значение `none`, чтобы указать, что в этом месте не допускается удаление, хотя в этом случае лучше не отменять событие.

В событиях ``drop (en-US)`` и ``dragend (en-US)``, вы можете проверить свойства `dropEffect` (en-US) для определения того, какой эффект был в конечном итоге выбран. Если выбран эффект "`move`",то исходные данные должны быть удалены из источника перетаскивания в событии``dragend (en-US)``.

Указание drop-целей
-------------------

Обработчик для событий ``dragenter (en-US)`` и ``dragover (en-US)`` используются для указания допустимых drop-целей, то есть мест, где могут быть сброшены перетаскиваемые элементы. Большинство областей веб-страницы или приложения не являются допустимыми местами для сброса данных. Таким образом, обработка этих событий по умолчанию не допускает сброса перетаскиваемых данных.

Если вы хотите разрешить сброс переносимых данных, вы должны предотвратить обработку по умолчанию, отменив оба события `dragenter` и `dragover`. Это можно сделать, либо вернув `false` из определённых атрибутом обработчика событий, либо вызвав метод `preventDefault()` событие. Последнее может быть более осуществимо в функции, определённой в отдельном скрипте.

```html
<div ondragover="return false">
  <div ondragover="event.preventDefault()"></div>
</div>
```

Вызывая метод `preventDefault()` во время обоих событий ``dragenter (en-US)`` и ``dragover (en-US)`` укажите, что падение разрешено в этом месте. Однако обычно вы захотите вызвать метод `preventDefault()` события только в определённых ситуациях (например, только при перетаскивании ссылки).

Для этого вызовите функцию, которая проверяет условие и отменяет событие только при его выполнении. Если условие не выполнено, не отменяйте событие, и сброс перетаскиваемых данных не произойдёт, если пользователь отпустит кнопку мыши.

Наиболее распространённым является принятие или отклонение сброса в зависимости от типа данных перетаскивания при передаче данных — например, разрешение для изображений, ссылок или и того, и другого. Для этого вы можете проверить свойство `types` (en-US) события `dataTransfer` (en-US) (свойство). Свойство `types` (en-US) возвращает массив из строк, добавленных при начале перетаскивания, в порядке от наиболее значимого к наименее значимому.

```js
function doDragOver(event) {
  const isLink = event.dataTransfer.types.includes("text/uri-list");
  if (isLink) {
    event.preventDefault();
  }
}
```

В этом примере мы используем метод `includes` чтобы проверить, присутствует ли тип `text/uri-list` (en-US) в списке типов. Если это так, мы отменим событие, так что сброс становится разрешён. Если перетаскиваемые данные не содержат ссылки, событие не будет отменено, и сброс не может произойти в этом месте.

Вы также можете установить либо свойство `effectAllowed` (en-US), либо свойство`dropEffect` (en-US), либо оба одновременно, если вы хотите указать более конкретные сведения о типе операции, которая будет выполнена. Естественно, изменение любого свойства не будет иметь никакого эффекта, если вы не отмените событие.

Drop Feedback
-------------

There are several ways in which you can indicate to the user that a drop is allowed at a certain location. The mouse pointer will update as necessary depending on the value of the `dropEffect` (en-US) property.

Although the exact appearance depends on the user's platform, typically a plus sign icon will appear for a '`copy`' for example, and a 'cannot drop here' icon will appear when a drop is not allowed. This mouse pointer feedback is sufficient in many cases.

However, you can also update the user interface with an insertion point or highlight as needed. For simple highlighting, you can use the `:-moz-drag-over` CSS pseudoclass on a drop target.

```css
.droparea:-moz-drag-over {
  outline: 1px solid black;
}
```

In this example, the element with the class `droparea` will receive a 1 pixel black outline while it is a valid drop target, that is, if the `preventDefault()` method was called during the ``dragenter (en-US)`` event.

**Примечание:** You must cancel the ``dragenter (en-US)`` event for this pseudoclass to apply, as this state is not checked for the ``dragover (en-US)`` event.

For more complex visual effects, you can also perform other operations during the ``dragenter (en-US)`` event. For example, by inserting an element at the location where the drop will occur. This might be an insertion marker, or an element that represents the dragged element in its new location. To do this, you could create an image or separator element and simply insert it into the document during the ``dragenter (en-US)`` event.

The ``dragover (en-US)`` event will fire at the element the mouse is pointing at. Naturally, you may need to move the insertion marker around a ``dragover (en-US)`` event as well. You can use the event's `clientX` and `clientY` properties as with other mouse events to determine the location of the mouse pointer.

Finally, the ``dragleave (en-US)`` event will fire at an element when the drag leaves the element. This is the time when you should remove any insertion markers or highlighting. You do not need to cancel this event. Any highlighting or other visual effects specified using the `:-moz-drag-over` pseudoclass will be removed automatically. The ``dragleave (en-US)`` event will always fire, even if the drag is cancelled, so you can always ensure that any insertion point cleanup can be done during this event.

Performing a Drop
-----------------

When the user releases the mouse, the drag and drop operation ends.

If the mouse is released over an element that is a valid drop target, that is, one that cancelled the last ``dragenter (en-US)`` or ``dragover (en-US)`` event, then the drop will be successful, and a ``drop (en-US)`` event will fire at the target. Otherwise, the drag operation is cancelled, and no ``drop (en-US)`` event is fired.

During the ``drop (en-US)`` event, you should retrieve that data that was dropped from the event and insert it at the drop location. You can use the `dropEffect` (en-US) property to determine which drag operation was desired.

As with all drag-related events, the event's ``dataTransfer`` property will hold the data that is being dragged. The `getData()` (en-US) method may be used to retrieve the data again.

```js
function onDrop(event) {
  const data = event.dataTransfer.getData("text/plain");
  event.target.textContent = data;
  event.preventDefault();
}
```

The `getData()` (en-US) method takes one argument, the type of data to retrieve. It will return the string value that was set when `setData()` (en-US) was called at the beginning of the drag operation. An empty string will be returned if data of that type does not exist. (Naturally, though, you would likely know that the right type of data was available, as it was previously checked during a ``dragover (en-US)`` event.)

In the example here, once the data has been retrieved, we insert the string as the textual content of the target. This has the effect of inserting the dragged text where it was dropped, assuming that the drop target is an area of text such as a `p` or `div` element.

In a web page, you should call the `preventDefault()` method of the event if you have accepted the drop, so that the browser's default handling is not triggered by the dropped data as well. For example, when a link is dragged to a web page, Firefox will open the link. By cancelling the event, this behavior will be prevented.

You can retrieve other types of data as well. If the data is a link, it should have the type `text/uri-list` (en-US). You could then insert a link into the content.

```js
function doDrop(event) {
  const lines = event.dataTransfer.getData("text/uri-list").split("\n");
  lines
    .filter((line) => !line.startsWith("#"))
    .forEach((line) => {
      const link = document.createElement("a");
      link.href = line;
      link.textContent = line;
      event.target.appendChild(link);
    });
  event.preventDefault();
}
```

This example inserts a link from the dragged data. As the name implies, the `text/uri-list` (en-US) type actually may contain a list of URLs, each on a separate line. The above code uses `split` (en-US) to break the string into lines, then iterates over the list of lines, and inserts each as a link into the document. (Note also that links starting with a number sign (`#`) are skipped, as these are comments.)

For simple cases, you can use the special type `URL` just to retrieve the first valid URL in the list. For example:

```js
const link = event.dataTransfer.getData("URL");
```

This eliminates the need to check for comments or iterate through lines yourself. However, it is limited to only the first URL in the list.

The `URL` type is a special type. It is used only as a shorthand, and it does not appear within the list of types specified in the `types` (en-US) property.

Sometimes you may support some different formats, and you want to retrieve the data that is most specific that is supported. In the following example, three formats are supported by a drop target.

The following example returns the data associated with the best-supported format:

```js
function doDrop(event) {
  const supportedTypes = [
    "application/x-moz-file",
    "text/uri-list",
    "text/plain",
  ];
  const types = event.dataTransfer.types.filter((type) =>
    supportedTypes.includes(type),
  );
  if (types.length) {
    const data = event.dataTransfer.getData(types[0]);
  }
  event.preventDefault();
}
```

Окончание перетаскивания
------------------------

Как только перетаскивание завершено, событие ``dragend (en-US)`` запускается в источнике перетаскивания (тот же элемент, который получил событие ``dragstart (en-US)``). Это событие сработает, если перетаскивание было успешным[1] или если оно было отменено. Однако вы можете использовать свойство `dropEffect` (en-US) для определения, какая операция удаления произошла.

Если свойство `dropEffect` (en-US) имеет значение `none` во время события ``dragend (en-US)``, то перетаскивание было отменено. В противном случае эффект указывает, какая операция была выполнена. Источник может использовать эту информацию после операции перемещения для удаления перетаскиваемого элемента из старого расположения. Свойство `mozUserCancelled` будет присвоено значение `true`, если пользователь отменил перетаскивание (нажав `Escape`), и `false` если перетаскивание было отменено по другим причинам, таким как недопустимая цель перетаскивания, или если оно было успешным.

Сброс может произойти внутри того же окна или над другим приложением. Событие ``dragend (en-US)`` будет срабатывать всегда, независимо от этого. Свойство события `screenX` и `screenY` (en-US) будут установлены в координаты экрана, где произошёл сброс.

Когда событие ``dragend (en-US)`` завершило распространение, операция перетаскивания завершена.

[1]: В Gecko, `dragend (en-US)` не отправляется, если исходный узел движется или удаляется во время перетаскивания (например, при сбрасывании или `dragover (en-US)`). Bug 460801

Смотрите также
--------------

* HTML Drag and Drop API (Overview)
* Dragging and Dropping Multiple Items
* Recommended Drag Types (en-US)
* HTML5 Living Standard: Drag and Drop


HTMLCanvasElement.toBlob() - Интерфейсы веб API

HTMLCanvasElement.toBlob()
==========================

Метод **`HTMLCanvasElement.toBlob()`** создаёт объект `Blob` представляющий изображение, содержащееся в `canvas`; этот файл может быть закеширован на диске или храниться в памяти на усмотрение пользователя (*at the discretion of the user agent*). Если параметр `mimeType` не определён, типом изображения считается `image/png`. Созданное изображение имеет разрешение 96dpi.

Третий аргумент используется для изображений с MIME-типом `image/jpeg` для определения его качества.

Синтаксис
---------
```

void canvas.toBlob(callback, mimeType, qualityArgument);
```

### Параметры

callback

Callback-функция с результирующим объектом `Blob` в качестве единственного аргумента.

`mimeType` Необязательный

Аргумент типа `DOMString` определяющий формат изображения. По умолчанию `image/png`.

`qualityArgument` Необязательный

Аргумент типа `Number` со значением от `0` до `1`, определяющий качество изображения, если заявлен MIME-тип `image/jpeg` или `image/webp`. Если этот аргумент содержит нечто иное, для определения качества изображения будет использовано значение по умолчанию. Остальные аргументы проигнорируются.

### Возвращаемое значение

Не возвращает ничего.

Примеры
-------

### Получение файла, представленного в canvas

Как только вы нарисовали содержимое в `canvas`, вы можете сконвертировать его в файл изображения любого поддерживаемого формата. Ниже приведён фрагмент кода, для примера, принимает изображение в элементе `<canvas>` с ID = "canvas" и получает его копию в виде PNG изображения, затем добавляет в документ новый элемент `<img>`, исходное изображение которого создано с помощью холста.

```js
var canvas = document.getElementById("canvas");

canvas.toBlob(function (blob) {
  var newImg = document.createElement("img"),
    url = URL.createObjectURL(blob);

  newImg.onload = function () {
    // больше не нужно читать blob, поэтому он отменён
    URL.revokeObjectURL(url);
  };

  newImg.src = url;
  document.body.appendChild(newImg);
});
```

Обратите внимание, что здесь мы создаём изображение PNG; если вы добавите второй параметр в вызов `toBlob()`, вы сможете определить тип необходимого изображения. Например, чтобы получить изображение в формате JPEG:

```js
 canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95); // JPEG в 95% качестве
```

### A way to convert a canvas to an ico (Mozilla only)

Это использует `-moz-parse` для преобразования cnavas в ICO. Windows XP не поддерживает преобразование из PNG в ico, поэтому вместо этого использует bmp. Ссылка для загрузки создаётся путём установки атрибута загрузки. Значение атрибута загрузки - это имя, которое он будет использовать в качестве имени файла.

```js
var canvas = document.getElementById("canvas");
var d = canvas.width;
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(d / 2, 0);
ctx.lineTo(d, d);
ctx.lineTo(0, d);
ctx.closePath();
ctx.fillStyle = "yellow";
ctx.fill();

function blobCallback(iconName) {
  return function (b) {
    var a = document.createElement("a");
    a.textContent = "Download";
    document.body.appendChild(a);
    a.style.display = "block";
    a.download = iconName + ".ico";
    a.href = window.URL.createObjectURL(b);
  };
}
canvas.toBlob(
  blobCallback("passThisString"),
  "image/vnd.microsoft.icon",
  "-moz-parse-options:format=bmp;bpp=32",
);
```

### Сохранение toBlob на диске ОС(chrome/add-on context only)

**Примечание:** Этот метод сохраняет его на рабочем столе и полезен только в контексте Firefox chrome или дополнительном коде, поскольку API ОС не присутствуют на веб-сайтах.

```js
var canvas = document.getElementById("canvas");
var d = canvas.width;
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(d / 2, 0);
ctx.lineTo(d, d);
ctx.lineTo(0, d);
ctx.closePath();
ctx.fillStyle = "yellow";
ctx.fill();

function blobCallback(iconName) {
  return function (b) {
    var r = new FileReader();
    r.onloadend = function () {
      // r.result contains the ArrayBuffer.
      Cu.import("resource://gre/modules/osfile.jsm");
      var writePath = OS.Path.join(
        OS.Constants.Path.desktopDir,
        iconName + ".ico",
      );
      var promise = OS.File.writeAtomic(writePath, new Uint8Array(r.result), {
        tmpPath: writePath + ".tmp",
      });
      promise.then(
        function () {
          console.log("successfully wrote file");
        },
        function () {
          console.log("failure writing file");
        },
      );
    };
    r.readAsArrayBuffer(b);
  };
}

canvas.toBlob(
  blobCallback("passThisString"),
  "image/vnd.microsoft.icon",
  "-moz-parse-options:format=bmp;bpp=32",
);
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-canvas-toblob-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Полифил
-------

Полифил, основанный на toDataURL, со слабой производительностью.

```js
if (!HTMLCanvasElement.prototype.toBlob) {
  Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
    value: function (callback, type, quality) {
      var dataURL = this.toDataURL(type, quality).split(",")[1];
      setTimeout(function () {
        var binStr = atob(dataURL),
          len = binStr.length,
          arr = new Uint8Array(len);

        for (var i = 0; i < len; i++) {
          arr[i] = binStr.charCodeAt(i);
        }

        callback(new Blob([arr], { type: type || "image/png" }));
      });
    },
  });
}
```

Смотрите также
--------------

* The interface defining it, `HTMLCanvasElement`.
* `Blob`


HTMLCanvasElement: событие webglcontextlost - Интерфейсы веб API

HTMLCanvasElement: событие webglcontextlost
===========================================

 Событие **`webglcontextlost`** WebGL API вызывается когда User agent
 обнаруживает, что буфер, связанный с `WebGLRenderingContext`, потерян.

|  |  |
| --- | --- |
| Всплытие | Да |
| Отменяемое | Да |
| Интерфейс | `WebGLContextEvent` (en-US) |
| Свойство для обработки события | Нет |

Пример
------

Вы можете эмулировать `webglcontextlost` событие с помощью расширения `WEBGL_lose_context` (en-US):

```js
const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");

canvas.addEventListener("webglcontextlost", (event) => {
  console.log(event);
});

gl.getExtension("WEBGL\_lose\_context").loseContext();

// будет выведено "webglcontextlost".
```

Спецификация
------------

| Specification |
| --- |
| WebGL Specification # 5.15.2 |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* `WebGLContextEvent` (en-US)
* `WebGLRenderingContext.isContextLost()` (en-US)
* `WEBGL_lose_context` (en-US), `WEBGL_lose_context.loseContext()` (en-US), `WEBGL_lose_context.restoreContext()` (en-US)


HTMLCanvasElement.toDataURL() - Интерфейсы веб API

HTMLCanvasElement.toDataURL()
=============================

**`HTMLCanvasElement.toDataURL()`** — метод, который возвращает data URI изображения в
 формате, заданном параметром `type` (по умолчанию PNG). Возвращаемое изображение имеет
 разрешение 96 dpi.

* Если высота или ширина холста равна `0` или больше максимального размера холста,
 возвращается строка `"data:,"`.
* Если запрошенный тип отличается от `image/png`, но возвращаемое значение начинается с `data:image/png`, то запрошенный тип не поддерживается.
* Chrome также поддерживает тип `image/webp`.
Синтаксис
---------

```js
canvas.toDataURL(type, encoderOptions);
```

### Параметры

`type` Необязательный

Строка (`DOMString`), указывающая формат изображения. По умолчанию — `image/png`.

`encoderOptions` Необязательный

 Число (типа `Number`) между `0` и `1`, указывающее качество изображения для форматов, использующих сжатие с потерями, таких как
 `image/jpeg` и `image/webp`.
 Если передан невалидный аргумент, то используется значение по умолчанию `0.92`. Остальные аргументы игнорируются.

### Возвращаемое значение

Строка (`DOMString`), содержащая запрошенный data URI.

### Исключения

`SecurityError`

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

Примеры
-------

Возьмём следующий `<canvas>` элемент:

```html
<canvas id="canvas" width="5" height="5"></canvas>
```

Вы можете получить его data-URL следующим образом:

```js
var canvas = document.getElementById("canvas");
var dataURL = canvas.toDataURL();
console.log(dataURL);
// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby
// blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"
```

### Настройка качества изображения с помощью jpegs

```js
var fullQuality = canvas.toDataURL("image/jpeg", 1.0);
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...9oADAMBAAIRAxEAPwD/AD/6AP/Z"
var mediumQuality = canvas.toDataURL("image/jpeg", 0.5);
var lowQuality = canvas.toDataURL("image/jpeg", 0.1);
```

### Пример: Динамическое изменение изображений

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

#### HTML

```html
<img class="grayscale" src="myPicture.png" alt="Описание моей картинки" />
```

#### JavaScript

```js
window.addEventListener("load", removeColors);

function showColorImg() {
  this.style.display = "none";
  this.nextSibling.style.display = "inline";
}

function showGrayImg() {
  this.previousSibling.style.display = "inline";
  this.style.display = "none";
}

function removeColors() {
  var aImages = document.getElementsByClassName("grayscale"),
    nImgsLen = aImages.length,
    oCanvas = document.createElement("canvas"),
    oCtx = oCanvas.getContext("2d");
  for (
    var nWidth, nHeight, oImgData, oGrayImg, nPixel, aPix, nPixLen, nImgId = 0;
    nImgId < nImgsLen;
    nImgId++
  ) {
    oColorImg = aImages[nImgId];
    nWidth = oColorImg.offsetWidth;
    nHeight = oColorImg.offsetHeight;
    oCanvas.width = nWidth;
    oCanvas.height = nHeight;
    oCtx.drawImage(oColorImg, 0, 0);
    oImgData = oCtx.getImageData(0, 0, nWidth, nHeight);
    aPix = oImgData.data;
    nPixLen = aPix.length;
    for (nPixel = 0; nPixel < nPixLen; nPixel += 4) {
      aPix[nPixel + 2] =
        aPix[nPixel + 1] =
        aPix[nPixel] =
          (aPix[nPixel] + aPix[nPixel + 1] + aPix[nPixel + 2]) / 3;
    }
    oCtx.putImageData(oImgData, 0, 0);
    oGrayImg = new Image();
    oGrayImg.src = oCanvas.toDataURL();
    oGrayImg.onmouseover = showColorImg;
    oColorImg.onmouseout = showGrayImg;
    oCtx.clearRect(0, 0, nWidth, nHeight);
    oColorImg.style.display = "none";
    oColorImg.parentNode.insertBefore(oGrayImg, oColorImg);
  }
}
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-canvas-todataurl-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Интерфейс `HTMLCanvasElement`.
* Статья о Data URIs в разделе HTTP.


HTMLCanvasElement.transferControlToOffscreen() - Интерфейсы веб API

HTMLCanvasElement.transferControlToOffscreen()
==============================================

Baseline 2023
-------------

Newly availableSince March 2023, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.

* Learn more
* See full compatibility
* Report feedback
**Экспериментальная возможность:** **Это экспериментальная технология**
Так как спецификация этой технологии ещё не стабилизировалась, смотрите таблицу совместимости по поводу использования в различных браузерах. Также заметьте, что синтаксис и поведение экспериментальной технологии может измениться в будущих версиях браузеров, вслед за изменениями спецификации.

**`HTMLCanvasElement.transferControlToOffscreen()`**
 передаёт управление объекту `OffscreenCanvas` (en-US), который может использоваться либо в основном потоке, либо в `Worker`.

Синтаксис
---------

```js
OffscreenCanvas HTMLCanvasElement.transferControlToOffscreen()
```

### Возвращаемое значение

Объект `OffscreenCanvas` (en-US).

Примеры
-------

```js
var htmlCanvas = document.createElement("canvas");
var offscreen = htmlCanvas.transferControlToOffscreen();
var gl = offscreen.getContext("webgl");

// ... отрисовка с помощью gl контекста ...

// Возвращаем кадры в исходный HTMLCanvasElement
gl.commit();
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-canvas-transfercontroltooffscreen-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Интерфейс, определяющий метод: `HTMLCanvasElement`
* `OffscreenCanvas` (en-US)
* `WebGLRenderingContext.commit()`


HTMLCanvasElement.getContext() - Интерфейсы веб API

HTMLCanvasElement.getContext()
==============================

Метод **`HTMLCanvasElement.getContext()`** возвращает контекст рисования на холсте, или `null`, если идентификатор контекста не определён.

Синтаксис
---------
```

canvas.getContext(contextType, contextAttributes);
```

### Параметры

contextType

`DOMString`, содержащий идентификатор контекста, определяющий контекст рисования, связанный с холстом. Возможные значения:

* `"2d`", ведущий к созданию объекта `CanvasRenderingContext2D`, представляющий двумерный контекст.
* `"webgl"` (или `"experimental-webgl"`), который будет создавать объект `WebGLRenderingContext`, представляющий трёхмерный контекст. Этот контекст доступен только в браузерах, которые реализуют `WebGL` первой версии (OpenGL ES 2.0).
* "`webgl2`" (или "`experimental-webgl2`"), который будет создавать объект `WebGL2RenderingContext` (en-US), представляющий трёхмерный контекст. Этот контекст доступен только в браузерах, которые реализуют `WebGL` второй версии (OpenGL ES 3.0).
Экспериментальная возможность
* `"bitmaprenderer",` который позволит создать `ImageBitmapRenderingContext` (en-US), обеспечивающий только функции для замены содержимого холста с заданным `ImageBitmap`.

Примечание: Идентификаторы "`experimental-webgl`" или "`experimental-webgl2`" используются в новых реализациях WebGL. Эти реализации не достигли испытательного набора на соответствие или ситуация с графическими драйверами на платформе ещё не стабильна The Khronos Group certifies WebGL implementations under certain conformance rules.

`contextAttributes`

Вы можете использовать несколько атрибутов контекста при создании контекста рендеринга, например:

```js
canvas.getContext("webgl", { antialias: false, depth: false });
```

2d атрибуты контекста:

* **`alpha`**: Логическое значение, означающее, есть ли у холста альфа-канал. Значение `false` говорит браузеру, что фон холста непрозрачный, что может ускорить рисование прозрачного содержимого и изображений.
* Non-standard
 (Gecko only) **`willReadFrequently`**: Boolean that indicates whether or not a lot of read-back operations are planned. This will force the use of a software (instead of hardware accelerated) 2D canvas and can save memory when calling `getImageData()` frequently. This option is only available, if the flag `gfx.canvas.willReadFrequently.enable` is set to `true` (which, by default, is only the case for B2G/Firefox OS).
* Non-standard
 (Blink only) **`storage`**: String that indicates which storage is used ("persistent" by default).

Атрибуты контекста WebGL:

* **`alpha`**: Boolean that indicates if the canvas contains an alpha buffer.
* **`depth`**: Boolean that indicates that the drawing buffer has a depth buffer of at least 16 bits.
* **`stencil`**: Boolean that indicates that the drawing buffer has a stencil buffer of at least 8 bits.
* **`antialias`**: Boolean that indicates whether or not to perform anti-aliasing.
* **`premultipliedAlpha`**: Boolean that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha.
* **`preserveDrawingBuffer`**: If the value is true the buffers will not be cleared and will preserve their values until cleared or overwritten by the author.
* **`failIfMajorPerformanceCaveat`**: Boolean that indicates if a context will be created if the system performance is low.

### Возвращаемое значение

Контекст рендеринга, который представляет собой

* `CanvasRenderingContext2D` для `"2d"`,
* `WebGLRenderingContext` для `"webgl"` и `"experimental-webgl"`,
* `WebGL2RenderingContext` (en-US) для `"webgl2"` и `"experimental-webgl2"`, или
* `ImageBitmapRenderingContext` (en-US) для `"bitmaprenderer"`.

Если contextType не соответствует возможному контексту рисунка, то возвращается null.

Примеры
-------

Given this `<canvas>` element:

```html
<canvas id="canvas" width="300" height="300"></canvas>
```

You can get a `2d` context of the canvas with the following code:

```js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
console.log(ctx); // CanvasRenderingContext2D { ... }
```

Now you have the 2D rendering context for a canvas and you can draw within it.

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-canvas-getcontext-dev |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* The interface defining it, `HTMLCanvasElement`.
* `OffscreenCanvas.getContext()` (en-US)
* Available rendering contexts: `CanvasRenderingContext2D`, `WebGLRenderingContext` and `WebGL2RenderingContext` (en-US) and `ImageBitmapRenderingContext` (en-US).


HTMLCanvasElement.width - Интерфейсы веб API

HTMLCanvasElement.width
=======================

 Свойство **`HTMLCanvasElement.width`** — это положительное
 `целое число`, отображающее значение HTML атрибута `width` элемента `<canvas>` в CSS пикселях.
 Если атрибут не указан или для него задано недопустимое значение, например отрицательное, то используется значение по умолчанию `300`.

Одно из двух свойств (другое — `HTMLCanvasElement.height`), контролирующих размер холста.

Синтаксис
---------

```js
var pxl = canvas.width;
canvas.width = pxl;
```

Примеры
-------

Возьмём следующий `<canvas>` элемент:

```html
<canvas id="canvas" width="300" height="300"></canvas>
```

Вы можете получить ширину холста следующим образом:

```js
var canvas = document.getElementById("canvas");
console.log(canvas.width); // 300
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-canvas-height |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Интерфейс `HTMLCanvasElement`.
* Другое свойство, контролирующее размер холста,
 `HTMLCanvasElement.height`.


HTMLCanvasElement.height - Интерфейсы веб API

HTMLCanvasElement.height
========================

 Свойство **`HTMLCanvasElement.height`** — это положительное
 `целое число`, отображающее значение HTML атрибута `height` элемента `<canvas>` в CSS пикселях. Если атрибут не указан или
 для него задано недопустимое значение, например отрицательное, то используется значение по умолчанию `150`

Одно из двух свойств (другое — `HTMLCanvasElement.width`), контролирующих размер холста.

Синтаксис
---------

```js
var pxl = canvas.height;
canvas.height = pxl;
```

Примеры
-------

Возьмём следующий `<canvas>` элемент:

```html
<canvas id="canvas" width="300" height="300"></canvas>
```

Вы можете получить высоту холста следующим образом:

```js
var canvas = document.getElementById("canvas");
console.log(canvas.height); // 300
```

Спецификации
------------

| Specification |
| --- |
| HTML Standard # dom-canvas-height |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Интерфейс `HTMLCanvasElement`.
* Другое свойство, контролирующее размер холста
 `HTMLCanvasElement.width`.


HTMLCanvasElement.captureStream() - Интерфейсы веб API

HTMLCanvasElement.captureStream()
=================================

**Экспериментальная возможность:** **Это экспериментальная технология**
Так как спецификация этой технологии ещё не стабилизировалась, смотрите таблицу совместимости по поводу использования в различных браузерах. Также заметьте, что синтаксис и поведение экспериментальной технологии может измениться в будущих версиях браузеров, вслед за изменениями спецификации.

**`HTMLCanvasElement.captureStream()`** метод возвращает `CanvasCaptureMediaStream` (en-US), который является видеозахватчиком (оцифровщиком) в реальном времени на поверхности canvas.

Синтаксис
---------
```

MediaStream = canvas.captureStream(frameRate);
```

### Параметры

`frameRate` Необязательный

Число двойной точности с плавающей точкой, указывающее частоту захвата каждого фрейма. Если не установлено, новый фрейм будет захватываться каждый раз, как canvas изменится; `если установлено в 0`, будет захвачен один фрейм.

### Возвращаемое значение

Ссылка на `MediaStream` объект.

### Исключения

**NotSupportedError**

Параметр `frameRate` имеет отрицательное значение.

Пример
------

```js
// Поиск элемента canvas для захвата
var canvasElt = document.querySelector("canvas");

// Получение потока
var stream = canvasElt.captureStream(25); // 25 FPS

// Делать что-то с потоком
// например, отправить что-то другому компьютеру, используя RTCPeerConnection
// pc это RTCPeerConnection, созданное где-то ещё
pc.addStream(stream);
```

Спецификация
------------

| Specification |
| --- |
| Media Capture from DOM Elements # dom-htmlcanvaselement-capturestream |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* `CanvasCaptureMediaStream` (en-US), the interface it belongs to.
* `HTMLMediaElement.captureStream()` (en-US), which allows capturing a stream from a media element.
* `MediaStream`
* `Media Capture and Streams API` (en-US)


AnalyserNode.getByteFrequencyData() - Интерфейсы веб API

AnalyserNode.getByteFrequencyData()
===================================

Метод **`getByteFrequencyData()`** интерфейса `AnalyserNode` копирует текущие данные частоты в переданный в него Uint8Array `Uint8Array` (en-US) (unsigned byte array).

Данные частоты состоят из целых чисел по шкале от 0 до 255.

Каждый элемент в массиве представляет собой значение в децибелах для определённой частоты. Частоты распределены линейно от 0 до 1/2 частоты дискретизации. Например, для частоты дискретизации `48000` последний элемент массива будет представлять значение в децибелах для `24000` Гц.

Если в массиве меньше элементов, чем в `AnalyserNode.frequencyBinCount` (en-US), лишние элементы удаляются. Если в нем больше элементов, чем необходимо, лишние элементы игнорируются.

Синтаксис
---------

```js
var audioCtx = new AudioContext();
var analyser = audioCtx.createAnalyser();
var dataArray = new Uint8Array(analyser.frequencyBinCount); // Uint8Array должен иметь такой же размер, что и frequencyBinCount

void analyser.getByteFrequencyData(dataArray); // заполняет Uint8Array данными, полученными из getByteFrequencyData()
```

### Параметры

`array`

`Uint8Array` (en-US) , в который будут скопированы данные частотной области. Для любого образца, который не содержит данных, значение равно `-Infinity`.
 Если в массиве меньше элементов, чем в `AnalyserNode.frequencyBinCount` (en-US), лишние элементы удаляются. Если в нем больше элементов, чем необходимо, лишние элементы игнорируются.

### Возвращаемое значение

None.

Пример
------

Следующий пример показывает базовое использование `AudioContext` для создания AnalyserNode, затем `requestAnimationFrame` и `<canvas>` для повторения сбора частотных данных и вывода в стиле гистограммы winamp для текущего аудиовхода. Для получения дополнительных примеров / информации ознакомьтесь с нашей демонстрацией Voice-change-O-matic (см. соответствующий код в строках app.js lines 128–205 ).

```js
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioCtx.createAnalyser();

  ...

analyser.fftSize = 256;
var bufferLength = analyser.frequencyBinCount;
console.log(bufferLength);
var dataArray = new Uint8Array(bufferLength);

canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);

function draw() {
  drawVisual = requestAnimationFrame(draw);

  analyser.getByteFrequencyData(dataArray);

  canvasCtx.fillStyle = 'rgb(0, 0, 0)';
  canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);

  var barWidth = (WIDTH / bufferLength) \* 2.5;
  var barHeight;
  var x = 0;

  for(var i = 0; i < bufferLength; i++) {
    barHeight = dataArray[i];

    canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
    canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight/2);

    x += barWidth + 1;
  }
};

draw();
```

Параметры
---------

array

`Uint8Array` (en-US), в который будут скопированы данные частотной области.

Спецификации
------------

| Specification |
| --- |
| Web Audio API # dom-analysernode-getbytefrequencydata |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Смотрите также
--------------

* Using the Web Audio API (en-US)