通过显示框架 UI 来改善 React 应用中 UX
作者:Paramananham Harrison✏️
介绍
骨架屏是一种不包含实际内容的 UI;相反,它以类似于实际内容的形状显示页面的加载元素。
骨架屏幕向用户显示内容正在加载,并提供内容完全加载后的外观的模糊预览。
前端开发人员使用骨架 UI 的原因多种多样。
其中最主要的是 UI 能够在视觉上简化用户体验、模拟内容加载速度以及逐步加载内容,而无需一次获取页面上的所有内容。
Slack、Youtube、Facebook、Pinterest 和其他大型科技公司在加载内容时显示骨架屏幕以提升用户体验。


除了骨架屏幕之外,这些用户界面通常被称为内容占位符、内容加载器和幽灵元素。
骨架屏幕如何改善用户体验
Skeleton UI 与真实 UI 非常相似,因此用户在内容显示之前就能了解网站的加载速度。让我们通过两个屏幕的对比来看一下实际效果:


两个屏幕都没有加载实际内容,但空白页面对用户来说似乎比较慢,而骨架屏幕看起来更丰富,速度更快,响应也更灵敏。
尽管两个屏幕上的实际内容加载速度相同,但骨架屏幕提供了更出色的用户体验。
不同的骨架UI
骨架 UI 有几种不同的类型。主要包括内容占位符和图像(或颜色)占位符。
Medium、Slack 和 Youtube 等公司在其主页的骨架 UI 中使用内容占位符。
它们很容易构建,因为它们不需要任何有关实际内容数据的详细信息,而只是模仿 UI。
与此同时,Pinterest 和 Unsplash 这两个以图片为主的网站则使用了颜色占位符。颜色占位符的构建难度更大,因为它们需要实际内容数据的详细信息。
工作原理
首先,加载骨架而不是图像(通常带有灰色或灰白色背景)。
一旦获取数据,就从图像元数据中加载图像的实际颜色。
该元数据是通过后端算法从图像上传时获取的,并在图像之上进行处理。
最后,延迟加载图像以允许用户使用交叉观察器 API 实际查看内容。
演示
在我们的教程中,我们将通过创建 YouTube 主页的模拟来探索 React 中的骨架 UI。
在开始之前,让我们列出 React 中已经提供的用于骨架 UI 开发的最流行的软件包:
这些软件包维护得相当好,但也存在一些缺点。在决定在我们的应用中使用哪个之前,我们先来分析一下它们的优缺点。
React 内容加载器
优点
- 基于 SVG 的 API;您可以使用任何 SVG 形状来创建骨架元素
- 轻松创建动画占位符,从左到右发光(脉冲动画)
- 有一些预先设置的内容加载器(例如 Facebook、Instagram 等)
- 由于 SVG 支持多种形状,因此可以用于任何复杂的骨架 UI
缺点
- 您需要为所有组件分别创建自定义骨架组件
- SVG 与 CSS 元素不同,因此创建具有自定义对齐方式的自定义元素需要陡峭的学习曲线
- 由于 SVG 依赖性,浏览器支持可能不一致,因此骨架在不同的浏览器上的外观和感觉可能会有所不同
以下是使用骨架组件的示例react-content-loader
:
import ContentLoader from "react-content-loader";
// API support all SVG shapes - rect is a SVG shape for rectangle
const SkeletonComponent = () => (
<ContentLoader>
<rect x="0" y="0" rx="5" ry="5" width="70" height="70" />
<rect x="80" y="17" rx="4" ry="4" width="300" height="13" />
<rect x="80" y="40" rx="3" ry="3" width="250" height="10" />
</ContentLoader>
)
反应占位符
优点
- 基于组件的 API
- 使用占位符组件轻松创建自定义骨架 UI
- 支持脉冲动画,可以通过道具控制
缺点
- 与 React 内容加载器类似,我们需要单独维护一个骨架组件,因此更新组件的样式也可能需要更新骨架组件
- 学习曲线不是很线性,因为有多个组件可以满足不同的需求
以下是使用 的骨架组件的示例react-placeholder
:
import { TextBlock, RectShape } from 'react-placeholder/lib/placeholders';
import ReactPlaceholder from 'react-placeholder';
//
const MyCustomPlaceholder = () => (
<div className='my-custom-placeholder'>
<RectShape color='gray' style={{width: 30, height: 80}} />
<TextBlock rows={7} color='yellow'/>
</div>
);
// This is how the skeleton component is used
<ReactPlaceholder ready={ready} customPlaceholder={<MyCustomPlaceholder />}>
<MyComponent />
</ReactPlaceholder>
React 加载骨架
优点
- 非常简单的 API — 它只有一个包含所有自定义属性的组件
- 相当容易学习
- 可以作为单独的骨架组件使用,也可以直接在任何组件内部使用,因此可以灵活地按照我们想要的方式使用
- 支持动画和主题
缺点
- 对于简单的骨架 UI 来说非常好,但对于复杂的骨架来说很难
以下是 React 加载骨架的示例:
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
const SkeletonCompoent = () => (
<SkeletonTheme color="#202020" highlightColor="#444">
<section>
<Skeleton count={3} />
<Skeleton width={100} />
<Skeleton circle={true} height={50} width={50} />
</section>
</SkeletonTheme>
);
对于完整的演示,我们将使用react-loading-skeleton
。
也就是说,这三个库足以满足简单的用例。您可以随意浏览文档,并选择最适合您应用程序使用的那个。
使用 React 的 Skeleton UI 示例
我们将构建一个类似 YouTube 的 UI,并展示骨架 UI 的工作原理。
首先,让我们创建 YouTube UI:
import React from "react";
// Youtube fake data
import youtubeData from "./data";
// Styles for the layout
import "./App.css";
// Each Card item component which display one video - shows thumbnail, title and other details of a video
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" />
<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>
);
};
// Card list component
const CardList = ({ list }) => {
return (
<ul className="list">
{list.items.map((item, index) => {
return <Card key={index} item={item} channel={list.channel} />;
})}
</ul>
);
};
// App component - each section have multiple videos
const App = () => {
return (
<div className="App">
{youtubeData.map((list, index) => {
return (
<section key={index}>
<h2 className="section-title">{list.section}</h2>
<CardList list={list} />
<hr />
</section>
);
})}
</div>
);
}
export default App;
接下来,让我们输入虚假的 YouTube 数据:
const youtubeData = [
{
section: "JavaScript Tutorials by freeCodeCamp",
channel: "freeCodeCamp.org",
items: [
{
id: "PkZNo7MFNFg",
image: "https://img.youtube.com/vi/PkZNo7MFNFg/maxresdefault.jpg",
title: "Learn JavaScript - Full Course for Beginners",
views: "1.9M views",
published: "9 months ago"
},
{
id: "jaVNP3nIAv0",
image: "https://img.youtube.com/vi/jaVNP3nIAv0/maxresdefault.jpg",
title: "JavaScript, HTML, CSS - Rock Paper Scissors Game",
views: "216K views",
published: "1 year ago"
}
]
},
{
section: "Small steps on React",
channel: "Learn with Param",
items: [
{
id: "ylbVzIBhDIM",
image: "https://img.youtube.com/vi/ylbVzIBhDIM/maxresdefault.jpg",
title: "useState example by building a text-size changer",
views: "148 views",
published: "3 days ago"
}
]
}
];
export default youtubeData
在加载实际数据之前,让我们先展示一下框架 UI。由于我们的数据是伪造的,我们需要像 API 数据一样模拟它,在两秒超时后加载:
import React, { useState, useEffect } from "react";
const App = () => {
const [videos, setVideos] = useState([]);
// Load this effect on mount
useEffect(() => {
const timer = setTimeout(() => {
setVideos(youtubeData);
}, 2000);
// Cancel the timer while unmounting
return () => clearTimeout(timer);
}, []);
return (
<div className="App">
{videos.map((list, index) => {
...
})}
</div>
);
};
您会看到白屏三秒钟,然后数据突然加载。
现在,我们将安装react-loading-skeleton
:
yarn add react-loading-skeleton
让我们为视频数据创建一个骨架组件:
import Skeleton from "react-loading-skeleton";
/*
Separate Skeleton component
- It is created with the same shape as Card component
- Pros: Component will be isolated from the skeletons so the component won't become complex or heavy
- Cons: Maintaining separate skeleton component will make it harder to maintain when UI changes and style gets changed
*/
const CardSkeleton = () => {
return (
<section>
<h2 className="section-title">
<Skeleton height={28} 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 height={36} width={`80%`} />
</h4>
<p className="card-channel">
<Skeleton width={`60%`} />
</p>
<div className="card-metrics">
<Skeleton width={`90%`} />
</div>
</li>
))}
</ul>
</section>
);
};
您还可以通过将骨架直接嵌入到组件中来创建骨架组件,如下所示:
import Skeleton from "react-loading-skeleton";
/*
Cards component with embedded skeleton UI
- Pros: This is much easier to maintain for UI and styles changes
- Cons: UI will become complex and heavy with lot of unnecessary elements in it
*/
const Card = ({ item, channel }) => {
return (
<li className="card">
<a
href={item.id ? `https://www.youtube.com/watch?v=${item.id}` : `javascript:void(0)`}
target="_blank"
rel="noopener noreferrer"
className="card-link"
>
{
item.image ?
<img src={item.image} alt={item.title} className="card-image" />
:
<Skeleton height={180} />
}
<h4 className="card-title">
{
item.title ? item.title :
<Skeleton height={36} width={`80%`} />
}
</h4>
<p className="card-channel">
{ channel ? <i>{channel}</i> : <Skeleton width={`60%`} /> }
</p>
<div className="card-metrics">
{
item.id ?
<>{item.views} • {item.published}</>
:
<Skeleton width={`90%`} />
</div>
</a>
</li>
);
};
我在示例中使用了独立的骨架组件,但您可以随意使用最适合您需求的样式组件。这完全取决于个人喜好和组件的复杂程度。
最后,这是CardSkeleton
实际数据加载之前的组件:
const App = () => {
const [videos, setVideos] = useState([]);
// Manage loading state - default value false
const [loading, setLoading] = useState(false);
useEffect(() => {
// set the loading state to true for 2 seconds
setLoading(true);
const timer = setTimeout(() => {
setVideos(youtubeData);
// loading state to false once videos state is set
setLoading(false);
}, 2000);
return () => clearTimeout(timer);
}, []);
// Show the CardSkeleton when loading state is true
return (
<div className="App">
{loading && <CardSkeleton />}
{!loading &&
videos.map((list, index) => {
return (
<section key={index}>
<h2 className="section-title">{list.section}</h2>
<CardList list={list} />
<hr />
</section>
);
})}
</div>
);
};
现在,我们已经有了一个功能齐全的骨架 UI 示例。我们的示例会先加载骨架 2 秒,然后再显示数据。点击此处查看实际效果。
此示例的代码库可在Github中找到。我已编写了分支,以便您可以运行所有中间阶段并查看差异。
结论
骨架屏幕可显著改善用户体验,减轻用户因完全空白屏幕而产生的挫败感,并让用户了解内容在加载之前的样子。
在 React 应用程序中使用骨架 UI 很容易。
如果您不想使用现有的包,您也可以通过创建矩形和圆形元素来模拟骨架的 div 元素,从而轻松创建自己的骨架 UI。
在评论部分分享您使用骨架 UI 的经验。
编者注:觉得这篇文章有什么问题?您可以在这里找到正确版本。
插件:LogRocket,一个用于 Web 应用的 DVR
LogRocket是一款前端日志工具,可让您重播问题,就像它们发生在您自己的浏览器中一样。无需猜测错误发生的原因,也无需要求用户提供屏幕截图和日志转储,LogRocket 让您重播会话,快速了解问题所在。它可与任何应用程序完美兼容,不受框架限制,并且提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的额外上下文。
除了记录 Redux 操作和状态外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头 + 正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,以记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能重现像素完美的视频。
免费试用。
通过显示骨架 UI 来改善 React 应用程序中的 UX一文首先出现在LogRocket 博客上。
文章来源:https://dev.to/bnevilleoneill/improve-ux-in-react-apps-by-showing-sculpture-ui-5a3i