骨架屏实现的那些坑我帮你踩过了

Mc.静云 前端 阅读 2,910
赞 26 收藏
二维码
手机扫码查看
反馈

骨架屏的核心,其实就这么几招

之前做项目的时候,首页加载慢得要命,用户体验差到爆。后来加了骨架屏,整个感觉立马不一样了。不过说实话,刚开始搞骨架屏的时候我也踩了不少坑,现在回头看,其实核心就是几个要点。

骨架屏实现的那些坑我帮你踩过了

我一般的做法是把骨架屏分成两层:一层是通用的样式组件,另一层是业务层面的展示控制。这样既保证了复用性,又能灵活控制显示时机。

我的写法,亲测靠谱

先说样式部分,这是骨架屏的灵魂。我用的是渐变动画 + 圆角矩形的方式,视觉效果比较自然:

.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 4px;
}

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

然后是组件封装,我习惯用 Vue 的方式:

<template>
  <div v-if="loading" class="skeleton-container">
    <!-- 标题区域 -->
    <div class="skeleton-title"></div>
    
    <!-- 内容区域 -->
    <div class="skeleton-content">
      <div class="skeleton-line" style="width: 80%;"></div>
      <div class="skeleton-line" style="width: 60%;"></div>
    </div>
    
    <!-- 按钮区域 -->
    <div class="skeleton-button"></div>
  </div>
  
  <div v-else class="content-container">
    <!-- 实际内容 -->
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'SkeletonLoader',
  props: {
    loading: {
      type: Boolean,
      default: true
    }
  }
}
</script>

<style scoped>
.skeleton-container {
  padding: 16px;
}

.skeleton-title {
  height: 24px;
  width: 100%;
  margin-bottom: 12px;
}

.skeleton-content {
  margin-bottom: 16px;
}

.skeleton-line {
  height: 16px;
  margin-bottom: 8px;
}

.skeleton-button {
  height: 40px;
  width: 120px;
}
</style>

这几种错误写法,别再踩坑了

说起来我还记得第一次搞骨架屏的时候犯的几个低级错误。最典型的就是用纯色填充做骨架屏,结果看起来跟死页面一样,完全没有活的感觉。用户根本分不清是页面卡了还是在加载中。

还有一次是动画时间设置得太长,比如3-5秒的动画周期,用户等得都快睡着了。骨架屏的动画应该是快速循环的,一般1-2秒一个周期最合适。太慢的话反而会让用户觉得加载更慢。

另一个常见错误是骨架屏形状和真实内容差距太大。比如真实内容是圆形头像,骨架屏却用了方形,这样切换的时候会有明显的形状跳跃,用户体验很糟糕。我现在的做法是让骨架屏的形状、尺寸尽量贴近真实内容。

还有一个坑是全局显示骨架屏。有些同学为了省事,把骨架屏做成全局组件,所有数据加载都用同一个骨架屏。结果就是首页用列表骨架屏,详情页也用列表骨架屏,完全不符合实际布局,看起来就很奇怪。

实际项目中的坑

在真实项目里,最大的问题是何时隐藏骨架屏。我之前遇到过一个情况:数据加载完了,但是页面渲染还没完成,这时候隐藏骨架屏就会出现闪烁。后来我发现需要结合 Vue 的 nextTick 或者 React 的 useEffect 来确保 DOM 渲染完成后再切换。

还有个头疼的问题是移动端适配。不同屏幕尺寸下,骨架屏的宽高比需要动态调整。我的解决方案是在 CSS 中使用相对单位,并配合媒体查询来做响应式处理:

@media (max-width: 768px) {
  .skeleton-title {
    height: 20px;
    margin-bottom: 8px;
  }
  
  .skeleton-line {
    height: 14px;
    margin-bottom: 6px;
  }
}

性能方面也要注意,如果页面元素太多,每个都需要骨架屏,CSS 类就会变得很庞大。我一般会提取公共的骨架屏样式,通过不同的宽度、高度、圆角来区分不同类型的内容元素。

另外,网络状态异常的处理也很重要。有时候接口挂了,骨架屏一直在转圈,用户以为还在加载。我现在的做法是给骨架屏加上超时机制,超过5秒就提示加载失败,并提供重试按钮。

和 Loading 的区别使用

很多人问我骨架屏和传统 Loading 有什么区别,什么时候用哪个。我的理解是:Loading 更适合全屏或大面积的数据加载,比如页面初次进入;而骨架屏更适合局部内容的预占位,让用户知道即将显示的内容结构。

举个例子:首页列表数据加载时,我会用 Loading 显示全屏遮罩;而当用户下拉刷新时,我会保持现有列表不变,在顶部插入新的骨架屏条目,这样用户的上下文不会丢失。

在实际项目中,我经常组合使用这两种方案。比如页面初始化时显示 Loading,获取到数据结构后切换到骨架屏,最后渲染真实内容。

最后的一些优化建议

骨架屏的缓存策略也要考虑。如果用户频繁进出同一页面,每次都重新渲染骨架屏会造成性能浪费。我一般会在组件销毁时不立即清除骨架屏状态,而是缓存一段时间,下次进入时可以直接复用。

颜色搭配也很重要,骨架屏的颜色要和整体设计风格一致。不要用太鲜艳或者太突兀的颜色,最好选择页面主色调的浅色版本,这样切换时不会显得突兀。

还有个小技巧:可以根据数据量动态调整骨架屏的数量。比如列表页,如果知道服务器会返回10条数据,那就渲染10个骨架屏条目,而不是固定的3-5个。这样用户对等待时间的心理预期会更准确。

以上是我踩坑后的总结,希望对你有帮助。骨架屏虽然看起来简单,但要做好确实需要考虑很多细节。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论