乐趣区

关于javascript:还在手写骨架屏快来试试smartskeletonscreen吧

什么是骨架屏?

什么是骨架屏呢?骨架屏 (Skeleton Screen) 是指在页面数据加载实现前,先给用户展现出页面的大抵构造,在拿到接口数据后渲染出理论页面内容而后替换掉。Skeleton Screen 是风行很久的加载控件,实质上是界面加载过程中的过渡成果。

为什么咱们须要骨架屏

骨架页面是在页面真正解析和利用启动之前给用户展现页面的 CSS 款式和页面布局,并通过骨架页面的明暗变动,告知用户页面正在致力加载中,让用户感知页面仿佛加载得比以前快了。当利用启动、数据获取后,通过实在数据渲染的页面替换骨架页面。

  • 在骨架页面呈现之前,很多利用在实在数据获取之前,都是采纳 Loading 图标的模式通知用户数据正在加载,请期待,然而用户此时无奈感知行将出现的页面,也无奈确定期待的时长,千篇一律的 Loading 图标曾经让用户产生了审美疲劳,长时间的期待促使用户产生期待焦虑,依据 Google Research 的钻研显示,53% 的用户在期待加载 3s 后,抉择敞开 Web 页面或利用,导致用户散失。而骨架页面让用户感觉数据曾经加载好,只是还在渲染过程中,这也是为什么用户感觉页面加载得比之前快的起因所在。同时因为骨架页面和实在页面款式布局完全一致,在用户视觉感知上,骨架页面能够平滑的切换到实在数据渲染的页面。如果是通过 Loading 图标切换到最终页面,用户感知上会显得比拟突兀。
  • 纵览当下前端框架,未然是 React、Vue、Angular 三足鼎立之势,市面上大多数前端利用也都是基于这三个框架或库及相应生态圈实现的。这三大框架都有一个独特的特点,其都是 JS 驱动,在 JS 代码解析实现之前,页面不会展现任何内容,也就是所谓的白屏。用户是极其不喜爱看到白屏的,什么都没有展现,用户很有可能狐疑网络或者利用出了什么问题。拿 Vue 来说,在利用 bootstrap 时,Vue 会对组件中的 data 和 computed 中状态值通过 Object.defineProperty 办法转化成 set、get 拜访属性,以便对数据变动进行监听。而这一过程都是在启动利用时实现的,这也势必导致页面启动阶段比非 JS 驱动(比方 jQuery 利用)的页面要慢一些。

未应用骨架屏 VS 应用骨架屏

为什么咱们须要智能骨架屏工具

程序员都是懈怠的,没有哪个程序员违心反复去做一些雷同或者相似的工作。在应用智能骨架屏工具之前,咱们应用图片、svg 或者手动编写 HTML + CSS 骨架屏代码实现骨架屏成果,然而面对视觉设计的改版以及需要的更迭,咱们对骨架屏的跟进批改会十分被动,并且机械化反复劳作的形式此时未免显得有些灵活性有余

业界的解决方案

  • 百度 – vue-skeleton-webpack-plugin

    通过 vueSSR (vue 服务端渲染)联合 webpack 在构建时渲染写好的 vue 骨架屏组件,将预渲染生成的 DOM 节点和相干款式插入到最终输入的 html 中。

    计划的有余:

    1. 骨架屏组件代码和相干动画还须要开发者编写,不能做到真正的 no-code
    2. 该计划与 vue 相干技术间接关联,在当今前端框架三分天下的大环境下,咱们可能须要一个更加灵便的计划
  • 饿了么 – page-skeleton-webpack-plugin

    通过 puppeteer 在服务端操控 headless Chrome 关上开发中的须要生成骨架页面的页面,在期待页面加载渲染实现之后,在保留页面布局款式的前提下,通过对页面中元素进行删减或削减,对已有元素通过层叠款式进行笼罩,这样达到在不扭转页面布局下,暗藏图片、文字和图片的展示,通过款式笼罩,使得其展现为灰色块。并且将批改后的 HTML 和 CSS 款式提取进去,通过 webpack 插件的模式注入最初生成的 html 中,并且还能够启动 UI 界面专门调整骨架屏代码。

    计划的有余:

    1. 生成的骨架屏节点是基于页面自身的构造和款式,在某些嵌套比拟深的页面,骨架屏代码体积不会很小,并且对于多路由的页面,生成的代码就更加宏大了。
    2. 目前只反对 history 模式
    3. 插件获取 dom 构造依赖 puppeteer,在某些须要第三方依赖能力关上的页面,比方依赖浏览器 jsbridge 接口的页面,该插件无奈应用
  • 京东 – dps

    通过 Puppeteer 关上要生成骨架屏的页面,注入遍历 js 的代码,通过单纯的 DOM 操作,遍历 dom 树,pick 须要生成骨架屏的 dom 节点

    计划的有余:

    1. 插件获取 dom 构造依赖 puppeteer,在某些依赖浏览器 jsbridge 接口的页面,该插件无奈应用
    2. 当页面存在重定向(如果未登录重定向到登录页面)的状况时,生成的骨架屏可能与预期不统一
    3. 外部实现并不欠缺,可能导致某些结构复杂的页面下生成的骨架屏须要二次优化调整,工具也未提供 UI 界面给开发者微调

咱们的解决方案: smart-skeleton-screen

微视直播很多 H5 页面都依赖第三方资源(jsbridge 接口等),目前业界计划都无奈解决,并且在微视端内有很多半屏 H5,生成成果也不好,于是咱们研发了smart-skeleton-screen

整体架构图

如何获取正确 dom 构造

目前业界获取残缺的 dom 构造的办法根本能够总结为以下两个:

  1. 通过服务端渲染(SSR) 获取残缺 dom 构造
  2. 通过 puppeteer 等无头浏览器关上页面链接获取 dom 构造

以上两种形式都有一些缺点,在以下状况咱们不能获取正确的 dom:

  1. 强依赖第三方环境比方须要浏览器 jsbridge 接口 (直播内很多页面都依赖 jsbridge 接口返回的 room-id)
  2. 页面存在重定向,未登录重定向到登录页面 (某些 H5 页面是须要登录态的,尽管能够 puppeteer 能够设置 cookie,然而还是很麻烦)
  3. 须要配置域名代理的能力获取接口数据的页面 (本地谷歌浏览器通过 SwitchyOmega + whistle 能够任意配置域名,解决跨域问题,然而在 puppeteer 中是不行的)

最终做法

只有在实在的浏览器场景 (能够是 PC,也能够是挪动端) 下由开发者手动触发生成开关,就能够保障此时的 dom 构造是正确的。咱们只须要在我的项目构建阶段, 通过 webpack-pluginrollup-plugin构建工具插件 注入 js 脚本(遍历 dom 树和开关等代码)即可

如何生成骨架屏代码

要生成骨架屏代码,咱们首先面临的问题是 如何精确的剖析 dom 节点?

为了保障生成的骨架屏代码的大小,首先咱们须要 筛选节点,具体的规定如下:

  1. 只遍历首屏 可见的 dom 节点

  1. 只筛选 指标节点: 如:(背景)图片、文字、表单项、音频视频、Canvas、伪元素,自定义特色节点

  1. 筛选 白名单节点,删除黑名单节点 ,用户可在 dom 节点上增加 need-node(白名单节点标注) 和 unneed-node(节点)

节点曾经有了,是如何 生成节点代码?

在计划调研过程中, 京东 dps 为咱们的设计提供了灵感, 它的思维是: “厚此薄彼“ 生成相应区域的色彩块,不必管节点自身的层级,款式,元素类型,对立依据该区域与视口的相对间隔值生成 div 的色彩块, 并且间隔的单位为百分比,这样也能够兼容不同的挪动端设施, 那么具体如何做呢?

如何对生成的骨架屏代码进行调优

smart-skeleton-screen提供了一个可视化编辑器为骨架屏代码进行调优。

  1. 工具在生成骨架屏代码时会对页面进行截图,用户关上编辑器即可看到比照窗口

  1. 工具提供hot reload(热重载),用户在我的项目中批改骨架屏代码,编辑器监听到代码变动会主动进行刷新
  2. 用户可间接通过浏览器提供的 devtool 批改款式代码,点击保留之后款式代码会同步更新到我的项目中
  3. 编辑器上的骨架屏节点都可间接拖拽,并且双击点击即可呼出性能菜单(蕴含删除,复制节点,切换动画等性能)

  1. 反对切换多路有进行编辑

  1. 手机查看具体成果(须要和电脑的 wifi 为同一局域网)

如何将生成的骨架屏代码主动插入到 html 中

要想实现自动化生成骨架页面,还须要将下面的步骤和咱们的开发流程联合起来,通过 构建工具插件,webpack-plugin, rollup-plugin 咱们能够很轻松实现这一步,在适合的 webpack tapable 钩子中找到指标 html,通过正则多 replace 替换占位符即可,具体代码如下:

如何接入 smart-skeleton-screen

装置插件包 (依据我的项目构建工具抉择相干的插件包,以 webpack 为例)

tnpm install @tencent/smart-skeleton-screen

构建工具引入

const SmartSkeletonScreen = require('@tencent/smart-skeleton-screen').plugin;

new SmartSkeletonScreen({
   background: '#33333324',
   serverUrl: 'https://server.qq.com',
   port: 4001,
   pathname: path.join(__dirname, 'src/pages/fans/skeleton'),
}),
退出移动版