骨架屏自动生成方案实践

722 阅读4分钟

因为自己最近在做骨架屏生成脚本,也参考了业界的一些方案,总结并优化了一下,并加入到项目开发流程中。 接下来先给一张图,让实现思路清晰明了。

image.png

1、打开url页面

对于无头浏览器打开url页面有两种方案:

  1. 直接安装puppeteer包,因其内置自带谷歌浏览器,体积大。
  2. 安装puppeteer-core核心包,无内置浏览器,需求手动输入浏览器应用位置。

这里根据自己的业务去适用方案,比如是后台管理系统,可以使用第一种比较简单方便。 h5建议采用第二种,但前提是有安装谷歌浏览器,相信大部分开发人员已经装了, 对于需要手动输入浏览器位置的问题我们使用carlo包的findChrome去自动寻找帮我们解决。

2、生成骨架屏代码

那么首先我们要知道生成规则,什么是生成规则呢,简单来讲就是要依据页面哪些元素去生成骨架屏样式盒子。 比如根据某些元素标签例如img, video等之类的比较特殊的标签。
编写js生成脚本,在页面的控制台下运行,根据自己所配置的规则去生成样式代码,使用puppeteer中page实例的,evaluate 方法。贴一下puppeteer文档链接 puppeteer.bootcss.com

根据适合项目首页的规则去生成适合我们项目的骨架屏代码,比如页面的上文本元素、媒体元素等,或者一些特殊的元素自定义处理等,提供配置化的方式给使用者去灵活使用。

详细的代码处理过程就不一一赘述了,大概的处理流程如下:

  1. 遍历页面所有元素需要生成骨架屏代码的元素进行处理

  2. 获取元素位置,大小信息

  3. 判断元素是否处于可视区域内(超出屏幕?被遮挡?样式性隐藏?)

  4. 根据元素信息生成元素及样式代码

3、代码注入html

将样式代码注入到index.html:这里一开始考虑用的是html-webpack-plugin的hooks去实现,但没实践出来,目前用的是cheerio,后续实践出来再做更新。

有了骨架屏代码后,我们就可以将代码注入到html里啦,这里我们使用cheerio包来操作html文件,将生成的骨架屏代码注入到html对应的元素下面,如vue项目中html下的#app元素。

4、包装成plugin

我们将骨架屏生成脚本包装成webpack plugin,在webpack.dev.config中使用,建议输入url参数时,带上页面所需地址参数,项目启动后可利用开启的服务去异步生成骨架屏代码并注入index.html,可正常访问项目,无须等待。

5、伪代码实现

咱们来简单的写下伪代码把主要流程给串起来:

// 首先打开URL页面
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
// 此时我们就拿到了url的页面实例,调用evaluate执行我们的js脚本
const html = await page.evaluate(handle, options);
// handle为处理页面的js方法(下面介绍),this指向window,返回生成的骨架屏html代码。

const fs = require('fs');
const cheerio = require('cheerio');
const fileHTML = fs.readFileSync('./index.html');
const $ = cheerio.load(fileHTML);
// 将html插入
$(#app).html(html);
// 写回index.html
fs.writeFileSync(this.filepath, $.html());

// handle
function handle() {
    ...遍历document.body下的元素
    ...拿到node.tagName === 'IMG' 标签元素
    ...通过node.getBoundingClientRect()获取坐标位置以及大小信息
    ...判断元素是否在可视范围
    ...生成对应元素及样式代码
    return `
        <style>
        .sk_box {
            position: absolute;
            background-color: #EEEEEE;
        }
        </style>
        <div class="sk_box" style="width: 100px;height: 100px;top: 100px;left: 100px;"></div>
    `;
}

大概的流程代码就是这样啦,当然实际实现的话那就要复杂的多了,还需要考虑配置化的问题,给使用者能自定义一些配置,毕竟有的页面元素布局比较特殊。

把上面代码包成一个方法后带上配置参数,就可以提供给外部使用啦,顺便贴下webpackplugin代码:

const Skeleton = require('./index');
const once = (fn) => {
    let called = false;
    return () => {
      if (called) return;
      called = true;
      fn();
    };
};
class skeletonPlugin {
    constructor(options = {}) {
        this.options = options;
    }
    apply(compiler) {
        const that = this;
        compiler.hooks.done.tap('skeletonPlugin', once(() => {
            setTimeout(() => {
                new Skeleton(that.options).start();
            });
        }));
    }
}
module.exports = skeletonPlugin;

webpack.dev使用

new SkeletonPlugin({ url: 'http://localhost:8080' }),

plugin是在开发模式下去使用,启动项目的时候去自动生成骨架屏,但是理想情况下是cicd的时候在打包后去自动生成。不过前提是服务器安装了谷歌浏览器,但是卡在了linux服务器打不开无头浏览器puppeteer,貌似版本不太一样; T_T