JavaScript: малоизвестные, но полезные API

Kate

Administrator
Команда форума

Код примеров на GitHub
.

Page Visibility API​


Данный интерфейс позволяет определять, когда пользователь покидает страницу. Точнее, он вызывает событие при каждом изменении состояния видимости (visibility status) страницы, например, когда пользователь сворачивает/разворачивает окно, переходит на другую вкладку и т.д.


Раньше для этого приходилось прибегать к таким уловкам, как обработка событий blur и focus. Соответствующий код выглядел так:


window.addEventListener('blur', () => {
// пользователь покинул страницу
})

window.addEventListener('focus', () => {
// пользователь вернулся на страницу
})

Приведенный код работает, но не совсем так, как ожидается. Поскольку событие blur вызывается, когда страница теряет фокус, оно может возникнуть, когда пользователь нажимает на поиск, диалоговое окно (alert), консоль или границу окна. События blur и focus сообщают нам о том, что страница активна, но не о том, видим или скрыт ее контент.


Случаи использования​


Page Visibility API может использоваться для предотвращения выполнения операций, которые имеют значение, только когда пользователь видит страницу, или для выполнения фоновых операций. Еще несколько кейсов:


  • приостановка воспроизведения видео, каруселей изображений (автослайдеров) или анимации, когда пользователь покидает страницу;
  • если на странице отображаются данные в реальном времени, нет смысла их обновлять, если пользователь покинул страницу;
  • отправка аналитических данных о действиях пользователя.

Интерфейс​


Page Visibility API предоставляет 2 свойства и одно событие для получения доступа к состоянию видимости страницы:


  • document.hidden — доступное только для чтения глобальное свойство. Признано устаревшим. Если страница скрыта, возвращается true, иначе — false;
  • document.visibilityState — обновленная версия document.hidden. Возвращает 4 возможных значения:
    • visible — страница видима или, если быть точнее, страница не свернута и находится в текущей вкладке;
    • hidden — страница скрыта;
    • prerender — начальное состояние видимой страницы: предварительный рендеринг;
    • unloaded — страница выгружена из памяти;
  • visibilitychange — событие объекта document, возникающее при изменении visibilityState:

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// страница видима
} else {
// страница скрыта
}
})

Пример использования​


В качестве примера рассмотрим приостановку видео и прекращение получения ресурсов из API, когда пользователь покидает страницу. Создаем шаблон проекта с помощью Vite и Yarn:


# unknown-web-apis - название проекта
# --template vanilla - шаблон на чистом JS
yarn create vite unknown-web-apis --template vanilla

Переходим в созданную директорию, устанавливаем зависимости и запускаем сервер для разработки:


cd unknown-web-apis
yarn
yarn dev

Приложение доступно по адресу http://localhost:3000.


Удаляем шаблонный код из файла main.js и добавляем элемент video в файле index.html:


<div id="app">
<video controls id="video" src="https://joy.videvo.net/videvo_files...d/_import_61516586ee8571.11252072_preview.mp4"></video>
</div>

Возвращаемся к main.js. Добавляем обработчик события visibilitychange объекта document:


document.addEventListener("visibilitychange", () => {
// выводим состояние видимости страницы в консоль
console.log(document.visibilityState);
});

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


Получаем ссылку на элемент video и управляем воспроизведением видео в зависимости от видимости страницы:


const video = document.getElementById("video");

document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
video.play();
} else {
video.pause();
}
});

Прим. пер.: приведенный код работать не будет: получаем ошибку Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.. Это объясняется тем, что вызов video.play() при изменении состояния видимости страницы на visible равносилен наличию у элемента video атрибута autoplay. Современные браузеры допускают автоматическое (без участия пользователя) воспроизведение видео только в режиме без звука. Соответственно, для того, чтобы код работал, как ожидается, элементу video необходимо добавить атрибут muted.


Когда пользователь покидает страницу, воспроизведение видео приостанавливается. Когда пользователь возвращается на страницу, воспроизведение видео продолжается.


Теперь рассмотрим пример прекращения выполнения запросов к API. Для этого напишем функцию, запрашивающую цитату из quotable.io. Добавляем в index.html элемент div для хранения цитаты:


<div id="quote"></div>

Определяем в main.js функцию для получения произвольной цитаты с помощью Fetch API:


const quote = document.getElementById("quote");

const getQuote = async () => {
try {
const response = await fetch("https://api.quotable.io/random");
const { content, author, dateAdded } = await response.json();
const parsedQuote = `<q>${content}</q> <br> <p>- ${author}</p> <br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
} catch (e) {
console.error(e);
}
};

getQuote();

Приведенный код работает, но функция getQuote вызывается только один раз. Обернем ее в setInterval с интервалом в 10 секунд:


setInterval(getQuote, 10000);

Если пользователь свернет окно или перейдет на другую вкладку, запросы все равно будут отправляться каждые 10 секунд. Давайте это исправим:


const getQuote = async () => {
if (document.visibilityState !== "visible") return;

// остальной код
};

Теперь запросы будут выполняться только при условии, что страница находится в видимом состоянии.


Поддержка — 98.24%.


Web Share API​


Web Share API предоставляет разработчикам доступ к встроенному механизму совместного использования (native sharing mechanism) операционной системы, что особенно актуально для мобильных телефонов. Данный интерфейс позволяет делиться текстом, ссылками и файлами без создания собственного механизма или использования сторонних решений.


Случаи использования​


Web Share API позволяет делиться содержимым страницы в соцсейтях или копировать его в буфер обмена пользователя.


Интерфейс​


Web Share API предоставляет 2 метода:


  • navigator.canShare(data): принимает данные для совместного использования и возвращает логическое значение — индикатор того, можно ли этими данными поделиться;
  • navigator.share(data): возвращает промис, который разрешается в случае успешного шаринга (sharing) данных. Данный метод вызывает нативный механизм и принимает данные для шаринга. Обратите внимание: этот метод может вызываться только в ответ на действие пользователя (нажатие кнопки, переход по ссылке и т.п.) (требуется кратковременная активация). data — это объект со следующими свойствами:
  • url — ссылка для шаринга;
  • text — текст;
  • title — заголовок;
  • files — массив объектов File.

Пример использования​


Возьмем последний пример и добавим возможность делиться цитатой. Добавляем соответствующую кнопку в index.html:


<button id="share-button">Share Quote</button>

В main.js получаем ссылку на эту кнопку и определяем функцию для шаринга данных:


const shareButton = document.getElementById("share-data");

const shareQuote = async (data) => {
try {
await navigator.share(data);
} catch (e) {
console.error(e);
}
};

Нам также потребуется глобальная переменная для хранения содержимого текущей цитаты:


let quoteText;

const getQuote = async () => {
if (document.visibilityState !== "visible") return;

try {
// ...
quoteText = content;
} catch (e) {
console.error(e);
}
};

Определяем обработчик нажатия кнопки:


shareButton.addEventListener("click", () => {
const data = {
title: "A Beautiful Quote",
text: quoteText,
url: location.href,
};

shareQuote(data);
});

Обратите внимание: Web Share API работает только в безопасном окружении. Это означает, что страница должна обслуживаться по протоколу https или wss.


Поддержка — 89.82%.


Broadcast Channel API​


Broadcast Channel API позволяет контекстам браузера (browser contexts) обмениваться данными друг с другом. К браузерным контекстам относятся такие элементы, как окно, вкладка, iframe и т.д. По причинам безопасности контексты, обменивающиеся данными, должны принадлежать одному источнику (same origin). Один источник означает одинаковый протокол, домен и порт.


Случаи использования​


Broadcast Channel API обычно используется для синхронизации окон и вкладок браузера для улучшения пользовательского опыта или повышения безопасности. Он также может применяться для уведомления одного контекста о завершении процесса в другом контексте. Другие примеры:


  • авторизация пользователя во всех вкладках;
  • отображение загруженного ресурса во всех вкладках;
  • запуск сервис-воркера для выполнения фоновой задачи.

Интерфейс​


Broadcast Channel API предоставляет объект BroadcastChannel, позволяющий обмениваться сообщениями с другими контекстами. Конструктор этого объекта принимает единственный аргумент: строку — идентификатор канала (channel identifier):


const broadcastChannel = new BroadcastChannel("channel_identifier");

BroadcastChannel предоставляет 2 метода:


  • broadcastChannel.postMessage(message): позволяет отправлять сообщения всем подключенным контекстам. В качестве аргумента данный метод принимает любой тип данных:

broadcastChannel.postMessage("Example message");

  • broadcastChannel.close(): закрываем канал коммуникации, что позволяет браузеру выполнить сборку мусора.

При получении сообщения возникает событие message. Это событие содержит свойство data с отправленными данными, а также другие свойства, позволяющие идентифицировать отправителя, такие как origin, lastEventId, source и ports:


broadcastChannel.addEventListener("message", ({ data, origin }) => {
console.log(`${origin} says ${data}`);
});

Пример использования​


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


Создаем директорию new-context в корне проекта. Создаем в ней файл index.html следующего содержания:


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:." />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="quote"></div>
<script type="module" src="./main.js"></script>
</body>
</html>

Создаем файл new-context/main.js.


Получаем ссылку на div и создаем новый канал коммуникации:


const quote = document.getElementById("quote");
const broadcastChannel = new BroadcastChannel("quote_channel");

Добавляем обработчик события message:


broadcastChannel.addEventListener("message", ({ data }) => {
quote.innerHTML = data;
});

Создаем канал в основном main.js и редактируем функцию getQuote:


const broadcastChannel = new BroadcastChannel("quote_channel");

const getQuote = async () => {
if (document.visibilityState !== "visible") return;

try {
// ...
broadcastChannel.postMessage(parsedQuote);
} catch (e) {
console.error(e);
}
};

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


  • в корне проекта создаем файл vite.config.js следующего содержания:

import { defineConfig } from "vite";

export default defineConfig({
appType: "mpa",
});


Поддержка — 92.3%.


Internationalization API​


Шпаргалка по Internationalization API


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


Предположим, что мы хотим отобразить на странице "10 ноября 2022 года" как "11/10/22". Эта дата в разных странах будет выглядеть по-разному:


  • 11/10/22 или ММ/ДД/ГГ в США;
  • 10/11/22 в Европе и Латинской Америке;
  • 22/11/10 в Японии, Китае и Канаде.

Здесь на помощь приходит Internationalization API (или I18n). Данный интерфейс позволяет решить несколько групп задач, связанных с интернационализацией и локализацией, но в этой статье мы не будем погружаться в него слишком глубоко.


Интерфейс​


Для определения страны пользователя в I18n используется идентификатор локали (или просто локаль) (locale identifier, locale). Локаль — это строка, представляющая страну, регион, диалект и другие характеристики. Если точнее, локаль — это строка, состоящая из подтегов (subtags), разделенных дефисом, например:


  • zh — китайский (язык);
  • zh-Hant — китайский (язык), традиционные иероглифы (сценарий — script);
  • zh-Hang-TW — китайский (язык), традиционные иероглифы (сценарий), используемые на Тайване (регион).

Полный список подтегов можно найти в этом RFC.


I18n предоставляет объект Intl, который, в свою очередь, предоставляет несколько специальных конструкторов для работы с чувствительными к языку данными, наиболее интересными из которых являются следующие:


  • Intl.DateTimeFormat — форматирование даты и времени;
  • Intl.DisplayNames — форматирование названий языков, регионов и сценариев;
  • Intl.Locale — генерация и манипуляция идентификаторами локалей;
  • Intl.NumberFormat — форматирование чисел;
  • Intl.RelativeTimeFormat — форматирование относительного времени (завтра, 2 дня назад и т.п.).

Пример использования​


В качестве примера рассмотрим использование конструктора Intl.DateTimeFormat для форматирование свойства dateAdded цитат. Данный конструктор принимает 2 аргумента: строку locale для определения правил форматирование даты и объект options для кастомизации форматирования.


Прим. пер.: в качестве первого аргумента Intl.DateTimeFormat также принимает массив локалей. Например, для установки дефолтной локали пользователя в конструктор передается пустой массив ([]).


Объект, возвращаемый Intl.DateTimeFormat, предоставляет метод format, который принимает объект Date с датой для форматирования и объект options для кастомизации отображения форматированной даты:


const logDate = (locale = []) => {
const date = new Date("2022-11-10");
const dateTime = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
const formattedDate = dateTime.format(date);
console.log(formattedDate);
};

logDate(); // 10.11.2022
logDate("en-US"); // 11/10/2022
logDate("de-DE"); // 10.11.2022
logDate("zh-TW"); // 2022/11/10

Обратите внимание: мы установили настройку timeZone в значение UTC для того, чтобы при форматировании даты не учитывалось локальное время пользователя.


Определяем в main.js функцию для форматирования даты:


function formatDate(dateString) {
const date = new Date(dateString);
const dateTime = new Intl.DateTimeFormat([], { timeZone: "UTC" });
return dateTime.format(date);
}

Вызываем эту функцию внутри функции getQuote для форматирования свойства dateAdded:


const getQuote = async () => {
if (document.visibilityState !== "visible") return;

try {
// ...
const parsedQuote = `<q>${content}</q> <br> <p>- ${author}</p> <br> <p>Added on ${formatDate(
dateAdded
)}</p>`;
// ...
} catch (e) {
console.error(e);
}
};

Поддержка — 97.74%.


Прим. пер.: на днях использовал Intl.DateTimeFormat для отображения даты и времени в коротком формате:


const getDateWithHoursAndMinutes = (date) =>
new Intl.DateTimeFormat([], {
dateStyle: "short",
timeStyle: "short",
}).format(date);

console.log(getDateWithHoursAndMinutes(new Date())); // 23.09.2022, 21:30

Надеюсь, что вы, как и я, узнали что-то новое и не зря потратили время.


Благодарю за внимание и happy coding!

 
Сверху