Каркасные экраны: реализация в React
Каркасные экраны — один из самых полезных UX-паттернов. Добавляем их в приложение React с помощью библиотеки React Loading Skeleton. Обсудить Профессионалы в области UI/UX утверждают, что важно сохранять заинтересованность пользователей, пока они ожидают загрузки контента на страницу. Традиционно для этого в вебе использовались спиннеры и лоадеры. Это хороший подход, ведь анимированная иконка четко говорит пользователю: про вас не забыли, процесс идет. Однако в современных условиях он уже несколько устарел. Человек понимает, что происходит, но смотреть на движущиеся точки довольно скучно. Каждый оборот спиннера добавляет раздражения. На смену лоадерам пришли «каркасные» экраны (skeleton screens), которые не просто «тянут время», но и лучше обозначают прогресс загрузки, уменьшая негативные ощущения юзера (loading-time frustration). Другими словами, создают иллюзию того, что контент вот-вот появится. Сегодня мы будем разбираться, как их делать в React-приложениях. Разница между интерфейсом с лоадером и каркасным экраном Глобального погружения в основы CSS, JS или React не будет, поэтому вы можете смело читать дальше, даже если не являетесь экспертом в этих технологиях. Каркасный, или скелетный, экран – разновидность интерфейса, которая не содержит реального контента. Такой UI повторяет «настоящую» разметку страницы, но вместо контентных единиц – текста, изображений, карточек, блоков – показывает «заглушки», схожие с ними по форме (обычно серые линии и прямоугольники, а также их группы). Когда контент загружается и занимает свое место в макете, заглушки исчезают. Этот переход выглядит не таким резким, как переход между чистой страницей и заполненной текстом. Другими словами, это занятый плейсхолдерами вместо реальных данных «скелет» страницы. Так как скелетный UI напоминает настоящий, пользователь видит структуру страницы даже без контента, и у него создается впечатление, будто загрузка идет быстрее – по крайней мере, что первый этап загрузки уже прошел. Каркасные экраны отлично сочетаются с техникой ленивой загрузки: постепенное появление элементов выглядит более естественным, когда уже ожидает их. Другие названия техники: Этим подходом пользуются многие сайты, в том числе порталы крупных технологических компаний: Blockchain.com, YouTube, Facebook, Medium. Интерфейс Blockchain.com с частично подгруженным контентом. Для представления графиков также используется скелетный плейсхолдер. Каркасный экран на сайте medium Главная страница сайта LinkedIn в 2018 до загрузки контента Каркасные экраны бывают разными. Самые популярные – это текстовые и графические (цветные) заполнители. Текстовые используются чаще всего, так как они максимально просты в реализации. Разработчику не нужно знать особенности реального контента, чтобы заменить его заполнителем. Графические каркасы сложнее – они зависят от контента, который замещают, и встречаются реже. Для быстрой реализации паттерна каркасных экранов в вашем приложении можно воспользоваться готовыми решениями. Для React они, конечно, тоже есть. Рассмотрим парочку для примера. Репозиторий: buildo/react-placeholder Загрузка пакета: Пример создания скелетного компонента с использованием react-placeholder: За создание каркасного плейсхолдера отвечает компонент Создаем для этого функциональный компонент Все плейсхолдеры можно настраивать через т.н. пропсы. Например, задать цвет и размер (количество рядов текста) для текстового блока. Чтобы определить, когда показывать каркас, а когда уже подъехал контент, используется булево свойство Больше информации вы найдете в документации модуля. Репозиторий: dvtng/react-loading-skeleton Загрузка пакета: Пример создания скелетного компонента с использованием react-loading-skeleton: Здесь мы импортируем основной компонент Создаем функциональный компонент Рассмотрим этот модуль подробнее: разработаем с его помощью скелетный экран YouTube. Самый простой способ установить и настроить React в новом приложении – использовать утилиту Create React App. При этом вам почти ничего не придется делать – только набрать команду в терминале. Сначала установите модуль глобально, если вы этого еще не сделали: Затем создайте новый проект Когда установка завершится, перейдите в папку проекта и запустите локальный сервер: Вы увидите приветственный экран React: Вместо реальных данных с YouTube мы будем использовать заранее подготовленный массив, чтобы не усложнять руководство дополнительной логикой. Создайте файл В этом фрагменте описаны два блока страницы – «Рекомендованное» и «Последние новости» – каждый из которых содержит несколько видео-превью. Теперь используем эти данные для верстки интерфейса. Нам потребуется три компонента: Создадим папку Создайте файл Card.js Простой функциональный React-компонент, который выводит данные в шаблон. Файл CardList.js Этот код еще проще – он выводит на экран массив превьюшек Наконец, откройте файл App.js Этот компонент сложнее, так как он имеет собственное состояние. Мы используем хук Хук После «загрузки» выводим отдельно каждый блок видео с помощью метода Пока в шаблоне не используется переменная До сих пор мы только расставляли классы в верстке, но не добавляли реальные стили для них. Пора облагородить внешний вид приложения. Откройте файл Теперь можно посмотреть как выглядит интерфейс, пока без каркасных экранов. Пару секунд, когда страница загружается, мы видим белый экран, а затем все данные появляются сразу. Интерфейс клона YouTube без каркасных экранов В React Loading Skeleton вам не нужно пошагово создавать скелетный экран, тщательно подбирая параметры типографики, отступы и прочие стили под ваш контент. Его можно поместить прямо внутрь вашего компонента вместо загружаемого контента. У этой библиотеки есть пара полезных плюшек: легкая темизация и указание продолжительности цикла анимации. React Loading Skeleton поддерживает простую настройку тем. Вы можете легко поменять стиль сразу всех скелетных экранов, внеся изменения только в одном месте. За это отвечает компонент Простая настройка темы для каркасных экранов Помимо очевидных пропсов По умолчанию оно равно Больше информации о настройках компонента вы можете найти в документации. Чтобы подключить библиотеку Теперь можно добавить каркасные экраны для видео-данных. В папке SkeletonCard.js Здесь мы создаем неупорядоченный список и заполняем его элементами ( Чтобы вспомнить, как работает метод Array.fill, загляните в документацию на MDN. Нам сейчас важно только, что он создает массив с 9 элементами, которые мы можем проитерировать и с помощью метода Итак, для каждого фейкового видео у нас уже есть каркас, состоящий из нескольких блоков. Пропсы По умолчанию для компонента React Loading Skeleton подключена симпатичная пульсирующая анимация. При желании вы можете создать свою анимацию, мы же воспользуемся дефолтной. Осталось только подключить каркасный экран в приложение и отображать его, пока происходит подгрузка данных. Внесите изменения в код компонента App.js Весь исходный код вы можете найти в репозитории проекта. Наше приложение полностью готово. При открытии отображается красивый каркасный экран с пульсирующей анимацией, а в это время за кадром подгружаются данные. Как только мы их получим (на демо-примере стоит задержка в 5 секунд), то сразу выведем на страницу. Вот так выглядит результат: Каркасные или скелетные экраны значительно улучшают пользовательский опыт. Они избавляют посетителей сайта от растерянности и непонимания происходящего, а также создают впечатление, будто контент уже на подходе и вот-вот его покажут. Для создания подобного интерфейса можно использовать готовые библиотеки или реализовать его самостоятельно с помощью простых геометрических фигур и капли анимации.Что такое каркасные экраны?
Типы каркасных экранов
Готовые решения
React Placeholder
npm install --save react-placeholder
Достоинства
Недостатки
Пример
import { TextBlock, RectShape } from 'react-placeholder/lib/placeholders'; import ReactPlaceholder from 'react-placeholder'; const GhostPlaceholder = () => ( <div className='my-placeholder'> <RectShape color='gray' style={{width: 25, height: 70}} /> <TextBlock rows={6} color='blue'/> </div> ); <ReactPlaceholder ready={ready} customPlaceholder={<GhostPlaceholder />}> <MyComponent /> </ReactPlaceholder>
ReactPlaceholder
. Он просто оборачивает нужный компонент приложения в родительский тег. Визуальное представление мы должны собрать сами из доступных элементов и форм и передать в атрибут customPlaceholder
.GhostPlaceholder
. Внутри него используем элементы TextBlock
(блок текста) и RectShape
(прямоугольник), импортированные из react-placeholder/lib/placeholder
.ready
компонента ReactPlaceholder
.React Loading Skeleton
npm install --save react-loading-skeleton
Достоинства
Недостатки
Пример
import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; const SkeletonComponent = () => ( <SkeletonTheme color="#202020" highlightColor="#444"> <section> <Skeleton height={50} width={50} /> </section> </SkeletonTheme> );
Skeleton
и компонент темы SkeletonTheme
из модуля. SkeletonComponent
. SkeletonTheme
служит оберткой для всего элемента и принимает в пропсах цвета (color
, highlightColor
), которые следует использовать в оформлении плейсхолдеров и создании эффектов. Внутри него располагается компонент Skeleton
, размеры которого мы задаем с помощью пропсов width
и height
.Каркасные экраны в стиле YouTube
Установка React
npm install -g create-react-app
skeleton-screens
:
npx create-react-app skeleton-screens
cd skeleton-screens yarn start
Создание интерфейса
data.js
в папке src
и скопируйте туда следующий код:
const dummyData= [ { section: "Recommended", channel: "CNN", items: [ { id: "fDObf2AeAP4", image: "https://img.youtube.com/vi/fDObf2AeAP4/maxresdefault.jpg", title: "75 million Americans ordered to stay home", views: "1.9M views", published: "3 days agos" }, { id: "3AzIgAa0Cm8", image: "https://img.youtube.com/vi/3AzIgAa0Cm8/maxresdefault.jpg", title: "Gupta: The truth about using chloroquine to fight coronavirus pandemic", views: "128K views", published: "4 hours ago" }, { id: "92B37aXykYw", image: "https://img.youtube.com/vi/92B37aXykYw/maxresdefault.jpg", title: "Willie Jones STUNS Simon Cowell In Pitch Perfect Performance of 'Your Man'!", views: "2.47 million views", published: "1 month ago" }, { id: "J6rVaFzOEP8", image: "https://img.youtube.com/vi/J6rVaFzOEP8/maxresdefault.jpg", title: "Guide To Becoming A Self-Taught Software Developer", views: "104K views", published: "17 days ago" }, { id: "Wbk8ZrfU3EM", image: "https://img.youtube.com/vi/Wbk8ZrfU3EM/maxresdefault.jpg", title: "Tom Hanks and Rita Wilson test positive for coronavirus", views: "600k views", published: "1 week ago" }, { id: "ikHpFgKJax8", image: "https://img.youtube.com/vi/ikHpFgKJax8/maxresdefault.jpg", title: "Faces Of Africa- The Jerry Rawlings story", views: "2.3 million views", published: "2014" } ] }, { section: "Breaking News", channel: "CGTN America", items: [ { id: "tRLDPy1A8pI", image: "https://img.youtube.com/vi/tRLDPy1A8pI/maxresdefault.jpg", title: "Is Trump blaming China for COVID-19? You decide.", views: "876k views", published: "9 days ago" }, { id: "2ulH1R9hlG8", image: "https://img.youtube.com/vi/2ulH1R9hlG8/maxresdefault.jpg", title: "Journalist still goes to office during pandemic, see her daily routine", views: "873 views", published: "3 hours ago" }, { id: "_TkfQ9MaIgU", image: "https://img.youtube.com/vi/_TkfQ9MaIgU/maxresdefault.jpg", title: "How are small businesses going to survive the economic downturn of the COVID-19 era?", views: "283 views", published: "4 day ago" } ] } ]; export default dummyData;
Card
– превью видео. Содержит миниатюру видео, его название, количество просмотров, название канала и дату публикации. CardList
– группа превью.App
– корневой компонент приложения. Он получает объект dummyData
, показывает каркасный экран в течение двух секунд, имитируя загрузку данных, а затем отображает компонент CardList
. components
внутри src
– в ней будем размещать файлы компонентов.Card
Card.js
в папке components
:
import React from "react"; const Card = ({ item, channel }) => { return ( <li className="card"> <a href={`https://www.youtube.com/watch?v=${item.id}`} target="_blank" rel="noopener noreferrer" className="card-link" > <img src={item.image} alt={item.title} className="card-image" /> <img src={item.image} alt={item.title} className="channel-image" /> <h4 className="card-title">{item.title}</h4> <p className="card-channel"> <i>{channel}</i> </p> <div className="card-metrics"> {item.views} • {item.published} </div> </a> </li> ); }; export default Card;
CardList
components/CardList.js
:
import React from "react"; import Card from "./Card"; const CardList = ({ list }) => { return ( <ul className="list"> {list.items.map((item, index) => { return <Card key={index} item={item} channel={list.channel} />; })} </ul> ); }; export default CardList;
Card
.App
App.js
, который уже создан при инициализации проекта и обновите в нем код:
import React, { useState, useEffect } from "react"; import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { setLoading(true); const timer = setTimeout(() => { setVideos(dummyData); setLoading(false); }, 2000); return () => clearTimeout(timer); }, []); return ( <div className="App"> { videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> ); }; export default App;
useState
, чтобы хранить список видео, а также состояние загрузки.useEffect
используется для имитации загрузки данных с сервера с задержкой: старый-добрый setTimeout. Только вместо реальных данных мы сохраним заранее подготовленный массив из файла data.js
.videos.map
. Массив карточек передается в атрибуте list
компоненту CardList
, который отвечает за его визуализацию.loading,
скоро мы к ней вернемся.Стилизация
App.css
в папке src
и замените код на следующий:
.App { max-width: 960px; margin: 0 auto; font-size: 16px; } .list { display: flex; justify-content: space-between; flex-wrap: wrap; list-style: none; padding: 0; } .section-title { margin-top: 30px; } .card { width: calc(33% - 10px); margin: 20px 0; } .card-link { color: inherit; text-decoration: none; } .card-image { width: 100%; } .channel-image { border-radius: 100%; padding: 0, 10px, 0, 0; width: 40px; height: 40px; } .card-title { margin-top: 10px; margin-bottom: 0; } .card-channel { margin-top: 5px; margin-bottom: 5px; font-size: 14px; } /* Tablets */ @media (max-width: 1000px) { .App { max-width: 600px; } .card { width: calc(50% - 22px); } } /* Mobiles */ @media (max-width: 640px) { .App { max-width: 100%; padding: 0 15px; } .card { width: 100%; } }
Подключение React Loading Skeleton
Темы
SkeletonTheme
.
import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; <SkeletonTheme color="grey" highlightColor="#444"> <p> <Skeleton height={250} width={300} count={1} /> </p> </SkeletonTheme> <SkeletonTheme color="#990" highlightColor="#550"> <p> <Skeleton height={250} width={300} count={1} /> </p> </SkeletonTheme>
Продолжительность
height
, width
и color
, мы можем также указать свойство duration
.
<Skeleton duration={2} />
1.2
. Это значение определяет продолжительность одного цикла анимации экрана.Установка пакета
react-loading-skeleton
в проект, запустите следующую команду:
npm install react-loading-skeleton
Создание компонента
components
создайте новый компонент SkeletonCard.js
:
import React from "react"; import Skeleton from "react-loading-skeleton"; const SkeletonCard = () => { return ( <section> <h2 className="section-title"> <Skeleton height={30} width={300} /> </h2> <ul className="list"> {Array(9) .fill() .map((item, index) => ( <li className="card" key={index}> <Skeleton height={180} /> <h4 className="card-title"> <Skeleton circle={true} height={50} width={50} /> <Skeleton height={36} width={`80%`} /> </h4> <p className="card-channel"> <Skeleton width={`60%`} /> </p> <div className="card-metrics"> <Skeleton width={`90%`} /> </div> </li> ))} </ul> </section> ); }; export default SkeletonCard;
li
) с помощью метода Array.fill()
. Всего элементов 9: по количеству видеороликов в массиве dummyData
, которые будут выведены на страницу после загрузки.Array.map
превратить в массив элементов li
.height
и width
отвечают за размеры блока, а circle={true}
формирует круглый блок. App
:
import SkeletonCard from './components/SkeletonCard'; const App = () => { // ... return ( <div className="App"> {loading && <SkeletonCard />} {!loading && videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> ); };
Заключение
- 86 views
- 0 Comment