实现带主题的 react 通用骨架屏

marvin

假定我们的页面左侧由一个文章列表, 右侧有一个用户信息的侧边栏, 现在需要为这些生成一些骨架屏用来代替加载状态组件, 我们可以通过定义一个自定义的 Skeleton 组件来实现, 通过给 Skeleton 组件传入不同的类型来显示不同的元素的骨架样式
基础的骨架屏组件
首先需要定义一个 Skeleton 组件, 通过 type 来定义 class, 不同的 type 定义不同的样式:
// SkeletonElement.js
import React from 'react'
const SkeletonElement = ({ type }) => {
const classes = `skeleton ${type}`;
return (
<div className={classes}></div>
)
}
export default SkeletonElement;
为 SkeletonElement 组件定义样式
不同的元素例如文字, 标题, 头像等它们的宽高和形状都不一样, 需要单独定义:
.skeleton {
background-color: #ddd;
margin: 10px 0;
border-radius: 4px;
&.text {
width: 100%;
height: 12px;
}
&.title {
width: 50%;
height: 20px;
margin-bottom: 15px;
}
&.avatar {
width: 100px;
height: 100px;
border-radius: 50%;
}
&.thumbnail {
width: 100px;
height: 100px;
}
}
有了基础的组件, 接下来可以定义文章列表的部分了
构建文章骨架屏
文章由标题和多行文本组成, 这里用三个 text 类型表示, 为了定义文章列表的样式, 我们在 SkeletonElement 的基础上再定义一个 SkeletonArticle 组件用来包装样式:
import SkeletonElement from './SkeletonElement';
const SkeletonArticle = () => {
return (
<div className="skeleton-wrapper">
<div className="skeleton-article">
<SkeletonElement type="title" />
<SkeletonElement type="text" />
<SkeletonElement type="text" />
<SkeletonElement type="text" />
</div>
</div>
}
组件定义好了, 接下来就需要定义样式
文章骨架屏的样式
这里为文章列表单独定义样式, 其中 position 和 overflow 是为了 shimmer 动画设置的, 可以让动画定位和不超出宽度:
.skeleton-wrapper {
margin: 20px auto;
padding: 10px 15px;
border-radius: 4px;
position: relative;
overflow: hidden;
}
文章部分完成了, 接下来看用户信息部分
构建用户信息骨架屏
用户信息由用户头像, 标题, 多行描述文本组成, 这里用两行 text 类型表示:
import React from 'react'
const SkeletonProfile = () => {
return (
<div className="skeleton-wrapper">
<div className="skeleton-profile">
<div>
<SkeletonElement type="avatar" />
</div>
<div>
<SkeletonElement type="title" />
<SkeletonElement type="text" />
<SkeletonElement type="text" />
</div>
</div>
</div>
)
}
组件完成,我们还需要将头像和文本部分对齐
用户信息骨架屏的样式
这里将整个容器设置为 grid, 居中对齐, 头像部分占一份, 文本部分占两份
.skeleton-profile {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 30px;
align-items: center;
}
为文章骨架屏添加主题
为了支持主题颜色修改, 这里需要修改 SkeletonArticle 组件, 传入 theme 参数
const SkeletonArticle = ({ theme }) => {
return (
<div className={`skeleton-wrapper ${themeClass}`}>
{/* ... */}
</div>
)
}
对于不同的 theme 参数分别定义背景色
为文章骨架屏添加主题颜色
这里支持白色和黑色主题, 其中黑色主题的 SkeletonElement 元素颜色更深:
.skeleton-wrapper.light {
background-color: #f2f2f2;
}
.skeleton-wrapper.dark {
background-color: #444;
}
.skeleton-wrapper.dark .skeleton {
background-color: #777;
}
为骨架屏添加动画
我们的骨架屏还缺少动画, 这里修改 SkeletonArticle 组件, 新增 Shimmer 组件, 作为动画层覆盖在 SkeletonElement 组件之上:
import Shimmer from './Shimmer'
const SkeletonArticle = ({ theme }) => {
return (
<div className={`skeleton-wrapper ${themeClass}`}>
{/* ... */}
<Shimmer />
</div>
)
}
Shimmer 定义如下:
import React from 'react'
const Shimmer = () => {
return (
<div className="shimmer-wrapper">
<div className="shimmer"></div>
</div>
)
}
export default Shimmer;
定义 Shimmer 的样式
shimmer 组件由 shimmer-wrapper 和 shimmer 类组成, 下面分别设置样式:
.shimmer-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
animation: loading 2.5s infinite;
}
.shimmer {
width: 50%;
height: 100%;
background-color: rgba(255, 255, 255, .8);
transform: skewX(-20deg);
box-shadow: 0 0 30px 30px rgba(255, 255, 255, .05);
}
.dark .shimmer {
background-color: rgba(255, 255, 255, .05);
}
@keyframes loading {
0% {
transform: translateX(-150%);
}
50% {
transform: translateX(-60%);
}
100% {
transform: translateX(150);
}
}
这里让 shimmer-wrapper 绝对定位, 并定义了从左到右的平移动画 loading 应用到 wrapper 上, 内部 shimmer 通过 transform 将 shimmer 斜着放置, 最后添加了一个阴影使背景更加明显.
写在最后
以上就实现了一个通用骨架屏, 但是如果有更多的页面和组件, 还是需要在此基础上封装不同的 Skeleton.