首页白屏的引发的思考(一)

栏目: Node.js · 发布时间: 6年前

内容简介:现在的前端框架,用户是极其不喜欢看到白屏的,什么都没有展示,用户很有可能怀疑网络或者应用出了什么问题。 拿 Vue 来说,在应用启动时,Vue 会对组件中的 data 和所以我们首页就是一个典型的案例,在一次我们前端周会上,我们老大问了我们一个问题,如何给用户啊一个更好的体验。

现在的前端框架, ReactVueAngular 三大巨头已经占据了主导地位,市面上大多数前端应用也都是基于这三个框架或库完成,这三个框架有一个共同的特点,都是 JS 驱动,在 JS 代码解析完成之前,页面不会展示任何内容,也就是所谓的白屏。

用户是极其不喜欢看到白屏的,什么都没有展示,用户很有可能怀疑网络或者应用出了什么问题。 拿 Vue 来说,在应用启动时,Vue 会对组件中的 data 和 computed 中状态值通过 Object.defineProperty 方法转化成 set、get 访问属性,以便对数据变化进行监听。 而这一过程都是在启动应用时完成的,这也势必导致页面启动阶段比非 JS 驱动(比如 jQuery 应用)的页面要慢一些。

所以我们首页就是一个典型的案例,在一次我们前端周会上,我们老大问了我们一个问题,如何给用户啊一个更好的体验。

SSR

这时候我第一个反应是,尽量不出现白屏的话,可以用VueSSR,就是服务端直出页面。

首先我们了解到,服务端渲染主要有两个目的,一是 SEO,二是加快内容展现。 在带来这两个好处的同时,我们也需要评估服务端渲染的成本,首先我们需要服务端的支持,因此涉及到了服务构建、部署等,同时 web 项目是一个流量较大的网站,也需要考虑服务器的负载,以及相应的缓存策略,特别像我们行业,由于地理位置的不同,不同用户看到的页面也是不一样的,也就是所谓的千人千面,这也为缓存造成了一定困难。

预渲染

所谓预渲染,就是在项目的构建过程中,通过一些渲染机制,比如 puppeteer 或则 jsdom 将页面在构建的过程中就渲染好,然后插入到 html 中,这样在页面启动之前首先看到的就是预渲染的页面了。

但是该方案最终也抛弃了,预渲染渲染的页面数据是在构建过程中就已经打包到了 html 中, 当真实访问页面的时候,真实数据可能已经和预渲染的数据有了很大的出入,而且预渲染的页面也是一个不可交互的页面,在页面没有启动之前,用户无法和预渲染的页面进行任何交互,预渲染页面中的数据反而会影响到用户获取真实的信息,当涉及到一些价格、金额、地理位置的地方甚至会导致用户做出一些错误的决定。

骨架图

骨架页面(Skeleton Page)指的是当你打开一个移动端 web 页面,在页面解析和数据加载之前,首先给用户展示页面的大概样式。在骨架页面中,图片、文字、图标都将通过灰色矩形块或圆形块来展示,在真实页面展示之前,用户能够感知到即将加载页面的基本 CSS 样式和页面布局。

骨架屏初体验

一开始在我脑子里,以为骨架屏是一个页面去手写一个css和Html,或者说是让ui去设计一个骨架图。但是这样是有缺点的,比如产品改需求了呢,不仅要去修改代码,还要去重新修改骨架页面或者骨架图?

后来,看到了饿了么大神的文章和很成熟的产品 page-skeleton-webpack-plugin ,瞬间明白了我和大佬的区别

首页白屏的引发的思考(一)

解析-饿了么骨架屏

生成骨架页面的基本方案

通过 puppeteer 在服务端操控 headless Chrome 打开开发中的需要生成骨架页面的页面,在等待页面加载渲染完成之后,在保留页面布局样式的前提下,通过对页面中元素进行删减或增添,对已有元素通过层叠样式进行覆盖,这样达到在不改变页面布局下,隐藏图片、文字和图片的展现,通过样式覆盖,使得其展示为灰色块。然后将修改后的 HTML 和 CSS 样式提取出来,这样就是骨架页面了。

在阐述具体生成骨架页面之前,先了解下 puppeteer, GitHub 上是这样介绍的。

首页白屏的引发的思考(一)

Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。

Puppeteer API 是分层次的,反映了浏览器结构。说实话,这是我第一次接触这个 Node 库,刚上手安装的时候就遇到了不少坑,哈哈哈哈哈哈,尴尬。

有想了解的同学请看 puppetter安装就踩坑-解决篇

骨架屏的开发基本上就是基于这个node库开始的。接下来我们来解析一下基础代码

skeleton.js

const puppeteer = require('puppeteer')
const devices = require('puppeteer/DeviceDescriptors') //puppeteer 提供了一些设备的参数选项
const { sleep , genScriptContent } = require('./util/utils') //公共 工具 方法
const scriptFns = require('./util/browserUtils')

const skeleton = async function(url, option = {}) {

  const defaultOption = {
    device: 'iPhone 6'
  }

  const { 
    device, 
    defer = 0, //延迟的时间
    remove = [], //页面想要移除的class类名数组
    excludes = [], //页面想要不包括的class类名数组
    hide= [],//页面想要隐藏的class类名数组
    launch: launchOpt
 } = Object.assign({}, defaultOption, option)

  // 当 Puppeteer 连接到一个 Chromium 实例的时候会通过 puppeteer.launch 或 puppeteer.connect 创建一个 Browser 对象。
  // 返回一个新的 [Page] 对象。[Page] 在一个默认的浏览器上下文中被创建。
  const browser = await puppeteer.launch(launchOpt) 

  const page = await browser.newPage() //新建一个页面

  /**
   * 根据指定的参数和 user agent 生成模拟器。此方法是和下面两个方法效果相同
   * @param { options }
   * viewport <[Object]>
        width <[number]> 页面的宽度,单位像素.
        height <[number]> 页面的高度,单位像素.
        deviceScaleFactor <[number]> 定义设备缩放, (类似于 dpr). 默认 1。
        isMobile <[boolean]> 要不要包含meta viewport 标签. 默认 false。
        hasTouch<[boolean]> 指定终端是否支持触摸。默认 false
        isLandscape <[boolean]> 指定终端是不是 landscape 模式. 默认 false。
      userAgent <[string]>
   * 
   */
  await page.emulate(devices[device])
  
  await page.goto(url)

  // 将一些 utils 插入到打开的页面执行环境中,这里会引入如何判断图片,文字的方法,将他们覆盖成灰色,也是骨架图中必不可缺的代买
  await page.addScriptTag({
    content: genScriptContent(...scriptFns)
  })

/**
还应注意一点,defer 配置,用于告诉 Puppeteer 打开页面后需等待的时间,这是因为,在打开开发中页面后,页面中有些内容还未真正加载完成,如果在这之前进行骨架页面生成,很有可能导致最终生成的骨架页面和真实页面不符。使得生成骨架页面失败。
**/
  await sleep(defer)
/**
 * page.evaluate(pageFunction, ...args)
 * pageFunction <[function]|[string]> 要在页面实例上下文中执行的方法
    ...args <...[Serializable]|[JSHandle]> 要传给 pageFunction 的参数
    返回: <[Promise]<[Serializable]>> pageFunction执行的结果
 */
  const html = await page.evaluate(async ( remove, excludes, hide ) => { 
    const $ = document.querySelectorAll.bind(document)

    if (remove.length) { 
      const removeEle = $(remove.join(','))
      Array.from(removeEle).forEach(ele => ele.parentNode.removeChild(ele))
    }

    if (hide.length) {
      const hideEle = $(hide.join(','))
      Array.from(hideEle).forEach(ele => ele.style.opacity = 0)
    }

    const excludesEle = excludes.length ? Array.from($(excludes.join(','))) : []

    await traverse(document.documentElement, excludesEle)

    return document.documentElement.outerHTML

  }, remove, excludes,hide)

  // browser.close()

  return { html }
}

module.exports = skeleton
复制代码

下一篇,我们会认真的去分析,饿了么骨架屏中是如何去将页面根据不同元素分成不同的块:文本、图片块,SVG块,伪元素块、按钮块 将元素区分为不同块后,下一步就是对这些块分别进行处理,包括元素的增减和样式的覆盖,目的只有一个,就是将这些块转化为骨架页面的样式。


以上所述就是小编给大家介绍的《首页白屏的引发的思考(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Solve It

How to Solve It

Zbigniew Michalewicz、David B. Fogel / Springer / 2004-03-01 / USD 59.95

This book is the only source that provides comprehensive, current, and detailed information on problem solving using modern heuristics. It covers classic methods of optimization, including dynamic pro......一起来看看 《How to Solve It》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具