什么是骨架屏?

骨架屏(Skeleton Screen)是指在页面数据加载完成前,先给用户展示出页面的大致结构(灰色占位图),在拿到接口数据后渲染出实际页面内容然后替换掉。Skeleton Screen 是近两年开始流行的加载控件,本质上是界面加载过程中的过渡效果。

假如能在加载前把网页的大概轮廓预先显示,接着再逐渐加载真正内容,这样既降低了用户的焦灼情绪,又能使界面加载过程变得自然通畅,不会造成网页长时间白屏或者闪烁。这就是 Skeleton Screen !

Skeleton Screen 能给人一种页面内容"已经渲染出一部分"的感觉,相较于传统的 loading 效果,在一定程度上可提升用户体验。

骨架屏实现方案概览

目前生成骨架屏的技术方案主要分为手动编写和自动生成两大类:

🔧 方案对比

方案类型 代表工具 优点 缺点
手动编写 HTML+CSS / vue-skeleton-webpack-plugin 完全可控,样式精准 重复劳动,维护成本高
自动生成 page-skeleton-webpack-plugin 自动生成,提供UI调整界面 依赖Puppeteer,嵌套较深
自动生成 draw-page-structure (DPS) 扁平DOM结构,体积小 复杂布局效果可能不理想

1. 手动编写方案

使用图片、SVG或手动编写代码

html

<!-- 手动编写的骨架屏示例 -->
<div class="skeleton">
  <div class="skeleton-header"></div>
  <div class="skeleton-content">
    <div class="skeleton-line"></div>
    <div class="skeleton-line"></div>
    <div class="skeleton-thumbnail"></div>
  </div>
</div>

缺点:面对视觉设计的改版以及需求的更迭,对骨架屏的跟进修改会非常被动,这种机械化重复劳作的方式此时未免显得有些机动性不足。

通过预渲染手动书写的代码

该方案做的比较成熟的是 vue-skeleton-webpack-plugin,通过 vueSSR 结合 webpack 在构建时渲染写好的 vue 骨架屏组件。

javascript

// webpack.conf.js
const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');

plugins: [
  new SkeletonWebpackPlugin({
    webpackConfig: {
      entry: {
        app: resolve('./src/entry-skeleton.js')
      }
    }
  })
]

局限:该方案与 vue 相关技术直接关联,在当今前端框架三分天下的大环境下,我们可能需要一个更加灵活、可控的方案。

2. 自动生成方案

page-skeleton-webpack-plugin(饿了么)

javascript

// webpack.conf.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { SkeletonPlugin } = require('page-skeleton-webpack-plugin')
const path = require('path')

plugins: [
  new HtmlWebpackPlugin({
    // Your HtmlWebpackPlugin config
  }),
  new SkeletonPlugin({
    pathname: path.resolve(__dirname, `${customPath}`),
    staticDir: path.resolve(__dirname, './dist'),
    routes: ['/', '/search'],
  })
]

特点:可以启动 UI 界面专门调整骨架屏,但是在面对复杂的页面也会有不尽如人意的地方,而且生成的骨架屏节点是基于页面本身的结构和 CSS,存在嵌套比较深的情况,体积不会太小。

创新实现方案:基于DOM分析的自动生成

后来仔细想想,骨架屏这幅样子不是和一堆颜色块拼起来的页面一样吗?对比现有的骨架屏方案,这个想法有点"走捷径"的感觉。再进一步思考,这些色块基于当前页面去分析节点来生成,不如来段 JS 分析页面节点,一顿 DOM 操作生成颜色块拼成骨架屏。

🎯 核心设计思路

既然骨架屏代表了页面的大致结构,那么需要先用 js 对页面的结构进行分析。分析之前,我们需要制定一种规则,以确定:

  • 需要排除哪些节点?

  • 哪些种类的节点需要生成颜色块?

  • 生成的颜色块如何定位?

📝 核心规则设计

1. DOM节点遍历筛选规则
  • 只遍历可见区域可见的 DOM 节点

  • 非隐藏元素、宽高大于 0 的元素

  • 非透明元素、内容不是空格的元素

  • 位于浏览窗口可见区域内的元素

2. 元素类型处理规则
  • 针对(背景)图片、文字、表单项、音频视频、Canvas等区域生成颜色块

  • 使用扁平化的色块结构,避免复杂嵌套

3. 适配规则
  • 页面节点使用的样式不可控,所以不可取 style 的尺寸相关的值

  • 通过 getBoundingClientRect 获取节点宽、高、距离视口距离的绝对值

  • 计算出与当前设备的宽高对应的百分比作为颜色块的单位

🔨 技术实现细节

节点筛选实现

javascript

function isHideStyle(node) {
    return getStyle(node, 'display') === 'none' || 
        getStyle(node, 'visibility') === 'hidden' || 
        getStyle(node, 'opacity') == 0 ||
        node.hidden;
}
颜色块生成核心

javascript

const blocks = [];

// width,height,top,left 都是算好的百分比
function drawBlock({width, height, top, left, zIndex = 9999999, background, radius} = {}) {
  const styles = [
    'position: fixed',
    'z-index: '+ zIndex,
    'top: '+ top +'%',
    'left: '+ left +'%',
    'width: '+ width +'%',
    'height: '+ height +'%',
    'background: '+ background
  ];
  radius && radius != '0px' && styles.push('border-radius: ' + radius);
  blocks.push(`<div style="${ styles.join(';') }"></div>`);
}
递归遍历算法

基于确定的 rootNode(如 document.body)作为入口节点进行递归遍历和筛选,初步排除不可见节点。

🎨 绘制策略

对于符合条件的区域,"一视同仁"生成相应区域的颜色块。这种统一生成的方式使得骨架屏的节点更可控:

  • 扁平结构:生成的节点是扁平的,体积比较小

  • 样式独立:避免额外的读取样式表,通过抽离样式维持骨架屏的外观

  • 统一处理:不区分具体元素、不考虑结构层级、不考虑样式

⚡ 浏览器端直接运行

由于方案基于纯 DOM 操作,核心代码可以直接在浏览器中运行:

javascript

const createSkeletonHTML = require('draw-page-structure/evalDOM')

createSkeletonHTML({
    background: 'red',
    animation: 'opacity 1s linear infinite;'
}).then(skeletonHTML => {
    console.log(skeletonHTML)
}).catch(e => {
    console.error(e)
})

🔧 微调钩子函数

针对复杂场景,提供了两个钩子函数进行微调:

1. init函数

在开始遍历节点之前执行,适合删除干扰节点等操作。

2. includeElement(node, draw)函数

在遍历到指定节点时,调用 draw 方法进行自定义绘制。

🚀 CLI工具集成

为了让生成过程更加自动化,开发了配套的 CLI 工具,通过 Puppeteer 实现全自动生成:

使用步骤
  1. 安装工具

    bash

    npm i draw-page-structure -g
  2. 初始化配置

    bash

    dps init
  3. 修改配置

    javascript

    // dps.config.js
    module.exports = {
      url: 'https://example.com',
      output: {
        filepath: '/path/to/skeleton.html',
        injectSelector: '#app'
      },
      background: '#eee',
      animation: 'opacity 1s linear infinite'
    }
  4. 开始生成

    bash

    dps start
核心配置项
  • url: 目标页面地址

  • output.filepath: 骨架屏HTML输出路径

  • output.injectSelector: 骨架屏插入的位置

  • background: 骨架屏背景色

  • animation: 骨架屏动画效果

  • header: 请求头设置

  • device: 模拟设备类型

📊 方案架构图

text

页面URL
    ↓
Puppeteer打开页面
    ↓
注入evalDOM.js脚本
    ↓
执行DOM分析遍历
    ↓
应用生成规则
    ↓
生成骨架屏HTML
    ↓
保存到文件/插入页面

🎯 实际效果展示

京东PLUS会员首页

原页面:完整的商品展示和布局
骨架屏效果:精确还原了页面结构轮廓,包括头部、导航栏、商品卡片等区域的占位

移动端百度首页

生成效果:准确捕捉了搜索框、热门推荐、底部导航等关键区域的骨架结构

💡 技术挑战与解决方案

复杂布局处理

挑战:对于页面结构复杂或大图片比较多的页面,生成的颜色块可能紧挨着,效果不理想。

解决方案

  • 提供钩子函数进行自定义调整

  • 针对特定布局模式制定特殊规则

  • 支持区域级别的块生成策略

性能优化

  • 使用百分比单位确保多设备适配

  • 扁平DOM结构减少内存占用

  • 异步生成避免阻塞主线程

🔮 未来优化方向

  1. 智能布局识别:通过机器学习识别常见布局模式

  2. 动画效果丰富:提供多种加载动画选择

  3. 跨框架支持:更好地支持React、Angular等框架

  4. 可视化配置:提供图形界面进行规则配置和预览

📝 总结

基于DOM分析的骨架屏自动生成方案,通过纯JS遍历页面节点,根据制定的规则生成相应区域的颜色块,最终形成页面的骨架屏。这种方案具有以下优势:

  • 自动化程度高:只需简单配置即可生成

  • 体积小巧:扁平的DOM结构,代码量少

  • 适配性好:使用百分比单位,多设备兼容

  • 灵活可控:提供钩子函数进行个性化调整

虽然网页布局和样式组合的可能性太多,想要在各种场景下都获得理想的效果还有很长的路要走,但基于DOM分析的自动生成方案已经为骨架屏的大规模应用提供了可行的技术路径。

GitHub项目famanoder/dps

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐