Введение.
Существует множество отличных статей, посвященных скелетон загрузчикам, в которых рассказывается об их типах, случаях и необходимости их использования. Я не буду перечислять их здесь, вы можете легко найти их в своем любимом поисковике.
После подробного изучения этой темы, я решил создать очень простое, гибкое, переиспользуемое, настраиваемое и легкое решение, которое подойдет для большинства случаев использования. В этой статье я опишу процесс создания этого решения и превращения его в библиотеку, а также трудности, с которыми я столкнулся при работе над ним.
Что такое Скелетон загрузчики?
Примечание: Вы можете пропустить этот раздел, если знаете, что такое скелетон.
Скелетон загрузчик, также известный как скелетон экран или заполнитель, представляет собой шаблон проектирования пользовательского интерфейса, используемый для улучшения взаимодействия с пользователем во время загрузки содержимого в веб и мобильных приложениях. Когда данные загружаются или обрабатываются в фоновом режиме, вместо отображения пустого экрана, скелетон имитирует макет страницы, предоставляя пользователям визуальную подсказку о том, чего ожидать, сокращая воспринимаемое время загрузки и смягчая потенциальное разочарование.
Вот примеры скелетонов из LinkedIn и Youtube:
Почему нужно использовать скелетоны?
-
Улучшенное взаимодействия с пользователем: Скелетоны улучшают взаимодействие с пользователем, предоставляя визуальную обратную связь и уменьшая восприятие задержек загрузки контента.
-
Уменьшение показателей отказов: Они могут препятствовать тому, что пользователь покинет страницу из-за задержек загрузки.
-
Плавные переходы: Создают более плавные переходы между различными состояниями страницы или приложения.
-
В отличие от спиннеров, скелетоны привлекают внимание пользователя к прогрессу, а не к времени ожидания.
Проблемы большинства существующих скелетонов.
Принимая во внимание существование множества примеров создания собственных скелетонов и наличие готовых библиотек, которые сделали это за вас, с ними все равно остается ряд недостатков.
-
Ограниченная настраиваемость: Многие существующие скелетоны имеют ограниченные возможности настройки. Это приводит к несоответствию стилей фактического дизайна и скелетона.
-
Хотя их целью является обеспечение визуального представления, большинство из них не адаптированы для пользователей с нарушениями зрения, или тех, кто использует программы для помощи чтения с экрана.
-
Универсальность и возможность повторного использования. Большинство подходов к созданию скелетонов предлагают либо создание поверхностных копий компонентов в виде заполнителей, в результате чего получается множество копий, либо существенное изменение структуры существующих компонентов. Оба подхода требуют достаточно много усилий для дополнительной разработки и ресурсов.
-
Сложность обслуживания: По мере развития веб-сайтов и изменения контента, поддержание скелетонов в актуальном состоянии может стать довольно сложным.
Альтернативы.
Существуют некоторые «альтернативы» использованию скелетонов.
Забегая вперед и отвечая, действительно ли существуют альтернативы, мой ответ скорее нет, чем да. Если говорить о правильном использовании, то скелетон — одно из лучших решений. Ниже я все же приведу пару примеров вместе с их плюсами и минусами.
Спиннер.
Спиннер — распространенная альтернатива скелетонам. Они состоят из анимированных иконок, которые постоянно вращаются, визуально показывая, что контент загружается.
Плюсы.
-
Простота: Простая реализация, часто требующая всего нескольких строк кода или использующая предварительно разработанные библиотеки.
-
Универсальное понимание: Спиннеры широко известны на разных платформах и приложениях, гарантируя, что пользователи понимают что контент загружается.
Минусы.
-
Ограниченная информация: Спиннеры не предоставляют никакой информации о загружаемом контенте.
-
Перекрывает всю страницу или ее большую часть, а не отдельные элементы. Что дает ощущение загрузки не отдельных элементов, а всего сайта в целом.
Спиннеры — неотъемлемая часть интерфейсов, но они не совсем подходят для замены скелетонов.
Прогресс Бар (Индикатор выполнения).
Прогресс Бар — это визуальный элемент, указывающий на статус завершения задачи или процесса. Он обеспечивает линейное представление, обычно с заполняемой частью, которая постепенно увеличивается.
Плюсы.
-
Точная обратная связь: Обеспечивает точную обратную связь о статусе завершения задачи.
-
Оценка времени: Прогресс бары могут дать пользователям оценку оставшегося времени, необходимого для завершения.
-
Многоцелевые: Можно использовать в различных контекстах и сценариях, что делает их универсальным компонентом в веб-разработке и разработке приложений.
Минусы.
-
Недостаток контекста: В некоторых случаях индикаторы выполнения могут не давать достаточного контекста о фактической задаче или процессе, который они представляют.
-
Сложность реализации: Создание индикаторов выполнения с точным представлением и плавной анимацией может быть сложным, особенно при работе с задачами разной продолжительности и временем отклика.
Прогресс бары больше подходят для сценариев, показывающих ход загрузки файла или количественный прогресс. Они часто используются в верхней части страниц, чтобы показать ход загрузки всей страницы. Но они не могут служить равноценной заменой скелетону, потому что предназначены для других целей.
Отсутствие какого-либо визуала.
Да, отсутствие загрузчиков или заполнителей также является альтернативой. И в ряде случаев это будет лучшим решением, чем использование неподходящих элементов. Главный и пожалуй единственный плюс в том, что вам не нужно тратить дополнительное время и ресурсы на внедрение. Но здесь появляются очевидные минусы — менее привлекательный дизайн вашего сайта и восприятие более медленного времени загрузки.
Создание универсального и переиспользуемого скелетона.
После того, как я получил достаточно знаний о том, что такое скелетоны, когда их использовать, какие они бывают и подходы к их разработке, я попытался определить для себя, каким должен быть мой конечный результат.
Универсальный и переиспользуемый.
Существует много примеров с достаточно сложными подходами, где вам нужно создать отдельный скелетон для каждого компонента, в котором вы хотите его использовать.
В моем случае, я хотел чтобы это было что-то уникальное, что можно было бы повторно использовать в большинстве сценариев и не привязываться к какому-либо JavaScript фреймворку (например, React.js или Vue.js).
Гибкость конфигурации.
Поскольку каждый проект и каждый случай могут быть совершенно разными, мой скелетон должен быть настраиваемый.
Многофункциональный.
Помимо стандартного набора, хотелось добавить в него поддержку дополнительных полезных и нужных функций.
Легковесный и без зависимостей.
Легковесный и максимально свободный от других сторонних зависимостей.
Все эти ожидания и исследования привели меня к тому, что мой будущий скелетон должен был быть написан на чистом CSS, без какого-либо JavaScript и сторонних зависимостей. Это позволяет быть легковесным и свободным от зависимостей. Основная идея заключается в том, что он наследует макеты/стили компонентов, к которым он применяется, и кастомизирует их с помощью собственных стилей. Возможно, в будущем для целей разработки имеет смысл все это переписать на синтаксис SCSS, так как это сделает код короче и переиспользуемым, а финальная сборка все равно будет компилироваться в чистый CSS.
Компонент базовой карточки.
В качестве базового примера и в целях демонстрации я буду использовать React.js и возьму разметку компонента обычной базовой карточки, чтобы показать, как это работает. Но напомню, что библиотека не привязана ни к одному из фреймворков, а в конце статьи будут ссылки на исходный код библиотеки и демо.
Вот пример разметки карточки, которая имеет свои стили и еще не знает о существовании скелетона.
<div className='card'> <div className='card__img-wrapper'> <img className='card__img' src={require(`../../images/cards/${imgUrl}`)}/> </div> <div className='card__body'> <div className='card__details'> <p className='card__title'>{title}</p> <p className='card__subtitle'>{subtitle}</p> </div> </div> </div>
Для того чтобы активировать скелетон, необходимо лишь применить родительский класс sm-loading к самой карточке, и дочерние классы sm-item-primary или sm-item-secondary к тем элементам, на которых мы хотим видеть скелетон. Таким образом, обновленный результат будет выглядеть так:
<div className={`card ${dataState.dataStatus === 'loading' ? "sm-loading" : ""}`}> <div className='card__img-wrapper sm-item-primary'> <img className='card__img' src={require(`../../images/cards/${imgUrl}`)}/> </div> <div className='card__body'> <div className='card__details'> <p className='card__title sm-item-secondary'>{title}</p> <p className='card__subtitle sm-item-secondary'>{subtitle}</p> </div> </div> </div>
Давайте я объясню что тут происходит поэтапно. В данной строке:
<div className={`card ${dataState.dataStatus === 'loading' ? "sm-loading" : ""}`}>
Я применяю класс sm-loading в зависимости от условия. Если статус dataState.dataStatus имеет значение loading, то класс будет применен, иначе — нет. Класс sm-loading должен быть установлен/присутствовать только во время загрузки ваших данных. Это что-то вроде переключателя. Только при его наличии дочерние элементы с наличием соответствующих классов sm-item-primary или sm-item-secondary будут отображать скелетон. Таким образом, всего 3 класса приведут скелетон в действие.
Базовые стили скелетона.
Корневые переменные.
Чтобы иметь аккуратный и переиспользуемый код, а также возможность дальнейшей настройки (переопределения), я создал корневые переменные с базовыми стилями.
/* Root variables. --------------------------------------------------------------------------------*/ :root { /* Light theme colors. */ --sm-color-light-primary: 204, 204, 204, 1; --sm-color-light-secondary: 227, 227, 227, 1; --sm-color-light-animation-primary: color-mix( in srgb, #fff 15%, rgba(var(--sm-color-light-primary)) ); --sm-color-light-animation-secondary: color-mix( in srgb, #fff 15%, rgba(var(--sm-color-light-secondary)) ); /* Dark theme colors. */ --sm-color-dark-primary: 37, 37, 37, 1; --sm-color-dark-secondary: 41, 41, 41, 1; --sm-color-dark-animation-primary: color-mix( in srgb, #fff 2%, rgba(var(--sm-color-dark-primary)) ); --sm-color-dark-animation-secondary: color-mix( in srgb, #fff 2%, rgba(var(--sm-color-dark-secondary)) ); /* Animations. */ --sm-animation-duration: 1.5s; --sm-animation-timing-function: linear; --sm-animation-iteration-count: infinite; }
Здесь задаются значения цветов для статического (без анимации) и анимированного скелетона, а также настройки анимации.
Базовые стили.
Следующий раздел файла посвящен базовым стилям, не связанными ни с какой цветовой схемой или конфигурацией.
/* Base styles. Applied by default and not related to any of the color scheme. --------------------------------------------------------------------------------*/ .sm-loading .sm-item-primary, .sm-loading .sm-item-secondary { border-color: transparent !important; color: transparent !important; cursor: wait; outline: none; position: relative; user-select: none; } .sm-loading .sm-item-primary:before, .sm-loading .sm-item-secondary:before { clip: rect(1px, 1px, 1px, 1px); content: "Loading, please wait."; inset: 0; overflow: hidden; position: absolute; white-space: nowrap; } .sm-loading .sm-item-primary::placeholder, .sm-loading .sm-item-secondary::placeholder { color: transparent !important; } .sm-loading .sm-item-primary *, .sm-loading .sm-item-secondary * { visibility: hidden; } .sm-loading .sm-item-primary :empty:after, .sm-loading .sm-item-primary:empty:after, .sm-loading .sm-item-secondary :empty:after, .sm-loading .sm-item-secondary:empty:after { content: "\00a0"; } /* Animations related styles. */ @keyframes --sm--animation-wave { to { background-position-x: -200%; } } @keyframes --sm--animation-wave-reverse { to { background-position-x: 200%; } } @keyframes --sm--animation-pulse { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } } .sm-loading .sm-item-primary, .sm-loading .sm-item-secondary { animation: var(--sm-animation-duration) --sm--animation-wave var(--sm-animation-timing-function) var(--sm-animation-iteration-count); }
Как указывалось ранее, sm-loading родительского класса используется для активации стилей скелетона. Классы sm-item-primary и sm-item-secondary переопределяют/дополняют стили элемента и отображают скелетон.
Таким образом, стили и размеры элементов (в нашем случае компонента карточки) сохраняются и наследуются скелетоном. Дополнительно хочу сказать, что при таком подходе мы гарантируем, что все дочерние элементы классов sm-item-primary или sm-item-secondary будут скрыты и как минимум имеют символ Неразрывного пробела. Если элемент совсем не имеет содержимого, этот символ обеспечивает отображение и визуализацию элемента. Также есть часть, которая отвечает за пользователей программ помогающий чтению с экрана (screen readers) и дает им знать, что контент находится в процессе загрузки.
Далее идет разделение на тематические разделы, такие как цветовая схема, анимация, доступность. Давайте посмотрим на стили для светлой темы.
/* Light theme. The library's default color scheme. Styles applied to the light color scheme. --------------------------------------------------------------------------------*/ .sm-loading .sm-item-primary { background: rgba(var(--sm-color-light-primary)); } .sm-loading .sm-item-secondary { background: rgba(var(--sm-color-light-secondary)); } /* Animations related styles. */ .sm-loading .sm-item-primary { background: linear-gradient( 90deg, transparent 40%, var(--sm-color-light-animation-primary) 50%, transparent 60% ) rgba(var(--sm-color-light-primary)); background-size: 200% 100%; } .sm-loading .sm-item-secondary { background: linear-gradient( 90deg, transparent 40%, var(--sm-color-light-animation-secondary) 50%, transparent 60% ) rgba(var(--sm-color-light-secondary)); background-size: 200% 100%; }
Цветовая схема.
С помощью CSS медиа функции prefers-color-scheme, я реализовал автоматическую поддержку светлой и темной темы. В зависимости от настроек пользователей, она будет применяться автоматически. Конечно же есть возможность установить тему вручную, об этом я расскажу далее в статье.
/* Dark theme. Styles to apply if a user's device settings are set to use dark color scheme. https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme --------------------------------------------------------------------------------*/ @media (prefers-color-scheme: dark) { /*Omitted pieces of code.*/ }
Доступность.
Анимации.
По умолчанию в скелетоне я решил сделать анимацию включенной, но бывают случаи, когда разработчики или пользователи предпочли бы ее выключить. И если для первых это может быть продиктовано дизайном и требованиями, то для вторых это может быть обусловлено вестибулярно-двигательными нарушениями. В этом случае на помощь приходит CSS медиа функция prefers-reduced-motion.
/* Accessibility. Disable animations if a user's device settings are set to reduced motion. https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion --------------------------------------------------------------------------------*/ @media (prefers-reduced-motion) { /*Omitted pieces of code.*/ .sm-loading .sm-item-primary, .sm-loading .sm-item-secondary { animation: none; } /*Omitted pieces of code.*/ }
Конфигурация.
На этом этапе основные стили закончены, и скелетон можно считать готовым. Но, меня преследовала мысль, что я должен иметь возможность настраивать все вышеперечисленное. Что делать, если я хочу отключить анимацию, или если я хочу всегда использовать темную тему? Поскольку CSS не может принимать какие-либо значения в качестве аргументов, как это делают функции JavaScript, а добавление JavaScript было исключено (по крайней мере, на данном этапе), потому что это полностью нарушило бы основную концепцию простоты и легкости.
Но все же, мы можем реализовать что-то похожее на аргументы, если заранее знаем их значения. И здесь нам на помощь приходят атрибуты data-*. С их помощью мы можем проверить наличие нужного нам значения в атрибуте, и применить нужные стили.
Я покажу вам, как я это реализовал на небольшом фрагменте кода, а полную реализацию вы можете найти в исходном коде по ссылке в конце статьи.
Например, если вы хотите явно использовать темную тему, вам нужно создать JSON объект:
const config = JSON.stringify({ theme: "dark", })
Примечание:
Атрибутыdata-*могут работать только со строками, поэтому важно применить методJSON.stringify()к объекту конфигурации.
Далее передаем этот объект в пользовательский атрибут data-sm-config:
<div class="card sm-loading" data-sm-config={config}> <!-- Omitted pieces of code. --> </div>
Вот как это выглядит в CSS файле. Если в data-sm-config есть значение "theme":"dark", применяются соответствующие стили.
.sm-loading[data-sm-config*='"theme":"dark"'] .sm-item-primary, .sm-loading[data-sm-config*='"theme":"dark"'] .sm-item-secondary { /* Omitted pieces of code. */ }
Продвинутое использование.
Переопределение стилей с помощью глобальных переменных.
Каждый проект и случай уникальны, и невозможно предугадать и сделать все универсальным. Особенно, когда дело касается цвета. Именно поэтому, как было сказано в начале статьи, большая часть значений помещена в глобальыне переменные. Если вы хотите настроить стили по умолчанию, просто переопределите соответствующие переменные в вашем собственном файле *.css внутри CSS псевдокласса :root.
Так, например, если вы хотите изменить цвет основного элемента (с классом sm-item-primary), вам нужно всего лишь перезаписать соответствующую переменную:
/* Your own custom.css file: */ :root { --sm-color-light-primary: 255, 0, 0, 0.5; }
Демо.
Вы можете опробовать готовый результат в действии по следующей ссылке: Live demo.
Давайте подытожим.
После того, как я долго изучал тему скелетонов, их разновидностей, использования, подходов к разработке, мне удалось собрать суть полезной информации и превратить ее в конечный продукт. Собрав лучшие практики, улучшив их и объединив в единое целое, я создал библиотеку под названием Skeleton Mammoth. Я считаю, что мне удалось достичь поставленных целей и создать довольно неплохую библиотеку со всеми достоинствами, описанными в этой статье. Я надеюсь, что эта библиотека способна принести пользу людям при ее использовании или дать новые знания и опыт для создания чего-то своего.
Ваша поддержка.
Если вы находите для себя полезной мою библиотеку и хотели бы выразить свою поддержку, есть простые способы сделать это:
Ставьте звездочку в GitHub репозитории: это помогает повысить его видимость и позволяет другим узнать, что библиотека имеет сильную пользовательскую базу.
Распространяйте информацию: вы можете познакомить новых пользователей с библиотекой, поделившись информацией о ней на любой платформе. Например, если вы напишете об этом в блоге, упомянете в социальных сетях или обсудите в соответствующих сообществах разработчиков, это будет очень полезно.
Ниже я размещу список полезных ссылок, в том числе ссылку на библиотеку.
Полезные ссылки.
-
Skeleton Mammoth — GitHub репозиторий библиотеки.
-
Live demo — Демо.
-
Live demo source code — Демо GitHub репозиторий.
ссылка на оригинал статьи https://habr.com/ru/articles/751956/
Добавить комментарий