QQ登录

只需要一步,快速开始

APP扫码登录

只需要一步,快速开始

手机号码,快捷登录

手机号码,快捷登录

查看: 3457|回复: 0

[VUE] 前端网页骨架屏自动生成方案

[复制链接]

等级头衔

积分成就    金币 : 2841
   泡泡 : 1516
   精华 : 6
   在线时间 : 1294 小时
   最后登录 : 2024-11-21

丰功伟绩

优秀达人突出贡献荣誉管理论坛元老

联系方式
发表于 2021-1-22 12:23:47 | 显示全部楼层 |阅读模式
一、什么是骨架屏?
2 [( M! z' }  B& s       什么是骨架屏呢?骨架屏(Skeleton Screen)是指在页面数据加载完成前,先给用户展示出页面的大致结构(灰色占位图),在拿到接口数据后渲染出实际页面内容然后替换掉。Skeleton Screen 是近两年开始流行的加载控件,本质上是界面加载过程中的过渡效果。
/ X& T' H1 n. e5 H& `6 a/ C       假如能在加载前把网页的大概轮廓预先显示,接着再逐渐加载真正内容,这样既降低了用户的焦灼情绪,又能使界面加载过程变得自然通畅,不会造成网页长时间白屏或者闪烁。这就是 Skeleton Screen !4 @& D) U" B" Q1 K- i$ E" E$ b
       Skeleton Screen 能给人一种页面内容“已经渲染出一部分”的感觉,相较于传统的 loading 效果,在一定程度上可提升用户体验。  D0 `# G- D* `
二、骨架屏的实现方案( k! j" ?1 v3 r, A; J) K
       目前生成骨架屏的技术方案大概有三种:: f6 {+ h+ H1 {8 C9 e" @, R4 a' d
       1. 使用图片、svg 或者手动编写骨架屏代码:使用 HTML + CSS 的方式,我们可以很快的完成骨架屏效果,但是面对视觉设计的改版以及需求的更迭,我们对骨架屏的跟进修改会非常被动,这种机械化重复劳作的方式此时未免显得有些机动性不足;( P9 D8 K+ `9 F- z4 X6 n+ i6 J# G6 U
       2. 通过预渲染手动书写的代码生成相应的骨架屏:该方案做的比较成熟的是 vue-skeleton-webpack-plugin,通过 vueSSR 结合 webpack 在构建时渲染写好的 vue 骨架屏组件,将预渲染生成的 DOM 节点和相关样式插入到最终输出的 html 中。
8 E% i8 _; M  w& _) w1 ~
  1. // webpack.conf.js
  2. const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');
  3. plugins: [
  4.   //...
  5.   new SkeletonWebpackPlugin({
  6.     webpackConfig: {
  7.       entry: {
  8.         app: resolve('./src/entry-skeleton.js')
  9.       }
  10.     }
  11.   })
  12. ]
      该方案的前提同样是编写相应页面的骨架屏组件,然后预渲染生成骨架屏所需的 DOM 节点,但由于该方案与 vue 相关技术直接关联,在当今前端框架三分天下的大环境下,我们可能需要一个更加灵活、可控的方案;- |. e0 C* o0 T/ k0 j9 ]
       3 . 饿了么内部的生成骨架页面的工具:该方案通过一个 webpack 插件 page-skeleton-webpack-plugin 的方式与项目开发无缝集成,属于在自动生成骨架屏方面做的非常强大的了,并且可以启动 UI 界面专门调整骨架屏,但是在面对复杂的页面也会有不尽如人意的地方,而且生成的骨架屏节点是基于页面本身的结构和 CSS,存在嵌套比较深的情况,体积不会太小,并且只支持 history 模式。1 O  e8 q' t7 K) L
  1. // webpack.conf.js
  2. const HtmlWebpackPlugin = require('html-webpack-plugin')
  3. const { SkeletonPlugin } = require('page-skeleton-webpack-plugin')
  4. const path = require('path')
  5. plugins: [
  6.   //...
  7.   new HtmlWebpackPlugin({
  8.     // Your HtmlWebpackPlugin config
  9.   }),
  10.   new SkeletonPlugin({
  11.     pathname: path.resolve(__dirname, `${customPath}`), // 用来存储 shell 文件的地址
  12.     staticDir: path.resolve(__dirname, './dist'), // 最好和 `output.path` 相同
  13.     routes: ['/', '/search'], // 将需要生成骨架屏的路由添加到数组中
  14.   })
  15. ]
三、我们的实现方案2 T" M1 [8 \$ H" j3 p  u9 ^
       后来仔细想想,骨架屏这幅样子不是和一堆颜色块拼起来的页面一样吗?对比现有的骨架屏方案,这个想法有点“走捷径”的感觉。再进一步思考,这些色块基于当前页面去分析节点来生成,不如来段 JS 分析页面节点,一顿 DOM 操作生成颜色块拼成骨架屏。那么问题来了,该怎么样精确的分析页面节点,不同节点又该生成什么样的色块呢?+ u( h0 M- h/ k
       既然骨架屏代表了页面的大致结构,那么需要先用 js 对页面的结构进行分析。分析之前,我们需要制定一种规则,以确定需要排除哪些节点?哪些种类的节点需要生成颜色块?生成的颜色块如何定位等等。我们初步定下的规则如下:
: [2 d+ G4 c9 S       1. 只遍历可见区域可见的 DOM 节点,包括:非隐藏元素、宽高大于 0 的元素、非透明元素、内容不是空格的元素、位于浏览窗口可见区域内的元素等;- Q# {) f0 |/ X( G: O7 K
       2. 针对(背景)图片、文字、表单项、音频视频、Canvas、自定义特征的块等区域来生成颜色块;" D+ B; Z3 p' q- C6 T, o7 q! H% V5 A. H
       3. 页面节点使用的样式不可控,所以不可取 style 的尺寸相关的值,可通过 getBoundingClientRect 获取节点宽、高、距离视口距离的绝对值,计算出与当前设备的宽高对应的百分比作为颜色块的单位,来适配不同设备;
, h9 t+ O" o2 `0 C, q基于这套规则,我们开始生成骨架屏
4 _, ^/ z1 ]5 L0 `" ^0 _& u3 l       首先,确定一个 rootNode 作为入口节点,比如 document.body,同时方便以后扩展到生成页面内局部的骨架屏,由此入口进行递归遍历和筛选,初步排除不可见节点。+ `$ x$ Q) Z$ a: t
  1. function isHideStyle(node) {
  2.     return getStyle(node, 'display') === 'none' ||
  3.         getStyle(node, 'visibility') === 'hidden' ||
  4.         getStyle(node, 'opacity') == 0 ||
  5.         node.hidden;
  6. }
      接下来判断元素特征,确定是否符合生成条件,对于符合条件的区域,”一视同仁”生成相应区域的颜色块。”一视同仁”即对于符合条件的区域不区分具体元素、不考虑结构层级、不考虑样式,统一根据该区域与视口的绝对距离值生成 div 的颜色块。之所以这样是因为生成的节点是扁平的,体积比较小,同时避免额外的读取样式表、通过抽离样式维持骨架屏的外观,这种统一生成的方式使得骨架屏的节点更可控。基于那上述“走捷径”的想法,该方法生成的骨架屏是由纯 DOM 颜色块拼成的。
% G' h3 h4 Q5 K7 ~6 E, g, x: k生成颜色块的方法:1 S8 i( g4 ^; @) m6 O) \: p
  1. const blocks = [];
  2. // width,height,top,left 都是算好的百分比
  3. function drawBlock({width, height, top, left, zIndex = 9999999, background, radius} = {}) {
  4.   const styles = [
  5.     'position: fixed',
  6.     'z-index: '+ zIndex,
  7.     'top: '+ top +'%',
  8.     'left: '+ left +'%',
  9.     'width: '+ width +'%',
  10.     'height: '+ height +'%',
  11.     'background: '+ background
  12.   ];
  13.   radius && radius != '0px' && styles.push('border-radius: ' + radius);
  14.   // animation && styles.push('animation: ' + animation);
  15.   blocks.push(`<div style="${ styles.join(';') }"></div>`);
  16. }
      绘制颜色块并不难,绘制之前的分析确认才是这个方案真正的核心和难点。比如,对于页面结构比较复杂或者大图片比较多的页面,由图片拼接的区域没有边界,生成的颜色块就会紧挨着,出现不尽如人意的地方。再比如,一个包含很多符合生成条件的小块的 card 块区域,是以 card 块为准还是以里面的小块为准来生成颜色块呢?如果以小块为准,绘制结果可能给人的感觉压根就不是一个 card 块,再加上布局方式和样式的可能性太多,大大增加了不确定因素。而如果以 card 块为准生成颜色块的话还要对 card 块做专门的规则。
: k3 C  n* d9 P" {2 U" h4 ~       目前来说,对于页面结构不是特别复杂,不是满屏图片的,不是布局方式特别“飘逸“的场景,该方式已经可以生成比较理想的骨架屏了。而对于那些与预期相差较远的情况,我们提供了两个钩子函数可供微调:
+ J8 o& ?; ?% N& O       1. init 函数,在开始遍历节点之前执行,适合删除干扰节点等操作。; ?0 S4 ?# Y5 T$ h; J5 q
       2. includeElement(node, draw) 函数,可在遍历到指定节点时,调 用 draw 方法进行自定义绘制。' c$ ^- f* J( }/ E3 f# M9 {5 ]
       通过以上步骤就能够直接在浏览器中生成骨架屏代码了。: M9 e; E/ F/ U; @& x$ N. s
四、在浏览器里运行
$ _; \; ^5 I2 T3 |       由于我们的方案出发点是通过单纯的 DOM 操作,遍历页面上的节点,根据制定的规则生成相应区域的颜色块,最终形成页面的骨架屏,所以核心代码完全可以直接跑在浏览器端;
& j- N: s+ ^  F) `+ q4 e
  1. const createSkeletonHTML = require('draw-page-structure/evalDOM')
  2.     createSkeletonHTML({
  3.         // ...
  4.         background: 'red',
  5.         animation: 'opacity 1s linear infinite;'
  6.     }).then(skeletonHTML => {
  7.         console.log(skeletonHTML)
  8.     }).catch(e => {
  9.         console.error(e)
  10.     })
五、结合 Puppeteer 自动生成骨架屏
% O) A6 y; _: \: M       虽然该方式已经可以生成骨架屏代码了,但是还是不够自动化,为了让生成的骨架屏代码自动加载进指定页面。于是,我们开发了一个配套的 CLI 工具。这个工具通过 Puppeteer 运行页面,并把 evalDOM.js 脚本注入页面自动执行,执行的结果是生成的骨架屏代码被插入到应用页面。
4 E% p$ }) n& y; O& J0 t我们的方案大概思路如下:
1 |6 ?7 g3 @. N8 E 1.jpg
0 G& c. ]1 a5 ~2 [5 P$ Q: z# }接下来看看如何使用 CLI 工具生成骨架屏,最多只需如下四步:
; @$ O7 }. x% `3 ~+ c/ ^: Z4 f       1. 全局安装,npm i draw-page-structure – g
9 s2 B& ?# }1 n! [  n       2. dps init 生成配置文件 dps.config.js
; {3 a$ m3 B; \# t7 _8 d. D4 X       3. 修改 dps.config.js 进行相关配置
: m$ v# I* G" I$ h0 x6 i$ T       4. dps start 开始生成骨架屏6 b; |, Y- h; D4 i# n
只需简单几步,然而并没有繁琐的配置:
1 G6 S. L% m5 |! u8 @+ G& ^ 2.jpg ; @* h* r- Y6 j7 ^, a
一般来说,你需要按自己的项目情况来配置 dps.config.js ,常见的配置项有:  F2 A/ d5 Y2 A  S: `. u% l$ P6 Q1 ^, m
  • url: 待生成骨架屏的页面地址
  • output.filepath: 生成的骨架屏节点写入的文件
  • output.injectSelector: 骨架屏节点插入的位置,默认 #app
  • background: 骨架屏主题色
  • animation: css3 动画属性
  • rootNode: 真对某个模块生成骨架屏
  • device: 设备类型,默认 mobile
  • extraHTTPHeaders: 添加请求头
  • init: 开始生成之前的操作
  • includeElement(node, draw): 定制某个节点如何生成
  • writePageStructure(html, filepath): 回调的骨架屏节点3 p4 D, t3 A' q8 m& f; U
六、初步实现的效果:
4 \0 @( {# b1 C* 京东 PLUS 会员正式中首页:
" x: W( \- F5 K; K- Z 3.jpg ! R+ x- ~  K3 ?# {* H. W( M; f" M: \
* 京东 PLUS 会员正式中首页,通过该方案生成的骨架屏效果:
8 H6 Y6 }) e7 K 4.jpg ) I& x1 `, v1 X
七、总结
" u6 V9 {- j5 H# R( L: H       以上就是基于 DOM 的骨架屏自动生成方案,其核心是 evalDOM 函数。这个方案在很多场景下的表现还是令人满意的。不过,网页布局和样式组合的可能性太多,想要在各种场景下都获得理想的效果,还有很长的路要走,但既然已经在路上,就勇敢的向前吧!3 R: t2 R) R/ Y
+ p# W% ^0 k1 X! W! H' D2 b1 n5 L' w
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|paopaomj.COM ( 渝ICP备18007172号|渝公网安备50010502503914号 )

GMT+8, 2024-11-22 00:07

Powered by paopaomj X3.5 © 2016-2025 sitemap

快速回复 返回顶部 返回列表