携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
今天我们来用原生js实现一个骨架屏的效果,效果如下:
首先思考如何实现
思考实现方式
骨架屏的原理是在数据没加载出来的时候,使用滚动的背景颜色去替代,等到加载完毕后则显示对应内容
那么我们的核心就是实现一个.skeleton
的样式,当这个样式出现的时候,就通过animation
去开启一个背景色无限滚动的动画,数据加载完毕后则将这个类名去除即可
思路还是比较简单的,我们先搭建一个整体结构,将数据都写死看看效果单的,我们先搭建一个整体结构,将数据都写死看看效果e单的,我们先搭建一个整体结构,将数据都写死看看效果单的,我们先搭建一个整体结构,将数据都写死看看效果e先
静态结构
<div class="card">
<!-- 存放图片 -->
<header>
<img
src="https://unsplash.com/photos/EaB4Ml7C7fE/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGNvZGV8ZW58MHx8fHwxNjYwMzQwOTQ4&force=true"
alt=""
/>
</header>
<main>
<h3>I love coding!</h3>
<p>Talk is cheap. Show me the code.</p>
<section class="author">
<div class="author-avatar">
<img src="https://pixabay.com/get/g9ac59ef391ebfbe7b3303ed0278986a31783bedd580d5e097980ed990d9bf646b5dc44932b18fd633df1813686e4f809.png?attachment=" alt="" />
</div>
<div class="author-info">
<!-- 名字 -->
<strong>Plasticine</strong>
<!-- 日期 -->
<small>Aug 13, 2022</small>
</div>
</section>
</main>
</div>
复制代码
再写一些基础样式
img {
height: 100%;
width: 100%;
object-fit: cover;
}
.card {
width: 350px;
border-radius: 10px;
background-color: white;
overflow: hidden;
box-shadow: 5px 5px 10px 10px rgba(255, 255, 255, 0.2);
}
.card header {
height: 200px;
}
.card main {
padding: 30px;
}
.card main h3 {
margin: 0;
}
.card main p {
color: gray;
}
.author {
display: flex;
align-items: center;
gap: 10px;
}
.author .author-avatar {
height: 40px;
width: 40px;
border-radius: 50%;
overflow: hidden;
}
.author .author-info {
display: flex;
flex-direction: column;
gap: 5px;
width: 100px;
}
.author .author-info small {
color: gray;
}
复制代码
现在的效果如下:
骨架屏特效
现在就可以尝试添加骨架屏特效了,骨架屏特效本身就只是一个背景色向右流动的效果,所以我们需要一个渐变色背景,然后设置一个动画让背景色的background-position
不断向右移动,就可以实现骨架屏的效果
对应的css
代码如下:
.skeleton {
background: linear-gradient(
to right,
#f6f7f8 0%,
#edeef1 10%,
#f6f7f8 20%,
#f6f7f8 100%
);
background-size: 200% 100%;
animation: flow 1s linear infinite;
}
@keyframes flow {
0% {
background-position: 50% 0;
}
100% {
background-position: -150% 0;
}
}
复制代码
那么有了这个骨架屏特效的代码,我们还需要看看其效果是否真的和我们预期中一样呢?
可以先把html
中的内容注释掉,只保留框架部分,模拟一下数据还没加载时候的效果,然后再在需要应用骨架屏特效的地方加上.skeleton
类名
我们要应用骨架屏特效的地方有背景图片、卡片标题、卡片内容、作者头像、作者姓名、留言日期,所以在这些地方加上.skeleton
类名
<div class="card">
<!-- 存放图片 -->
<header class="skeleton">
<!-- <img
src="https://unsplash.com/photos/EaB4Ml7C7fE/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGNvZGV8ZW58MHx8fHwxNjYwMzQwOTQ4&force=true"
alt=""
/> -->
</header>
<main>
<h3 class="skeleton">
<!-- I love coding! -->
</h3>
<p class="skeleton">
<!-- Talk is cheap. Show me the code. -->
</p>
<section class="author">
<div class="author-avatar skeleton">
<!-- <img
src="https://pixabay.com/get/g9ac59ef391ebfbe7b3303ed0278986a31783bedd580d5e097980ed990d9bf646b5dc44932b18fd633df1813686e4f809.png?attachment="
alt=""
/> -->
</div>
<div class="author-info">
<!-- 名字 -->
<strong class="skeleton">
<!-- Plasticine -->
</strong>
<!-- 日期 -->
<small class="skeleton">
<!-- Aug 13, 2022 -->
</small>
</div>
</section>
</main>
</div>
复制代码
再来看看效果:
设置文本元素占位符作为骨架屏填充
咦?生效是生效了,但是只有头部背景图和作者头像有效果,而文字部分全都没效果了,这是为啥呢?
这是因为文本元素中没有文本的时候,它没有自己的宽高,那设置background
属性自然也是不会生效的,所以我们需要给它添加一个用于占位的元素,只要有一个字符,就能够充满当前行了,这里我们就填充一个
空格占位符吧
<div class="card">
<!-- 存放图片 -->
<header class="skeleton">
<!-- <img
src="https://unsplash.com/photos/EaB4Ml7C7fE/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGNvZGV8ZW58MHx8fHwxNjYwMzQwOTQ4&force=true"
alt=""
/> -->
</header>
<main>
<h3 class="skeleton">
<!-- I love coding! -->
</h3>
<p class="skeleton">
<!-- Talk is cheap. Show me the code. -->
</p>
<section class="author">
<div class="author-avatar skeleton">
<!-- <img
src="https://pixabay.com/get/g9ac59ef391ebfbe7b3303ed0278986a31783bedd580d5e097980ed990d9bf646b5dc44932b18fd633df1813686e4f809.png?attachment="
alt=""
/> -->
</div>
<div class="author-info">
<!-- 名字 -->
<strong class="skeleton">
<!-- Plasticine -->
</strong>
<!-- 日期 -->
<small class="skeleton">
<!-- Aug 13, 2022 -->
</small>
</div>
</section>
</main>
</div>
复制代码
现在的效果如下:
可以看到这样就行了,那么接下来我们就通过js
去模拟数据加载,加载完成后,将数据插入到对应元素中,并将.skeleton
样式去除
js模拟数据加载效果
为了方便js
获取对应元素,我们给应用了骨架屏特效的元素起一个语义化的id
<div class="card">
<!-- 头部图片 -->
<header class="skeleton" id="header-img-container">
<!-- <img
src="https://unsplash.com/photos/EaB4Ml7C7fE/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGNvZGV8ZW58MHx8fHwxNjYwMzQwOTQ4&force=true"
alt=""
/> -->
</header>
<main>
<h3 class="skeleton" id="card-title">
<!-- I love coding! -->
</h3>
<p class="skeleton" id="card-content">
<!-- Talk is cheap. Show me the code. -->
</p>
<section class="author">
<div class="author-avatar skeleton" id="card-author-avatar-container">
<!-- <img
src="https://pixabay.com/get/g9ac59ef391ebfbe7b3303ed0278986a31783bedd580d5e097980ed990d9bf646b5dc44932b18fd633df1813686e4f809.png?attachment="
alt=""
/> -->
</div>
<div class="author-info">
<!-- 名字 -->
<strong class="skeleton" id="card-author-name">
<!-- Plasticine -->
</strong>
<!-- 日期 -->
<small class="skeleton" id="card-author-date">
<!-- Aug 13, 2022 -->
</small>
</div>
</section>
</main>
</div>
复制代码
现在就可以用js
去模拟数据加载效果啦
(() => {
const skeletonEls = {
oHeaderImgContainer: document.getElementById("header-img-container"),
oCardTitle: document.getElementById("card-title"),
oCardContent: document.getElementById("card-content"),
oCardAuthorAvatarContainer: document.getElementById(
"card-author-avatar-container"
),
oCardAuthorName: document.getElementById("card-author-name"),
oCardAuthorDate: document.getElementById("card-author-date"),
};
const init = () => {
const fetchData = () => {
setTimeout(() => {
const data = {
headerImg: `
<img
src="https://unsplash.com/photos/EaB4Ml7C7fE/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGNvZGV8ZW58MHx8fHwxNjYwMzQwOTQ4&force=true"
alt=""
/>
`,
cardTitle: "I love coding!",
cardContent: "Talk is cheap. Show me the code.",
cardAuthorAvatar: `
<img
src="https://pixabay.com/get/g9ac59ef391ebfbe7b3303ed0278986a31783bedd580d5e097980ed990d9bf646b5dc44932b18fd633df1813686e4f809.png?attachment="
alt=""
/>
`,
cardAuthorName: "Plasticine",
cardAuthorDate: "Aug 13, 2022",
};
// 插入加载到的数据
skeletonEls.oHeaderImgContainer.innerHTML = data.headerImg.trim();
skeletonEls.oCardTitle.innerHTML = data.cardTitle;
skeletonEls.oCardContent.innerHTML = data.oCardContent;
skeletonEls.oCardAuthorAvatarContainer.innerHTML =
data.cardAuthorAvatar.trim();
skeletonEls.oCardAuthorName.innerHTML = data.cardAuthorName;
skeletonEls.oCardAuthorDate.innerHTML = data.cardAuthorDate;
// 移除 `.skeleton` 类名从而 移除骨架屏特效
for (const el of Object.values(skeletonEls)) {
el.classList.remove("skeleton");
}
}, 3000);
};
fetchData();
};
init();
})();
复制代码
最终效果就像开头中的那样,大功告成!