假设我们有一个UserCard组件:
function UserCard(props) {
const { avatarUrl, name, loading } = props
return (
<div className='UserCard'>
<Avatar image={avatarUrl} />
<Name name={name} />
</div>
)
}
现在我们想为它实现一个线框加载。
- 我们无法设计
Avatar
和Name
组件的线框副本-它们的作者可能会改变它们的大小和形状,我们不希望我们的骨骼失去同步 - 我们无法将加载程序插入
Avatar
和Name
组件中——我们无法控制它们或它们的css - 我们可以控制
Avatar
和Name
之间的空间,并从外部包裹它们 Avatar
和Name
可能有一些透明部分/边界半径,我们希望在它们的可见部分上绘制骨架动画
理想情况下,我们的Loader
的用法应该有点像这样:
function UserCard(props) {
const { avatarUrl, name, loading } = props
return (
<div className='UserCard'>
<Loader loading={loading}>
<Avatar image={avatarUrl} />
</Loader>
<Loader loading={loading}>
<Name name={loading ? 'some text to control the size of the skeleton' : name} />
</Loader>
</div>
)
}
实现这一目标的方法是什么?如果我们有类似的限制,但希望将骨架加载添加到UserCard
,该怎么办。从外部来看,我们知道这是一个包装其他div的单个div,我们希望显示一个骨架,而不是每个子div。
最终我想出的最好的办法是:
// Usage:
// @use 'styles/loading';
// .loading {
// @extend .wireframe;
// }
.wireframe {
background-color: var(--colors-borderColor2);
border-radius: 4px;
position: relative;
color: transparent; // hide text
overflow: hidden;
white-space: nowrap;
> * {
visibility: hidden; // hide children
}
&::after {
display: block;
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateX(-100%);
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.4),
transparent
);
animation: loading 1.5s infinite;
}
@keyframes loading {
100% {
transform: translateX(100%);
}
}
}
应该包装的组件由css选择器作为目标,并且可以在@extend
以下调整骨架形状和大小。
所以我们的UserCard
组件看起来是这样的:
import { stylesheet } from 'astroturf'
const styles = stylesheet`
@use 'styles/loading';
.UserCard {
//...
}
.UserCard:global(.loading) {
> * {
@extend .wireframe;
&:first-child {
border-radius: 50%; // make avatar round
}
}
}
`
function UserCard(props) {
const { avatarUrl, name, loading } = props
return (
<div className={cx('UserCard', { loading })>
<Avatar image={avatarUrl} />
<Name name={name} />
</div>
)
}
不理想,但对我有效。