Базовый обзор того, как создать адаптивный и доступный компонент всплывающего уведомления.
В этом посте я хочу поделиться мыслями о том, как создать компонент тоста. Попробуйте демо .
Если вы предпочитаете видео, вот версия этого поста на YouTube:
Обзор
Тосты — это неинтерактивные, пассивные и асинхронные короткие сообщения для пользователей. Обычно они используются в качестве шаблона обратной связи интерфейса для информирования пользователя о результатах действия.
Взаимодействия
Тосты отличаются от уведомлений, предупреждений и подсказок , поскольку они не интерактивны; они не предназначены для того, чтобы их отвергали или упорствовали. Уведомления предназначены для более важной информации, синхронного обмена сообщениями, требующими взаимодействия, или сообщений системного уровня (в отличие от уровня страницы). Тосты более пассивны, чем другие стратегии уведомления.
Разметка
Элемент <output>
— хороший выбор для всплывающего уведомления, поскольку он объявляется программам чтения с экрана. Правильный HTML обеспечивает нам безопасную основу для улучшения с помощью JavaScript и CSS, а JavaScript будет много.
тост
<output class="gui-toast">Item added to cart</output>
Его можно сделать более инклюзивным, добавив role="status"
. Это обеспечивает запасной вариант, если браузер не предоставляет элементам <output>
неявную роль согласно спецификации.
<output role="status" class="gui-toast">Item added to cart</output>
Контейнер для тостов
Одновременно может быть показано более одного тоста. Для организации нескольких тостов используется контейнер. Этот контейнер также обрабатывает положение всплывающих уведомлений на экране.
<section class="gui-toast-group">
<output role="status">Wizard Rose added to cart</output>
<output role="status">Self Watering Pot added to cart</output>
</section>
Макеты
Я решил прикрепить всплывающие уведомления к inset-block-end
просмотра, и если добавляется больше тостов, они складываются от этого края экрана.
Контейнер с графическим интерфейсом
Контейнер тостов выполняет всю работу по макетированию всплывающих уведомлений. Он fixed
к области просмотра и использует inset
логического свойства, чтобы указать, к каким краям прикрепиться, а также немного padding
от того же края block-end
.
.gui-toast-group {
position: fixed;
z-index: 1;
inset-block-end: 0;
inset-inline: 0;
padding-block-end: 5vh;
}
Контейнер тостов не только позиционируется в области просмотра, но и представляет собой сеточный контейнер, который может выравнивать и распределять тосты. Элементы центрируются как группа с помощью justify-content
и индивидуально с помощью justify-items
. Оставьте небольшой gap
, чтобы тосты не соприкасались.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
Тост графического интерфейса
Отдельный тост имеет padding
, несколько более мягких углов с border-radius
и функцию min()
, помогающую изменять размеры на мобильных устройствах и настольных компьютерах. Адаптивный размер в следующем CSS предотвращает увеличение ширины всплывающего уведомления более чем на 90 % области просмотра или 25ch
.
.gui-toast {
max-inline-size: min(25ch, 90vw);
padding-block: .5ch;
padding-inline: 1ch;
border-radius: 3px;
font-size: 1rem;
}
Стили
После настройки макета и позиционирования добавьте CSS, который поможет адаптироваться к настройкам и взаимодействиям пользователя.
Контейнер для тостов
Тосты не являются интерактивными, касание или пролистывание по ним ничего не делает, но в настоящее время они потребляют события указателя. Предотвратите кражу кликов всплывающими уведомлениями с помощью следующего CSS.
.gui-toast-group {
pointer-events: none;
}
Тост графического интерфейса
Придайте всплывающим уведомлениям светлую или темную адаптивную тему с настраиваемыми свойствами, HSL и медиа-запросом предпочтений.
.gui-toast {
--_bg-lightness: 90%;
color: black;
background: hsl(0 0% var(--_bg-lightness) / 90%);
}
@media (prefers-color-scheme: dark) {
.gui-toast {
color: white;
--_bg-lightness: 20%;
}
}
Анимация
Новое всплывающее уведомление должно сопровождаться анимацией при выходе на экран. Уменьшение движения достигается путем установки значений translate
на 0
по умолчанию, но обновления значения движения до длины в медиазапросе предпочтений движения. У всех есть анимация, но только у некоторых пользователей тост перемещается на большое расстояние.
Вот ключевые кадры, используемые для анимации всплывающего уведомления. CSS будет управлять входом, ожиданием и выходом всплывающего уведомления в одной анимации.
@keyframes fade-in {
from { opacity: 0 }
}
@keyframes fade-out {
to { opacity: 0 }
}
@keyframes slide-in {
from { transform: translateY(var(--_travel-distance, 10px)) }
}
Затем элемент Toast настраивает переменные и координирует ключевые кадры.
.gui-toast {
--_duration: 3s;
--_travel-distance: 0;
will-change: transform;
animation:
fade-in .3s ease,
slide-in .3s ease,
fade-out .3s ease var(--_duration);
}
@media (prefers-reduced-motion: no-preference) {
.gui-toast {
--_travel-distance: 5vh;
}
}
JavaScript
Когда стили и средства чтения с экрана готовы к использованию HTML, JavaScript необходим для организации создания, добавления и удаления всплывающих уведомлений на основе пользовательских событий. Опыт разработчика компонента всплывающего уведомления должен быть минимальным и простым для начала, например:
import Toast from './toast.js'
Toast('My first toast')
Создание группы тостов и тостов
Когда модуль всплывающего уведомления загружается из JavaScript, он должен создать контейнер всплывающего уведомления и добавить его на страницу. Я решил добавить элемент перед body
, это сделает маловероятными проблемы с наложением z-index
, поскольку контейнер находится над контейнером для всех элементов тела.
const init = () => {
const node = document.createElement('section')
node.classList.add('gui-toast-group')
document.firstElementChild.insertBefore(node, document.body)
return node
}
Функция init()
вызывается внутри модуля, сохраняя элемент как Toaster
:
const Toaster = init()
Создание HTML-элемента Toast выполняется с помощью функции createToast()
. Функция требует некоторый текст для всплывающего уведомления, создает элемент <output>
, дополняет его некоторыми классами и атрибутами, устанавливает текст и возвращает узел.
const createToast = text => {
const node = document.createElement('output')
node.innerText = text
node.classList.add('gui-toast')
node.setAttribute('role', 'status')
return node
}
Управление одним или несколькими тостами
Теперь JavaScript добавляет в документ контейнер для всплывающих уведомлений и готов добавить созданные всплывающие уведомления. Функция addToast()
управляет обработкой одного или нескольких всплывающих уведомлений. Сначала проверяется количество всплывающих уведомлений и наличие движения, а затем используется эта информация для добавления всплывающего уведомления или создания какой-нибудь причудливой анимации, чтобы другие всплывающие уведомления «освободили место» для нового всплывающего уведомления.
const addToast = toast => {
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Toaster.children.length && motionOK
? flipToast(toast)
: Toaster.appendChild(toast)
}
При добавлении первого всплывающего уведомления Toaster.appendChild(toast)
добавляет всплывающее уведомление на страницу, запускающую анимацию CSS: анимация, ожидание 3s
, анимация. flipToast()
вызывается при наличии тостов с использованием метода FLIP Пола Льюиса . Идея состоит в том, чтобы вычислить разницу в позициях контейнера до и после добавления нового всплывающего уведомления. Думайте об этом как о том, что вы отмечаете, где тостер сейчас находится и где он будет, а затем анимируете то место, где он был, туда, где он находится.
const flipToast = toast => {
// FIRST
const first = Toaster.offsetHeight
// add new child to change container size
Toaster.appendChild(toast)
// LAST
const last = Toaster.offsetHeight
// INVERT
const invert = last - first
// PLAY
const animation = Toaster.animate([
{ transform: `translateY(${invert}px)` },
{ transform: 'translateY(0)' }
], {
duration: 150,
easing: 'ease-out',
})
}
CSS-сетка поднимает макет. Когда добавляется новый тост, Grid помещает его в начало и размещает между остальными. Между тем, веб-анимация используется для анимации контейнера со старой позиции.
Собираем весь JavaScript воедино
При вызове Toast('my first toast')
создается всплывающее уведомление, добавляется на страницу (возможно, даже контейнер анимируется для размещения нового всплывающего уведомления), возвращается обещание и созданное всплывающее уведомление отслеживается на предмет завершения CSS-анимации ( три анимации ключевых кадров) для обещанного разрешения.
const Toast = text => {
let toast = createToast(text)
addToast(toast)
return new Promise(async (resolve, reject) => {
await Promise.allSettled(
toast.getAnimations().map(animation =>
animation.finished
)
)
Toaster.removeChild(toast)
resolve()
})
}
Я чувствовал, что самая запутанная часть этого кода находится в функции Promise.allSettled()
и сопоставлении toast.getAnimations()
. Поскольку для всплывающего уведомления я использовал несколько анимаций по ключевым кадрам, чтобы с уверенностью знать, что все они завершились, каждый из них должен быть запрошен из JavaScript, и каждое из их finished
обещаний должно быть проверено на предмет завершения. allSettled
делает эту работу за нас, считая себя завершенным, как только все его обещания были выполнены. Использование await Promise.allSettled()
означает, что следующая строка кода может уверенно удалить элемент и предположить, что всплывающее уведомление завершило свой жизненный цикл. Наконец, вызов resolve()
выполняет высокоуровневое обещание Toast, поэтому разработчики могут очистить или выполнить другую работу после отображения всплывающего уведомления.
export default Toast
Наконец, функция Toast
экспортируется из модуля для импорта и использования другими скриптами.
Использование компонента Toast
Использование всплывающего уведомления или опыта разработчика всплывающего уведомления осуществляется путем импорта функции Toast
и вызова ее со строкой сообщения.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
Если разработчик хочет выполнить работу по очистке или что-то еще, после отображения всплывающего уведомления он может использовать async и await .
import Toast from './toast.js'
async function example() {
await Toast('Wizard Rose added to cart')
console.log('toast finished')
}
Заключение
Теперь, когда вы знаете, как я это сделал, как бы вы‽ 🙂
Давайте разнообразим наши подходы и изучим все способы разработки в Интернете. Создайте демо, пришлите мне ссылку в Твиттере , и я добавлю ее в раздел ремиксов сообщества ниже!
Ремиксы сообщества
- @_developit с HTML/CSS/JS: демонстрация и код
- Йост ван дер Шее с HTML/CSS/JS: демо и код