Beacon API
Beacon API позволяет отправлять на сервер асинхронные и неблокирующие запросы (методом POST), которые гарантированно завершаются до выгрузки страницы, в отличие от XMLHttpRequest или Fetch API.
Одним из основных вариантов использования Beacon API является логгирование активности пользователей или отправка аналитических данных на сервер.
Раньше для этого приходилось прибегать к таким уловкам, как обработка событий unload или beforeunload глобального объекта Window с помощью синхронного XMLHttpRequest, например:
const someData = {
a: 1,
b: 2,
};
// страница будет выгружена только после отправки данного запроса
window.addEventListener("beforeunload", () => {
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://example.com/beacon");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
const params = new URLSearchParams(Object.entries(someData)); xhr.send(params);
});
Интерфейс
Beacon API расширяет свойство navigator методом sendBeacon, который имеет следующую сигнатуру:
navigator.sendBeacon(url: string | URL, data?: BodyInit | null)
- url — адрес сервера;
- data — опциональные данные для отправки, которые могут быть строкой, объектом, ArrayBuffer, Blob, DataView, FormData, TypedArray или URLSearchParams.
sendBeacon возвращает логическое значение (true или false) — индикатор постановки data в очередь для передачи.
Пример использования
Создаем шаблон проекта с помощью Yarn и Vite на чистом JavaScript:
# rare-web-apis - название проекта
# --template vanilla - используемый шаблон
yarn create vite rare-web-apis --template vanilla
Переходим в созданную директорию, устанавливаем зависимости и запускаем сервер для разработки:
cd rare-web-apis
yarn
yarn dev
Определяем в файле main.js следующий обработчик:
Событие visibilitychange объекта document (подробнее о нем можно почитать по ссылке, приведенной в начале статьи) является более надежным способом определения состояния видимости страницы, чем события unload или beforeunload. В обработчике при скрытии страницы, например, при переключении вкладки или сворачивании страницы, с помощью sendBeacon по адресу https://example.com/beacon отправляются некоторые данные в форме URLSearchParams.
Результат переключения вкладки:
Поддержка — 96.8%.
Clipboard API
Clipboard API позволяет выполнять асинхронные операции записи/чтения текстовых и других данных в/из системного буфера обмена, а также обрабатывать события copy, cut и paste (копирование, вырезка и вставка) буфера.
По причинам безопасности Clipboard API доступен только при условии, что:
- страница обслуживается по протоколу https или localhost;
- страница находится в активной вкладке браузера (не находится в фоновом режиме);
- операции записи/чтения инициализируются пользователем (например, с помощью нажатия кнопки).
Разрешение clipboard-write для записи данных предоставляется активной странице автоматически, а разрешение clipboard-read для чтения данных запрашивается у пользователя с помощью Permissions API.
Раньше для работы с содержимым редактируемой области использовался метод document.execCommand. Например, вот как выполнялась запись текста:
Интерфейс
Clipboard API расширяет свойство navigator интерфейсом Clipboard, экземпляры которого предоставляют следующие методы для работы с буфером:
- writeText(text: string) — для записи текста, принимает строку;
- readText() — для чтения текста, возвращает строку;
- write(data: ClipboardItem[]) — для записи данных, принимает массив объектов ClipboardItem (см. ниже);
- read() — для чтения данных, возвращает массив объектов ClipboardItem.
Поскольку речь идет об асинхронном интерфейсе, все названные методы возвращают промис, который разрешается данными или отклоняется с ошибкой.
ClipboardItem — это интерфейс, предназначенный для работы с нетекстовыми данными, который имеет следующую сигнатуру:
new ClipboardItem(
items: Record<string, string | Blob | PromiseLike<string | Blob>>,
options?: ClipboardItemOptions
)
- items — данные для записи в форме объектов, ключами которых являются MIME-типы, а значениями — строки, Blob или промисы, разрешающиеся строками или Blob;
- options — опциональные настройки (точнее, одна настройка — presentationStyle).
В действительности, ClipboardItem можно использовать также для работы с текстовыми данными, но есть один нюанс, о котором чуть позже.
Что касается событий copy, cut и paste, то их обработка обычно выполняется через свойство clipboardData события ClipboardEvent, которое содержит объект DataTransfer, предоставляющий следующие методы:
- setData(format: string, data: string) — для записи данных;
- getData(format: string) — для чтения данных;
- clearData() — для удаления данных и др.
Пример использования
Начнем с записи и чтения текста. Редактируем файл index.html следующим образом:
Получаем ссылки на DOM-элементы:
Определяем функцию копирования текста:
async function copyText() {
let textToCopy;
// получаем выделение
const selectedText = getSelection().toString().trim();
// текстом для копирования является либо выделенный текст, либо содержимое `copyBox`
selectedText
? (textToCopy = selectedText)
: (textToCopy = copyBox.textContent.trim());
// если текст отсутствует
if (!textToCopy) {
logBox.textContent = "No text to copy";
return;
}
try {
// записываем текст в буфер
await navigator.clipboard.writeText(textToCopy);
logBox.textContent = "Copy success";
} catch (e) {
console.error(e);
logBox.textContent = "Copy error";
}
}
Определяем функцию для вставки текста:
Наконец, регистрируем соответствующие обработчики:
copyBtn.addEventListener("click", copyText);
pasteBtn.addEventListener("click", pasteText);
Обратите внимание: при первой вставке текста браузер запрашивает разрешение на чтение буфера. При отказе в разрешении выбрасывается исключение DOMException: Read permission denied.
Записать текстовые данные с помощью ClipboardItem можно следующим образом:
Обратите внимание: несмотря на то, что значением объекта ClipboardItem может быть строка (new ClipboardItem({ [type]: text })), при записи такого объекта в буфер выбрасывается исключение DOMException: Invalid Blob types.
Также обратите внимание, что при программной записи данных в случае, когда страница находится в фоновом режиме, выбрасывается исключение DOMException: Document is not focused.
Для извлечения данных из ClipboardItem используется метод getType:
const blob = await item.getType(type);
const text = await blob.text();
console.log(text); // Text to copy
Добавим возможность копирования и вставки изображения, хранящегося на сервере.
Добавляем кнопки в index.html:
<div>
<button id="copy-img-btn">Copy remote image</button>
<button id="paste-img-btn">Paste remote image</button>
</div>
Определяем тип и функцию для копирования изображения:
const IMG_TYPE = "image/png";
async function copyRemoteImg() {
try {
const response = await fetch(
"https://images.unsplash.com/photo-1529788295308-1eace6f67388..."
);
// см. ниже
const blob = new Blob([await response.blob()], { type: IMG_TYPE });
// создаем элемент копирования
const item = new ClipboardItem({ [blob.type]: blob });
// записываем его в буфер
await navigator.clipboard.write([item]);
logBox.textContent = "Copy image success";
} catch (e) {
console.error(e);
logBox.textContent = "Copy image error";
}
}
Не уверен насчет других браузеров, но в Chrome наблюдается следующее:
- при преобразовании изображения из тела ответа в Blob с помощью response.blob() дефолтным типом становится image/jpeg (независимо от типа запрашиваемого изображения);
- при попытке записи такого Blob в буфер выбрасывается исключение DOMException: Type image/jpeg not supported on write.
Поэтому приходится выполнять двойное преобразование с помощью new Blob([await response.blob()], { type: IMG_TYPE });.
Определяем функцию для чтения данных из буфера и вставки изображения:
Итерация по item.types является безопасной, в отличие от прямого обращения к item.getType() — при отсутствии типа выбрасывается исключение DOMException: Failed to execute 'getType' on 'ClipboardItem': The type was not found.
Регистрируем соответствующие обработчики:
copyImgBtn.addEventListener("click", copyRemoteImg);
pasteImgBtn.addEventListener("click", pasteRemoteImg);
Реализуем модификацию копируемых и вставляемых данных.
Редактируем index.html:
<textarea cols="30" rows="10" id="text-area">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Libero, labore.</textarea>
Определяем функцию модификации копируемых данных:
Данная функция добавляет к копируемому тексту строку copied from MySite.com.
Определяем функцию модификации добавляемых данных:
Данная функция переводит добавляемый текст в верхний регистр.
Обратите внимание, что в обоих случаях отключается стандартная обработка события браузером с помощью e.preventDefault().
Регистрируем соответствующие обработчики:
textArea.addEventListener("copy", onCopy); textArea.addEventListener("paste", onPaste);
Поддержка — 95.08%.
Notifications API
Notifications API позволяет отображать системные уведомления. Особенность этих уведомлений состоит в том, что они находятся вне контекста окна браузера, поэтому могут отображаться даже если пользователь сменил вкладку или свернул окно. Данный интерфейс разработан таким образом, что совместим со встроенными механизмами уведомлений на большинстве платформ.
Интерфейс
Для запроса разрешения на показ уведомлений используется метод Notification.requestPermission. Данный метод возвращает промис, который разрешается или отклоняется со статусом разрешения. Статус разрешения содержится в свойстве Notification.permission и может иметь одно из трех значений:
- default — запрос на разрешение не выполнялся, уведомления не отображаются;
- granted — пользователь предоставил разрешение, уведомления отображаются;
- denied — пользователь отклонил запрос, уведомления не отображаются.
Для создания уведомления используется конструктор Notification, который имеет следующую сигнатуру:
new Notification(title: string, options?: NotificationOptions | undefined)
- title: string — заголовок уведомления;
- options — опциональный объект с настройками, такими как:
- body: string — тело уведомления;
- icon: string — ссылка на иконку;
- tag: string — тег, используемый для идентификации уведомления. Тег позволяет обновлять уведомления без их отображения, что может быть полезным при большом количестве уведомлений;
- image: string — ссылка на изображение;
- data: any — данные, ассоциированные с уведомлением и др.
Для закрытия уведомления используется метод notification.close.
Notifications API позволяет обрабатывать следующие события:
- show — отображение уведомления;
- close — закрытие уведомления;
- click — нажатие на уведомление;
- error.
Пример использования
В качестве примера реализуем отображение уведомлений о скрытии страницы. При клике по уведомлению в контейнер для логгов будет выводиться соответствующее сообщение.
Определяем в index.html кнопку для запроса разрешения на показ уведомлений:
<button id="notification-btn">Enable notifications</button>
Регистрируем соответствующий обработчик в main.js:
notificationBtn.addEventListener("click", () => { Notification.requestPermission();
});
Определяем переменные для уведомления и идентификатора таймера, а также функцию для создания уведомления:
Несмотря на то, что большинство браузеров автоматически уничтожают уведомления по прошествии некоторого времени (около 4 сек), рекомендуется делать это явно.
Расширяем обработку изменения состояния видимости страницы:
При клике по уведомлению на странице приложения появляется сообщение Notification clicked.
Обратите внимание: при нахождении в другой вкладке уведомление уничтожается через 3 сек, а при возвращении в приложение — сразу.
Поддержка оставляет желать лучшего — 79.86%.
Performance API
Performance API позволяет измерять задержку в приложении на стороне клиента. Интерфейсы Performance (интерфейсы производительности) считаются высокоточными (high resolution), поскольку имеют точность, равную тысячным миллисекунды (точность зависит от ограничений аппаратного или программного обеспечения). Данные интерфейсы используются для вычисления частоты кадров (например, в анимации) и бенчмаркинге (например, для измерения времени загрузки ресурса).
Поскольку системные часы (system clock) платформы подвергаются различным корректировкам (таким как коррекция времени по NTP), интерфейсы Performance поддерживают монотонные часы (monotonic clock), т.е. время, которое все время увеличивается. Для этого Performance API определяет тип DOMHighResTimeStamp вместо использования интерфейса Date.now().
DOMHighResTimeStamp представляет высокоточную отметку времени (point in time). Данный тип является double и используется интерфейсами производительности. Значение DOMHighResTimeStamp может быть дискретной отметкой времени или разницей между двумя такими отметками.
Единицей DOMHighResTimeStamp является миллисекунда с точностью до 5 микросекунд. Если браузер не может обеспечить такую точность, допускается представление значения в миллисекундах с точностью до миллисекунды.
Интерфейс
Основным методом, предоставляемым Performance API, является метод now, который возвращает DOMHighResTimeStamp, значение которого зависит от времени создания контекста браузера или воркера (worker).
Кроме этого, рассматриваемый интерфейс содержит два основных свойства:
- timing — возвращает объект PerformanceTiming, содержащий такую информацию, как время начала навигации, время начала и завершения перенаправлений, время начала и завершения ответов и т.д.;
- navigation — возвращает объект PerformanceNavigation, представляющий тип навигации, происходящей в текущем контексте браузера, такой как переход к странице из истории, по ссылке и т.п.
Пример использования
В качестве примера реализуем функцию для измерения времени выполнения другой функции.
Редактируем main.js:
Определяем функцию вычисления факториала числа и измеряем время ее выполнения:
const getFactorial = (n) => (n <= 1 ? 1 : n * getFactorial(n - 1));
howLong(getFactorial)(12);
Определяем функцию получения данных из сети и измеряем время ее выполнения:
const fetchSomething = (url) => fetch(url).then((r) => r.json());
howLong(fetchSomething)("https://jsonplaceholder.typicode.com/users?_limit=10");
Результат:
Поддержка — 97.17%.
Иии...
На этом все, надеюсь ты узнал что-то новое и применишь это на практике. Иначе зачем я писал этот и прошлый пост