js代码
import React, { useState, useEffect } from 'react';
import ReactDom from 'react-dom';
import './App.css'
const card = (
<div className="card">
<div className="header">
<img className="skeleton-img" src="https://source.unsplash.com/100x100/?animal" alt="" />
<div data-title className="title">
<div className="skeleton-text"></div>
<div className="skeleton-text"></div>
</div>
</div>
<div data-body>
<div className="skeleton-text"></div>
<div className="skeleton-text"></div>
<div className="skeleton-text"></div>
<div className="skeleton-text"></div>
</div>
</div>
);
function Skeleton() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then((res) => res.json())
.then((data) => {
setPosts(data);
});
}, []);
return (
<div className="grid">
{posts.length === 0
? Array(10).fill().map((_, index) => <div key={index}>{card}</div>)
: posts.map((post) => (
<div key={post.id} className="card">
<div className="header">
<img className="skeleton-img" src="https://source.unsplash.com/100x100/?animal" alt="Animal" />
<div data-title className="title">
{post.title}
</div>
</div>
<div data-body>{post.body}</div>
</div>
))}
</div>
);
}
export default Skeleton;
css代码
*,
*::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;
}
/* 渐进式效果 */
.skeleton-text {
width: 100%;
height: 0.5rem;
/* background-image: linear-gradient(90deg, #ff0000 0%, #41de6a 37%, #ff0000 100%); */
background-image: linear-gradient(90deg,
hsl(200, 20%, 70%) 0%,
hsl(200, 20%, 90%) 50%,
hsl(200, 20%, 70%) 100%);
/* background-image: linear-gradient(90deg, white 25%, red 37%, black 63%); */
margin-bottom: 0.25rem;
border-radius: 0.125rem;
opacity: 0.7;
background-size: 400% 100%;
background-position: 100% 50%;
animation: skeleton-text-loading 0.6s linear infinite;
}
.skeleton-text:last-child {
width: 80%;
margin-bottom: 0;
}
@keyframes skeleton-text-loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
/* 呼吸式效果 */
.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%);
}
}
li {
background-image: linear-gradient(90deg, #ff0000 25%, #41de6a 37%, #ff0000 63%);
width: 100%;
height: 0.6rem;
list-style: none;
background-size: 400% 100%;
background-position: 300% 50%;
}
参考链接:https://juejin.cn/post/7293765765126733859?searchId=202408271516313ED5BAB612E929DFF4A3