在 React 中实现骨架加载

2025-05-26

在 React 中实现骨架加载

骨架加载骨架屏幕的概念最早在 2013 年Luke Wroblewski 的博客文章中提出。它描述了一个空白屏幕的概念,其中动态内容被样式块(骨架)替换,加载完成后再替换为真实内容。

替代文本

骨架加载比旋转器更好

当用户看到一个带有标准加载旋转器的页面时,他们会想“我正在等它加载”。我们让用户观看一个枯燥的重复动画,让用户感觉就像在盯着时钟一样。

当用户看到带有骨架加载或骨架屏幕的页面时,他们会想“它快要加载完成了,有事情正在发生”。用户看到的页面看起来就像是即将完成加载一样。即使我们还没有加载任何内容,用户看到的页面也不是空洞或枯燥的。这给人一种速度飞快的错觉。

但是关于骨架加载有一些事情需要注意...

当最终加载状态可预测时使用骨架加载

我们不能简单地用骨架加载来替换页面上每个动态加载的元素。在某些情况下,我们无法预测页面最终加载状态会是什么样子。

例如,如果我们尝试假设页面的最终状态(假设我们从 4 种截然不同的可能状态中选择了 1 种),并根据该假设创建了一个骨架屏幕。当用户查看骨架屏幕时,如果加载的内容与骨架屏幕大相径庭,过渡效果就会显得格格不入,令人不快。这可能会损害用户体验,并使您的应用显得混乱。

假设我们正在浏览一个电商网站的主页。电商网站主页经常会根据正在进行的活动或促销活动改变其外观和布局。在这里实现骨架加载并不是一个好主意。

看一下目录页面,其中产品以 4 列布局显示,每页 24 件商品。我们可以放心地假设这种状态不会改变。即使我们最终加载了 12 或 15 件商品,而不是假设的 24 件,过渡仍然很流畅。目录页面的加载也非常耗时(如果使用分页、排序和筛选),因此即使在加载时间超出预期的情况下,骨架加载也可能有助于让用户停留在页面上。

在 React 中实现骨架加载

在下面的示例中,我们实现了食谱卡片组件的骨架加载。组件的外观如下。

替代文本

import * as React from 'react';
import { Link } from 'react-router-dom';
import { LazyImage } from 'components';
import { getUri } from 'util/getUri';

export const RecipeCard = ({
  calories,
  ingredients,
  image,
  label,
  source,
  uri
}) => {
  return (
    <li className="recipeCard">
      <Link className="recipeCard__link" to={`/recipes/recipe/${getUri(uri)}`}>
        <LazyImage className="recipeCard__image" src={image} alt={label} />
        <div className="recipeCard__wrapper">
          <div>
            <h3 className="recipeCard__title">{label}</h3>
            <p className="paragraph">
              <strong>
                By <span className="gradient--text">{source}</span>
              </strong>
            </p>
          </div>

          <div className="recipeCard__info">
            <strong className="gradient--text">{Math.ceil(calories)}</strong>{' '}
            calories |{' '}
            <strong className="gradient--text">{ingredients.length}</strong>{' '}
            ingredients
          </div>
        </div>
      </Link>
    </li>
  );
};

Enter fullscreen mode Exit fullscreen mode

这是一个简单的骨架,没有使用任何动画,只有纯色,看起来也不错。你可以通过背景渐变动画轻松添加动画。

首先,我们需要设置骨架组件的样式.recipeCard__skeleton。我们在类中设置基础骨架样式。我们设置inline-block模仿内容行为(例如对齐)的样式,用于为元素padding添加高度(在本例中等于1 个 line-height 单位)。我们还设置了两个修饰符类,用于更改内容的宽度,以及一个用于宽高比为 1:1 的图像的附加类(这就是为什么padding设置为 的原因100%)。

.recipeCard__skeleton {
    display: inline-block;
    background-color: var(--color__gray--lighter);
    padding-bottom: var(--spacing__vertical--1);
}

.recipeCard__skeleton--medium {
    width: 33%;
}

.recipeCard__skeleton--large {
    width: 100%;
}
.recipeCard__image--skeleton {
    padding-bottom: 100%;
    background-color: var(--color__gray--lighter);
}

Enter fullscreen mode Exit fullscreen mode

让我们创建我们的 Skeleton 组件:

  1. 复制“真实”组件的内容并将其粘贴到 Skeleton 组件中。更改 const 名称并导出。
  2. 将所有动态内容(来自 props)替换为骨架元素。由于 Span 没有任何默认样式,因此可以正常工作。让布局样式和网格处理其他所有操作,并保持骨架元素的正确位置。
  3. 有条件地加载主组件中的骨架
import * as React from 'react';
import { Link } from 'react-router-dom';

export const Skeleton = () => {
  return (
    <li className="recipeCard">
      <div className="recipeCard__link">
        <div className="recipeCard__image--skeleton" />
        <div className="recipeCard__wrapper">
          <div>
            <h3 className="recipeCard__title">
              <span className="recipeCard__skeleton recipeCard__skeleton--large"></span>
            </h3>
            <p className="paragraph">
              <span className="recipeCard__skeleton recipeCard__skeleton--large"></span>
            </p>
          </div>

          <div className="recipeCard__info">
            <span className="recipeCard__skeleton recipeCard__skeleton--medium"></span>{' '}
            <span className="recipeCard__skeleton recipeCard__skeleton--medium"></span>
          </div>
        </div>
      </div>
    </li>
  );
};

Enter fullscreen mode Exit fullscreen mode

最终组件的外观如下。

替代文本

很简单吧?一旦你掌握了用骨架替换内容的方法,你就可以建立一个功能丰富的类库来替换骨架内容,并快速创建骨架屏幕。


这些文章都是咖啡带来的灵感。所以,如果你喜欢我的文章,觉得它有用,不妨请我喝杯咖啡!我会非常感激的。

给我买杯咖啡

感谢您花时间阅读这篇文章。如果您觉得它有用,请点赞 ❤️ 或 🦄,分享并评论。

文章来源:https://dev.to/prototyp/implementing-骨骼-loading-in-react-kia
PREV
使用这 5 个原则改进你的 CSS
NEXT
延迟加载图像以获得最佳性能的最佳方法