实现骨架屏加载效果(含Vanilla React Vue3版本)

3,541 阅读3分钟

日常开发中,当遇到需要显示的数据尚未加载出之前我们可以选择用一个loading gif动图或者骨架屏来显示,当加载成功之后再隐藏掉

例如掘金的骨架屏 Img

加载方式有很多,今天准备手写一个骨架loading动画效果以备开发不时之需

实现的效果如下

Img 在chrome中勾选停用缓存和预设网速(低速3g)效果更加明显

数据来源

可以从这个url中获取模拟开发中需要展示的json数据jsonplaceholder.typicode.com/posts Img

从这个网站中得到获取开发中需要展示的图片 头像等 unsplash.com/documentati…

常用的骨架屏大概就是这俩种:一个是渐进滚动,一个是呼吸效果,接下来就开始coding!

结构

<body>
    <div class="grid">
        <div class="card">
            <div class="header">
                <img class="skeleton-img" src="https://source.unsplash.com/100x100/?animal" />
                <div data-title class="title">
                    <div class="skeleton-text"></div>
                    <div class="skeleton-text"></div>
                </div>
            </div>
            <div data-body>
                <div class="skeleton-text"></div>
                <div class="skeleton-text"></div>
                <div class="skeleton-text"></div>
                <div class="skeleton-text"></div>
            </div>
        </div>
    </div>
</body>

基本样式

  • 因为是多栏多列 所以整体grid布局
  • grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));其中 auto-fit表示列的重复次数会自动适应可用空间;minmax(300px, 1fr)表示每列的最小宽度为300px像素 如果大于300px会平均分配剩余空间
  • img object-fit: cover; 表示图像将被调整以填满其容器,并保持其宽高比例
*,
*::before,
*::after {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}

.grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	padding: 1rem;
	gap: 1rem;
}
.title {
	font-weight: bold;
	font-size: 1.25rem;
	width: 100%;
	height: 100%;
	word-wrap: none;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	flex-grow: 1;
}

.header {
	display: flex;
	margin-bottom: 1rem;
	align-items: center;
}
.card {
	background-color: white;
	border-radius: 0.5rem;
	box-shadow: rgba(0, 0, 0, 0.1) 0 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
	padding: 1rem;
}
img {
	width: 50px;
	height: 50px;
	border-radius: 50%;
	flex-shrink: 0;
	object-fit: cover;
	margin-right: 1rem;
}

骨架屏加载动画实现

呼吸效果

  • 以免背景颜色太鲜艳,设置一个透明度适配更好
  • 动画效果无限循环交替,关键帧调节背景色明暗,也就是hsl第三个参数 hsl(色相,饱和度,亮度)
.skeleton-img {
	opacity: 0.7;
	animation: skeleton-img-loading 1s linear infinite alternate;
}
@keyframes skeleton-img-loading {
	0% {
		background-color: hsl(200, 20%, 70%);
	}
	100% {
		background-color: hsl(200, 20%, 95%);
	}
}

渐进滚动效果

参照这篇文章的实现

动态效果的实现是通过拉伸背景图片,动态设置背景定位百分比,改变背景定位,从而计算得到图片相对容器的不同偏移值

background-image: linear-gradient背景色线性渐变实现 90deg:从左到右,hsl亮度转变:暗->明->暗

background-position: 100% 50%;第一个参数表示水平位置相对容器的偏移,第二个是垂直位置相对容器的偏移。使用百分比的计算实际定位值公式(x offset value) = (container width - image width) * (position x%),即实际的偏移值等于容器和图片的宽度之差乘上设置的百分比定位值

background-size: 400% 100%;设置宽度为400%的妙用是与容器形成宽度差 配合关键帧使background-position在x坐标的值从100%到0%就可以实现动画效果

.skeleton-text {
	width: 100%;
	height: 0.5rem;
    opacity: 0.7;
	background-image: linear-gradient(
		90deg,
		hsl(200, 20%, 70%) 0%,
		hsl(200, 20%, 90%) 50%,
		hsl(200, 20%, 70%) 100%
	);
	margin-bottom: 0.25rem;
	border-radius: 0.125rem;
	background-size: 400% 100%;
	background-position: 100% 50%;
	animation: skeleton-text-loading 0.6s ease infinite;
}

@keyframes skeleton-text-loading {
	0% {
		background-position: 100% 50%;
	}
	100% {
		background-position: 0 50%;
	}
}

展示数据

最后请求json数据,操作dom元素然后填充数据就可以咯

const grid = document.querySelector('.grid')
const card = document.querySelector('.card')
for (let i = 0; i < 10; i++) {
    grid.append(card.cloneNode(true))
}

fetch('https://jsonplaceholder.typicode.com/posts')
    .then(res => res.json())
    .then(posts => {
        grid.innerHTML = ''
        posts.forEach(post => {
            const div = card.cloneNode(true)
            div.querySelector('[data-title]').textContent = post.title
            div.querySelector('[data-body]').textContent = post.body
            grid.append(div)
        })
    })

代码版本

vanilla

React

Vue3

参考