Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
popring committed Sep 23, 2024
1 parent 43c6cec commit c677c0a
Showing 1 changed file with 27 additions and 38 deletions.
65 changes: 27 additions & 38 deletions source/_posts/首屏骨架屏减少白屏.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
---
title: 首屏骨架屏减少白屏
title: 使用骨架屏减少首屏白屏现象
date: 2024-09-22 21:55:05
tags:
categories: 性能优化
---

## 背景

用户从输入 `url` 到打开页面,经历的步骤 [参考这里](https://popring.github.io/2021/03/06/%E7%AE%80%E5%8D%95%E8%81%8A/%E4%BB%8E%E8%BE%93%E5%85%A5url%E5%88%B0%E9%A1%B5%E9%9D%A2%E6%98%BE%E7%A4%BA%E7%BB%8F%E8%BF%87%E4%BA%86%E4%BB%80%E4%B9%88/) ,而现在前端应用程序通常都会使用 `React` `Vue``Angular``Solid` 等库框架进行开发,由框架层面进行工程化进行统一管理
用户从输入 `url` 到打开页面,经历的步骤可以参考[这里](https://popring.github.io/2021/03/06/%E7%AE%80%E5%8D%95%E8%81%8A/%E4%BB%8E%E8%BE%93%E5%85%A5url%E5%88%B0%E9%A1%B5%E9%9D%A2%E6%98%BE%E7%A4%BA%E7%BB%8F%E8%BF%87%E4%BA%86%E4%BB%80%E4%B9%88/)。现代前端应用程序通常使用 `React``Vue``Angular``Solid` 等框架进行开发,这些框架统一管理工程化内容

而这也导致一个问题就是,由以上框架开发的 `SPA` 应用生成的 html 应用中,通常只包含一个 `<div id="#app"></div>` ,剩下的内容都是通过后续的 `script` 运行时进行渲染。

此时用户加载到的 `html` 就为白屏,等脚本解析执行后,才会主线渲染内容。于是就出现了 `SSR (服务端渲染)`,直接在服务端就将内容渲染好返回到前端,但是此方法需要项目整体改造,成本略高。我们也可以使用另一种方案,首屏骨架图渲染减少白屏。
这也导致了一个问题:通过这些框架开发的单页面应用(SPA)通常只包含一个 `<div id="app"></div>`,而其余内容都是在后续脚本运行时动态渲染。这使得用户加载的 HTML 页面往往呈现为白屏,只有等到脚本解析执行后,内容才会呈现。因此,服务端渲染(SSR)应运而生,它在服务器端就将内容渲染好并返回给前端,虽然这需要整体改造项目,成本较高。另一种方案是使用首屏骨架图渲染,以减少白屏现象。

## 原理

直接将骨架图写在 `html` 中,等实际内容加载完毕后直接将骨架图替换为内容。

## 生成骨架屏方式
骨架屏的原理是直接将骨架图嵌入 HTML 中,实际内容加载完毕后将骨架图替换为真实内容。

- 单独写一套骨架屏样式,然后注入
- 使用骨架屏图片
- 使用自动生成骨架屏形式
- `page-skeleton-webpack-plugin` ,这个插件目前已经没有维护了,(而且我也运行不起来,不是很推荐)
- 使用 `chrome 插件` 生成骨架屏,比如 `@killblanks/skeleton-ext` ,试了好几个,目前只有这个还生效,不过生成之后还是需要微调一下样式
- 自己写一套,其实原理并不复杂,简而言之就是将页面的文字和图片替换为骨架图形式展示。这里我用 chatgpt 生成了一段代码供参考,下面展示下效果图,分别用 demo页、掘金、小红书、淘宝、京东测试了下生成的骨架图,可以看出对于结构复杂页面生成还是会有点问题,如果要实际用,还是需要微调一下样式。
## 生成骨架屏的方式

![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409240136523.png)
![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409240136518.png)
![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409240136520.png)
![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409240136521.png)
![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409240136522.png)

下面这段代码可以复制到浏览器中执行,并预览生成的效果;如果想要使用生成的骨架屏,可以只在页面中复制dom `#skeleton-overlay`
1. **单独编写骨架屏样式并注入**:需要手动维护样式。
2. **使用骨架屏图片**:适合简单场景,但不够灵活。
3. **自动生成骨架屏**
- `page-skeleton-webpack-plugin`:不再维护,不推荐。
- 使用 Chrome 插件生成骨架屏,比如 `@killblanks/skeleton-ext`,效果不错,但样式需要微调。
- 自定义实现,原理简单,将页面的文字和图片替换为骨架图形式。以下是使用代码生成骨架图的示例,可以直接复制到浏览器中执行,预览效果。

```js
// 递归生成骨架图
Expand Down Expand Up @@ -127,11 +117,11 @@ window.generateSkeleton = generateSkeleton;
window.toggleSkeleton = toggleSkeleton;
```

## 注入代码 1 - 注入进 #app 内
## 注入代码 1 - 注入进 `#app`

比如这里我使用的是 `vite` 打包工具,'webpack' 同理,也有同类型的工具可以直接用
这里我使用的是 `vite` 打包工具,`webpack` 可以使用类似的方法

首先需要写一个插件,在生成时修改 html 代码
首先需要编写一个插件,在生成时修改 HTML 代码

```js
// /plugins/skeletonPlugin.ts
Expand All @@ -144,6 +134,8 @@ export function SkeletonPlugin(): PluginOption {
return {
name: 'SkeletonPlugin',
async transformIndexHtml(html) {
// 新增
const modifiedHtml = html.replace('<script', '<script defer');
const content = (await import(filename)).default;
const code = `
<script>
Expand All @@ -154,13 +146,13 @@ var content = target && target.html || ''
document.write(content)
</script>
`;
return html.replace(/__SKELETON_CONTENT__/, code);
return modifiedHtml.replace(/__SKELETON_CONTENT__/, code);
},
};
}
```

html 中在 `id="root"` 中增加内容 `__SKELETON_CONTENT__` ,方便填充骨架屏
在 HTML 中,`<div id="root">` 内部增加内容 `__SKELETON_CONTENT__`,以便填充骨架屏

```html
<!-- /index.html -->
Expand Down Expand Up @@ -189,11 +181,9 @@ export default {
};
```

生成结果如下,不过需要注意的是页面入口的 `script` 需要设置为 `defer` 才会生效,否则加载入口 `script` 会阻塞后续代码执行,也就相当于事骨架图代码不会生效。

![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409222315103.png)
生成结果如下,需要注意的是,页面入口的 `<script>` 需要设置为 `defer`,以确保骨架图代码生效,避免阻塞后续代码执行。

粗糙一点实现,在 `plugins/skeletonPlugin.ts` 中暴力将所有 `script` 都新增 `defer` ,当然这种操作不优雅,有待优化
粗糙一点的实现是,在 `plugins/skeletonPlugin.ts` 中暴力将所有 `<script>` 标签新增 `defer` 属性,虽然这种方式不够优雅,但可以解决问题

```js
// /plugins/skeletonPlugin.ts
Expand All @@ -206,8 +196,7 @@ export function SkeletonPlugin(): PluginOption {
return {
name: 'SkeletonPlugin',
async transformIndexHtml(html) {
// 新增
const modifiedHtml = html.replace('<script', '<script defer')
const modifiedHtml = html.replace('<script', '<script defer');
const content = (await import(filename)).default;
const code = `
<script>
Expand All @@ -224,23 +213,23 @@ document.write(content)
}
```

至此,该方案基本已经结束,但是实际应用时,发现还是白屏闪一下,是因为在框架侧加载页面时异步的,先进行渲染了根路由信息,然后才会渲染具体路由页面的信息,所以方案还是有待完善,于是出现了下面一种
至此,该方案基本完成。然而在实际应用中,仍会出现白屏闪烁现象,这是由于框架加载页面时的异步操作导致的,首先渲染根路由信息,然后才会渲染具体路由的信息,因此该方案有待进一步完善

## 注入代码 2 - 优化,显示在页面最上层

## 注入代码 2 - 显示在页面最上层
可以将骨架屏渲染在一个空的 `div` 中,并通过 `fixed` 样式将其固定在页面的最上层。随后,监听页面实际渲染的状态,页面渲染完成后将骨架图隐藏

将骨架屏渲染在一个空 `div` 上,然后用样式 `fixed` 在页面的最上层,然后监听页面实际渲染的页面是否渲染完成,实际页面渲染完之后将骨架图隐藏,就可以在视觉上进行实现了
,从而在视觉上达到良好的效果

实现效果如图
实现效果如下:

![](https://raw.githubusercontent.com/popring/assets-repo/master/img/202409240252167.gif)

**最终代码**
https://github.com/popring/vite-skeleton
[GitHub 链接](https://github.com/popring/vite-skeleton)

> 参考
>
> [Vue项目骨架屏注入实践](https://juejin.cn/post/6844903661726859272)
>
> [一个前端非侵入式骨架屏自动生成方案](https://juejin.cn/post/7109083708463775752)
>

0 comments on commit c677c0a

Please sign in to comment.