在 React 中实现骨架屏幕

已发表: 2022-03-10
快速总结 ↬在本教程中,您将了解什么是骨架屏幕 UI 和某些类型的骨架屏幕库,以及它们的优缺点。 我们将使用 React Loading Skeleton 构建一个类似于 YouTube 的骨架屏幕 UI。 然后,您可以自己尝试使用您选择的骨架屏幕 React 包。

Spinners 和 loader 传统上是告诉用户内容需要一段时间才能加载的方式。 虽然这种方法很棒,但它在现代开发中很快就过时了。 骨架屏幕正在成为传统加载器的完美替代品,因为它们专注于进度而不是等待时间,从而减少了加载时间的挫败感。

在本文中,我们不会介绍 CSS React 或 JavaScript 语法的基础知识,因此您无需成为这两种语言的专家即可学习。

loader 和骨架屏 UI 的区别
loader和骨架屏UI的区别(大预览)

UI 和 UX 专家告诉我们,当用户等待内容加载到页面上时,我们应该让他们保持参与。

在内容加载之前使用微调器吸引用户的想法很棒; 但是,结果可能不太理想,因为大多数用户会厌倦地盯着像时钟一样的虚拟动画微调器。 Luke Wroblewski 对此进行了详细阐述。

骨架屏幕通过减少加载时间的挫败感来提供更好的用户体验。 通过关注进度而不是等待时间,它为用户创造了一种错觉,即信息将逐渐显示在屏幕上。 Bill Chung 在他的研究中证实了这一点。

什么是骨架屏?

骨架屏幕是不包含实际内容的 UI 版本; 相反,它通过在加载和变为可用时(即在网络延迟允许时)以类似于实际内容的形状显示其元素来模仿页面的布局。

骨架屏幕本质上是页面的线框,带有用于文本和图像的占位符框。

跳跃后更多! 继续往下看↓

骨架屏幕有什么独特之处?

骨架 UI 类似于页面的实际 UI,因此用户甚至可以在内容显示之前了解 Web 或移动应用程序的加载速度。 以下是您可能要考虑在下一个项目中使用骨架屏幕的几个原因:

  • 使用骨架屏幕更容易模仿页面的布局,
  • 内容逐渐加载(不是一次全部加载)。

骨架屏幕也称为:

  • 鬼元素,
  • 内容占位符,
  • 内容加载器。

Blockchain.com、YouTube、Facebook、Medium 和其他大型科技公司在加载内容时显示骨架屏幕以提升用户体验。

区块链网

Blockchain.com 骨架屏 UI
Blockchain.com 的部分加载状态(注意在图形分析中如何使用骨架)(大预览)

中等的

中型骨架屏UI
Medium 的骨架 UI(大预览)

领英

LinkedIn 骨架屏幕 UI
2018年LinkedIn首页加载状态(大图预览)

骨架屏幕的类型

有不同种类的骨架屏。 主要的是文本占位符和图像(或颜色)占位符。

大多数开发人员更喜欢在他们的页面上使用文本占位符作为骨架 UI,因为它们易于构建,并且开发人员不需要任何有关实际内容实质的细节; 相反,骨架模仿 UI。

颜色占位符更难构建,因为它们需要有关内容的详细信息。

一些流行的包使在 Web 应用程序中实现骨架屏幕更容易。 让我们仔细看看它们:

  • 反应占位符
  • 反应加载骨架

在考虑将哪个包用于我们的应用程序之前,我们将查看每个包的优缺点。

反应占位符

优点

  • 占位符组件用于创建自定义骨架 UI。
  • 支持脉冲动画(即元素上的运动效果)。
  • 它带有一个基于组件的 API。

缺点

  • 骨架组件是单独维护的,因此更新组件的样式可能也需要更新骨架组件。
  • 学习曲线不是线性的,因为有多个组件可以满足不同的需求。

以下是使用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>

react-placeholder/lib/placeholder导入TextBlockRectShape ,从react-placeholder -placeholder 导入ReactPlaceholder ,我们创建了一个名为GhostPlaceholder的功能组件。 GhostPlaceholder有一个 div,在 div 中我们使用了 RectShape 组件,它描述了矩形的尺寸,传递任何颜色的值,并定义了矩形的样式。

接下来,我们使用TextBlock组件来设置行和颜色的值。 TextBlock组件定义文本的行数和颜色。

我们将MyComponent作为ReactPlaceholder组件的子组件传递,该组件接收readyGhostPlaceholder组件作为其readycustomPlaceholder道具的值。

MyComponent将在显示骨架屏幕 UI 时加载。

要了解更多信息,请查看文档。

反应加载骨架

优点

  • 它是基于 API 的,它有一个组件,其中包含用于所有自定义的 props。
  • 它可以作为单独的骨架组件使用,也可以直接在任何组件内部使用,因此非常灵活。
  • 它支持主题和脉冲动画。

缺点

  • 对于简单的骨架 UI,实现起来很容易,但对于更复杂的骨架,实现起来就很复杂。
  • 当 UI 和样式发生变化时,拥有一个单独的骨架组件将使其更难维护。

以下是 React 加载骨架的示例:

 import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; const SkeletonComponent = () => ( <SkeletonTheme color="#202020" highlightColor="#444"> <section> <Skeleton height={50} width={50} /> </section> </SkeletonTheme> );

我们从react-loading-skeleton库中导入了SkeletonSkeletonTheme ,然后创建了一个渲染SkeletonTheme组件的功能组件,其中colorhightlightColor作为属性。

SkeletonTheme组件用于主题化(例如,向骨架 UI 添加颜色效果)。

最后,在该部分中,我们定义了Skeleton组件,并传入了 height 和 width 属性及其适当的值。

构建类似 YouTube 的骨架屏幕 UI

让我们使用 React Loading Skeleton 创建一个类似于 YouTube 的骨架屏幕,以展示骨架 UI 的工作原理。

设置反应

设置 React 最简单的方法是使用 Create React App,这是“一种官方支持的创建单页 React 应用程序的方法。 它提供了无需配置的现代构建设置。”

我们将使用它来引导我们将要构建的应用程序。 从您的终端,运行以下命令:

 npx create-react-app skeleton-screens && cd skeleton-screens

安装完成后,通过运行npm start React 服务器:

React 应用程序 - 脚手架 React 应用程序
React 欢迎页面(大预览)

创建没有骨架屏幕的 YouTube 用户界面

首先,让我们输入 YouTube 虚拟数据。 通常会使用真实端点而不是虚拟数据,但在本教程中,我们将使用虚拟数据。

src/文件夹中创建一个文件,并将其命名为data.js ,向其中添加以下代码。

 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;

为了复制 YouTube 的格式,我们创建了具有对象数组的虚拟数据,这些对象具有 ID、图像、标题、观看次数和发布日期等属性。

接下来,让我们创建我们的 YouTube 用户界面。 我们将包含三个组件:

Card 保存视频缩略图、标题、观看次数、发布日期和频道的详细信息。
CardList 返回连续的所有卡片。
App 挂载我们的dummyData对象,加载骨架 UI 两秒钟,然后返回CardList组件。

在您的src文件夹中,创建一个文件夹并将其命名为components 。 在components文件夹中,创建一个Card.js文件,向其中添加以下代码:

 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;

我们创建了一个Card组件。 在其中,我们从react导入了React ,我们解构了itemchannel props,以便它们可以在Card组件中使用。 每个显示一个视频的Card项目组件都将显示缩略图、观看次数、发布日期和标题。

卡片列表组件

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组件。 卡片接受我们通过list.items映射获得的itemchannel道具。 然后我们将此组件导出为CardList ,因为我们将在App组件中使用它。

注意在这个组件中映射的 items 数组是我们dummyData中的对象数组。

应用组件

src/目录中的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); }, 5000); 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;

在这个组件中,我们将useStateuseEffect钩子与React以及我们创建的其他文件一起导入,这些文件将在App组件中使用。

因为我们的数据是虚拟数据,所以我们需要像 API 数据一样模拟它,方法是在两秒超时后加载内容,使用 JavaScript 的setTimeout方法。

接下来,在App组件中,我们创建一个视频状态,并使用useState将状态设置为一个空数组。

要加载我们的虚拟数据,我们将使用useEffect挂钩。 在我们的钩子中,我们创建了一个变量 timer 来保存setTimeout ()函数。 在函数内部,我们将视频状态设置为dummyData对象,并确保数据在两秒后加载,最后,我们在卸载时取消计时器。

最后,我们映射视频状态并返回包含list-sectionCardList组件及其列表道具的 section 元素。

添加 CSS

到目前为止,我们已经使用了很多没有实际 CSS 的类。 在src文件夹中,删除App.css中的所有内容,并将其替换为以下代码;

 .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%; } }

让我们看看我们的 YouTube 用户界面在没有骨架屏幕的情况下是什么样子的。 可以看到,页面加载的时候,出现白屏两秒,然后数据加载很快。

无骨架屏的类似 YouTube 的 UI
无骨架屏的类似 YouTube 的 UI(大预览)

使用 React 加载骨架

与其他需要精心制作骨架屏幕以匹配内容的字体大小、行高和边距的库不同, Skeleton组件旨在直接在您的组件中使用,以代替正在加载的内容。

让我们回顾一下为什么我们选择 React Loading Skeleton 而不是其他的一些原因。

主题化

React Loading Skeleton 支持主题化。 因此,您可以使用SkeletonTheme轻松更改所有骨架组件的颜色,并将值传递给 color props

下面是一个例子,展示了它是如何工作的:

 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> 
主题效果在行动
实际中的主题效果(大预览)

期间

除了heightwidthcolor属性,我们还可以指定一个duration属性。

 <Skeleton duration={2} />

持续时间默认为1.2 。 这决定了完成一个骨架动画周期需要多长时间。

要了解更多信息,请查看文档。

实现骨架屏幕 UI

现在,我们将安装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;

我们创建了一个无序列表。 在其中,我们使用了Array.fill()方法。 因为我们有九个虚拟数据项,所以我们使用Array.fill()方法循环遍历我们的items对象的长度并用没有索引值填充它,因此使我们的数组为。 请参阅 Array.fill 文档以了解其工作原理。

接下来,我们通过我们的空数组进行映射以返回一个包含骨架属性的列表,并且我们指定了每个骨架属性的值。

这里, height表示骨架矩形的长度, width表示宽度,而circle表示骨架 UI 的圆形部分。

React Loading Skeleton 带有默认的 Pulse 动画,这让它很方便。 您可以创建适合您项目的 Pulse 动画,但如果您问我,我会坚持使用默认值。

最后,完整的源代码可用。

我们现在有一个功能齐全的骨架屏幕 UI。 我们的示例在显示内容之前显示骨架五秒钟。

让我们看看到目前为止的结果:

类似 YouTube 的 UI 加上骨架屏 UI
我们类似于 YouTube 的骨架 UI(大预览)

结论

骨架屏幕通过避免面对完全空白屏幕的挫败感,并让用户在加载之前对内容的外观有一个印象,极大地改善了用户体验。

如果您对我们看过的任何软件包都不满意,您可以通过制作模仿页面布局的矩形和圆形来创建自己的骨架 UI。

请在下面的评论部分分享您的反馈和经验。 我很想看看你想出了什么!

本文的支持 repo 可在 Github 上找到。

参考

  • “关于骨架屏幕你需要知道的一切”,Bill Chung,UX Collective
  • “使用 React 加载页面的骨架”,Anthony Panagi,Octopus Wealth
  • “带有 React 和 React Native 的骨架屏幕”,Chris Dolphin,Alligator.io
  • “在 React 中实现骨架加载”,Adrian Bece,DEV