Composite Layer & Vue

看过浏览器渲染原理的同学应该可能对composite layer这个名词有所耳闻,有些地方翻译为渲染层。按照Chrome的渲染模式,浏览器将dom按照一定规则分成数个渲染层,分别将每一层绘制为位图后交由GPU处理,最终混合成显示屏上的页面。

一般来说前端页面的变化反应到浏览器显示的流程是:

js scripting —> style —> layout —> paint —> composit

最为理想、性能最佳的方式是略过重排(relayout)和重绘(repaint),直接合并渲染层(compositing layers)

如果一个渲染层只发生了compositing属性的改变,其实只有两种属性——transformopacity,那么就会走上面的最为理想的步骤,GPU重新混合渲染层就能简单地得到新的帧。

反之,在其他情况下,浏览器可能要重新处理渲染层的划分、布局重排、样式重绘,最后让GPU合并渲染层绘制出新的页面帧。

传统的PC Web页面开发规范往往会推荐开发者使用一些css hack让有动画效果的页面元素能被提升到一个独立的渲染层中,获得GPU的硬件加速,比如will-change属性、transform: translateZ(0);等其他涉及3D运算的CSS

但是在移动端页面中,反倒是要避免过多的渲染层创建,过多的渲染层带来过多的内存占用和GPU开销,增加耗电量,最重要的是影响页面性能甚至导致页面不可用 (微信页面尤其如此,由于对webview内存占用设限,可能会带来白屏或者闪退)

在dev tool timeline中可以清楚地看到渲染层的结构以及创建原因、消耗的内存等。

渲染层创建条件

  • 3D或透视相关的CSS属性
  • 使用视频加速编码的<video>标签
  • 使用3D或者加速的2D上下文的<canvas>标签
  • 混合插件 Flash等
  • 使用涉及透明度或transform相关的CSS动画的元素
  • 使用经过CSS Filter的元素
  • 子元素拥有独立渲染层的元素
  • 兄弟元素拥有独立渲染层并覆盖在其上面的元素

所有能涉及GPU加速的因素一定能创建渲染层;所有渲染层的DOM祖宗也是渲染层;妨碍兄弟出人头地的元素他当然也是渲染层……

Vue

最近在重构部门的商城端项目,原先是FTL的后端渲染配合zepto的刀耕火种。其实对目前的移动端页面开发来说,zepto配合前端模板以及少量的后端直出还是挺多优势,项目的打包结果会比较小,配合一些缓存技术能让首屏有不错的表现。可是,每一个使用Freemarker的前端都是折翼的天使,更主要是目前项目开发得不太规范,导致代码可维护性非常差,影响了开发效率。

将后台系统 (曾经也是jq= =、感觉这个部门原先是不是没有前端…) 转到ng后,终于有空处理商城端的问题。选型直接上了vue2以及三件套,项目结构是稍微改造的vue-cli webpack版。记一些目前的踩坑经验和想法。

tap 怎么办

使用Fastclick或者使用自定义的指令,前者的好处是可以在<template>中使用@click,兼容PC端的写法;后者会有更好的定制性,不过还是偷懒的选择了前者。

使用 vue 有多重

原先的项目首页 50kb js + 10kb css + 12kb html; 改基于vue的SPA后,仅vue和其三件套 vue-router / vuex / vue-resource 外加 Fastclick 等库文件js打包而成的就有140kb + 20kb css + 60kb 页面渲染及业务逻辑js,html忽略不计。

看起来是重了不少,不过库文件由于很少会去升级,可以选择 localStorage 的缓存,剩下的也就很可观了。这还是在不使用 require.ensure 语法开启 webpack 的懒加载情况,可以按 vue-rouer 官网的文档,使用如下方式引入页面级组件,避免一次性加载所有页面的代码:

1
const HomePage = resolve => require(['@page/HomePage'], resolve)

但是这有个副作用,就是懒加载后所有页面渲染及业务逻辑js的总和会更大不少,因为 webpack 按照懒加载打包后,如果几个页面共用了同一个组件的代码,它们会重复打包这些组件= =、

vue 目前的较佳实践

所有页面的顶层应该是一个“页面级组件”,并且和传统的“积木组件“分开维护

可以明确页面

所有页面级组件最外层可以套一个页面原型组件 (积木组件)

在这个页面原型组件.vue 中做一些各个页面都需要的事情:重置浏览器默认样式、加载全局的样式库(类、动画、字体等)、页面的初始布局、数据的初始化和前后端数据的同步、slot挖坑

一个积木组件如果在不同页面上有不同的样式、轻微布局变化或是逻辑变化,则新建一个以其名字命名的文件夹,里面维护公用的逻辑即mixin.js文件以及其他以形容词命名的.vue文件

方便组件的维护,具体的页面中可能采用形如 import ListItem from '@components/ListItem/Normal' 的方式使用所需的组件。

dom 相关操作使用指令解决 (重用性高的情况下)

不多说、vue 相比 ng 优秀的一点就在于明确了 directive 的作用

所有的数据操作最好经过 vuex action,由 action 组织 commit 操作

方便维护跟踪数据变化

只在 action 中使用 vue-resource

方便维护数据请求

页面数据初始化 (需要后端时),按需要在页面级或者页面原型组件的 beforeCreatedispatch 请求

在生命周期中 beforeCreate 是最先执行的,并且能获取到 this.$store

可以使用 vuex 的 store 避免单向数据流中,子组件操作数据需要使用 props 里的回调函数一层层向上修改父组件的麻烦写法

换句话说就是避免过度使用 data 存储组件数据

对 store 上对象的修改使用 ES6 解构语法

可以看下源码的 getter / setter 机制就能明白了,官网文档中对 data 上属性的操作说明也是这个原因

vue-router 在未开启 html5 history 模式时使用动态路由会导致路由变化时,路由参数变化无法被监听

需要使用 watch 属性,定义对 $router 的监听函数,官网有对这个的说明

在写自定义 Directives 时可以尝试写一个工厂,每次调用时返回一个经过配置的 Directive,传给对应的积木组件。此外,使用局部组件会比较好。

总归目前用 Vue 用的还是很舒服的~