You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何先显示骨架屏再加载实际数据?React实现方法咨询

实现Airbnb式骨架屏+逐个加载效果的方案

核心原理

这种效果的核心是先渲染占位的骨架屏,然后通过监听资源加载状态或定时控制,将骨架屏逐个替换为真实内容,同时配合过渡动画提升视觉流畅度。


React 基础实现(无依赖库)

1. 单元素的骨架屏+加载切换

下面是一个基础的图片卡片组件,先显示骨架屏,图片加载完成后淡入显示真实内容:

import { useState } from 'react';
import './Card.css';

const ImageCard = ({ src, alt }) => {
  const [isLoaded, setIsLoaded] = useState(false);

  return (
    <div className="card-container">
      {/* 骨架屏 */}
      {!isLoaded && <div className="card-skeleton"></div>}
      {/* 真实内容 */}
      <img
        src={src}
        alt={alt}
        className={`card-image ${isLoaded ? 'loaded' : 'hidden'}`}
        onLoad={() => setIsLoaded(true)}
        onError={() => setIsLoaded(true)} // 加载失败也切换显示状态
      />
    </div>
  );
};

export default ImageCard;

对应的CSS(Card.css):

.card-container {
  position: relative;
  width: 200px;
  height: 200px;
  border-radius: 8px;
  overflow: hidden;
}

.card-skeleton {
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

.card-image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.3s ease;
}

.card-image.hidden {
  opacity: 0;
}

.card-image.loaded {
  opacity: 1;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

2. 批量元素逐个加载

如果是列表类场景,需要逐个触发加载效果,可以结合setTimeout和状态数组来控制每个元素的显示时机:

import { useState, useEffect } from 'react';

const ImageList = ({ images }) => {
  const [loadedIndices, setLoadedIndices] = useState([]);

  useEffect(() => {
    images.forEach((_, index) => {
      // 每个元素延迟150ms加载,模拟逐个出现的效果
      setTimeout(() => {
        setLoadedIndices(prev => [...prev, index]);
      }, index * 150);
    });
  }, [images]);

  return (
    <div className="image-list">
      {images.map((img, index) => (
        <div key={index} className="list-item">
          {!loadedIndices.includes(index) && <div className="card-skeleton"></div>}
          <img
            src={img.src}
            alt={img.alt}
            className={`card-image ${loadedIndices.includes(index) ? 'loaded' : 'hidden'}`}
          />
        </div>
      ))}
    </div>
  );
};

export default ImageList;

简化实现的第三方库

如果不想手动写骨架屏样式和状态逻辑,可以用这些成熟的库:

  • react-content-loader: 可以快速生成SVG骨架屏,支持自定义形状,内置加载动画,只需配置骨架结构即可。
  • shimmer-react: 专门用于实现闪光(shimmer)效果的骨架屏,API简洁,适合快速集成。
  • react-skeleton-loader: 轻量的骨架屏组件,支持自定义样式和动画。

以react-content-loader为例,快速实现列表骨架屏:

import ContentLoader from 'react-content-loader';

const ListSkeleton = () => (
  <ContentLoader 
    speed={2}
    width={400}
    height={160}
    viewBox="0 0 400 160"
    backgroundColor="#f3f3f3"
    foregroundColor="#ecebeb"
  >
    <rect x="0" y="0" rx="8" ry="8" width="70" height="70" />
    <rect x="80" y="17" rx="4" ry="4" width="300" height="13" />
    <rect x="80" y="40" rx="4" ry="4" width="250" height="13" />
    <rect x="80" y="63" rx="4" ry="4" width="170" height="13" />
  </ContentLoader>
);

// 在组件中使用:
const List = ({ items, isLoading }) => {
  if (isLoading) return <ListSkeleton />;
  return (
    <div className="list">
      {items.map(item => <ListItem key={item.id} {...item} />)}
    </div>
  );
};

注意事项

  • 优先监听真实资源的onLoad事件,而不是固定setTimeout,这样更符合真实加载场景,避免内容未加载完成就出现占位消失的情况。
  • 骨架屏的样式尽量贴近真实内容的布局和尺寸,减少视觉跳动。
  • 可以结合React的Suspense和Lazy Loading实现更高级的代码分割+骨架屏效果。

内容的提问来源于stack exchange,提问作者Jonathan Lightbringer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 06:00:17