Giter VIP home page Giter VIP logo

share's People

Contributors

wengjq avatar

Stargazers

 avatar

Watchers

 avatar  avatar

share's Issues

DOM 最佳实践

1、大纲

image

2、从浏览器渲染原理说起

3、WebKit 主流程

image

  • 解析 HTML (HTML Parser)
  • 构建 DOM 树 (DOM Tree)
  • 构建 CSSOM 树 (Style)
  • 渲染树构建 (Render Tree)
  • 布局 (Layout)
  • 绘制渲染树 (Painting)

渲染流程(render.html):

image

4、文档对象模型 (DOM)

image

  • 转换
  • 令牌化
  • 词法分析
  • DOM 构建

5、CSS 对象模型 (CSSOM)

image

image

6、渲染树构建 (Render Tree)

image

7、布局 (Layout)

  • 布局,也可以称重排或者回流
  • 布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸
  • HTML 采用基于流的布局模型,处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档

8、更新“层”(Update Layer)

层的渲染:

  • 获取 DOM 并将其分割为多个层(包括渲染层和合成层)
  • 将每个层栅格化
  • 复合多个层来生成最终的屏幕图像

9、绘制(Paint)

  • 绘制当前层(Layer)
  • 一个 Layer,Paint 一次

问题:如何生成多个 Layer ?(layer.html

image

一层 Layer VS 两层 Layer

image

10、渲染层

渲染层:

一般来说,拥有相同的坐标空间的 节点 属于同一个渲染层。渲染层最初是用来实现层叠上下文,以此来保证页面元素以正确的顺序合成(composite),这样才能正确的展示元素的重叠以及半透明元素等

创造渲染层的条件:

  • 根元素(HTML)
  • 有明确的定位属性(relative、fixed、sticky、absolute)
  • 透明的(opacity 小于 1)
  • 有 CSS 滤镜(fliter)
  • 有 CSS mask 属性
  • 当前有对于 opacity、transform、fliter、backdrop-filter 应用动画
  • overflow 不为 visible
  • ...

11、合成层

合成层:

特殊的渲染层。合成层是单独的”层”,“层”的上下文会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后画到屏幕

注意:

先是渲染层,然后满足一些条件可以提升成合成层

12、渲染层提升合成层

直接原因:

13、渲染层提升合成层

后代元素原因:

  • 有合成层后代同时本身有 transform、opactiy(小于 1)、mask、fliter、reflection 属性(combo.html
  • 有合成层后代同时本身 overflow 不为 visible(overflow.html
  • 有合成层后代同时本身 fixed 定位(fixed.html
  • 有 3D transform 的合成层后代同时本身有 preserves-3d 属性(preserves-3d.html
  • 有 3D transform 的合成层后代同时本身有 perspective 属性(prespesctive.html

重叠原因:

  • 元素的 border box(content + padding + border) 和合成层的有重叠(b-overlay.html),margin 的重叠无效(m-overlay.html
  • 动画运行期间,元素可能和其他元素有重叠(a-overlay.html

14、层压缩

如果多个渲染层同一个合成层重叠时,这些渲染层会被压缩到一个合成层中,以防止由于重叠原因导致可能出现的“层爆炸”(layer-squash.html

不能压缩的情况:

  • 当渲染层同合成层有不同的裁剪容器,合成层在一个 overflow: hidden 的容器中(no-layer-squash.html
  • ...

15、Repaints and Reflows

Repaint:

可以理解为重绘或重画,当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,例如改变背景颜色 。则就叫称为重绘。

Reflows:

可以理解为回流、布局或者重排,当渲染树中的一部分(或全部)因为元素的尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow),也就是重新布局(relayout)。

16、回流或者重绘何时触发?

  • 添加,删除,更新DOM节点。
  • 用display: none(回流和重绘)或者visibility: hidden隐藏节点(只有重绘,因为没有几何更改)。
  • 添加样式表,调整样式属性。(csstrigger)
  • 调整窗口大小,更改字体大小。
  • 页面初始化的渲染。
  • 移动DOM元素。
  • ...

image

17、回流或者重绘带来的影响

回流的代价比重绘的代价高很多,重绘会影响部分的元素,而回流则有可能影响全部的元素。反复执行回流,会页面渲染效率变低,甚至造成页面“卡顿”。(animation.html

18、进入正题,DOM操作最佳实践

19、让DOM元素脱离渲染树后修改

  • 使用文档片段

image

  • 通过设置DOM元素的display样式为none来隐藏元素

image

  • 克隆DOM元素到内存中

image

20、使用局部变量缓存样式信息

image

21、合并多次的 DOM 操作

image

注意:

有些浏览器对于频繁的 DOM 操作会设置脚本所需更改的队列,并分批执行。每个需要 Reflows 的几个变化将被组合,并且将仅计算一个 Reflows。

22、脚本请求会阻止批量更改

image

虽然浏览器会有合并 Reflow 的优化,但有时脚本可能会阻止浏览器优化 Reflows,并使其刷新队列并执行所有批量更改。当您请求如下样式信息时(并非包含全部),会发生这种情况(reflow.html

23、渲染过程对比

image

vs

image

24、整体耗时对比

image

vs

image

25、提升为合成层

提升为合成层优点:

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当需要 repaint 或者 reflow 时,只需要 repaint 或者 reflow 本身,不会影响到其他的层([no-
    reduce.html](https://wengjq.github.io/GithubPage/Blog-Demo/no-
    reduce.html))
  • 用 transform 和 opacity 实现的动画效果,不会触发 layout 和 paint (transform.htmlopacity.html

26、合理管理合成层

  • 提升合成层会达到更好的性能。这看上去非常诱人,但是问题是,创建一个新的合成层并不是免费的,-它得消耗额外的内存和管理资源(has-layers.htmlno-layers.html
  • 防止层爆炸(layer-explode.html

27、最后一个例子

演示查浏览器的”卡顿“问题及定位(animation.html , animation-optimize.html

28、问答

Q&A

基于 vue 的页面无埋点方案

jz 现有的前端埋点统计实现

现有的页面统计基本都是在页面具体的 dom 节点处插入 js 代码执行,如 Mobi.logDog(xxx, xxx); 等。

jz 现有的前端埋点统计实现缺点

由于没有好的规范,有的直接是在 dom0 级事件上写,有的在 js 绑定点击事件执行,有的在 vue 实例的方法写,这种埋点方式分布散乱,不利于维护和统计。

Vue.mixins 功能介绍

混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。

例子:

// 同名钩子函数将混合为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
  created: function () {
    console.log('混入对象的钩子被调用')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('组件钩子被调用')
  }
});

// => "混入对象的钩子被调用"
// => "组件钩子被调用"
// 值为对象的选项,例如 methods, components 和 directives,将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

var vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () {
      console.log('from self')
    }
  }
})

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

基于 Vue 的页面无埋点方案实现

(1)利用 Vue.mixin 的 mounted 钩子收集组件或者实例的上下文。

var captrueContexts = [];

Vue.mixin({
  mounted: function() {
    var _this = this;

    captrueContexts.push(this);

    this.$nextTick(function() {
      delegateBehavior(_this);
    });
  }
});

(2)在每个渲染完成后我们把当前实例的 context 传进我们要进行捕获事件的函数中。

function delegateBehavior(context) {
  var _this = context;
  var eventTypes = ['click', 'change']; // 绑定事件数组

  // 实例根元素绑定事件
  // 每个 vue 实例的 $root 代表它的根节点,_isBindDelegate 判断是否绑定过事件
  if (context.$root.$el && !context.$root.$el._isBindDelegate) {
    eventTypes.forEach(function (eventType) {
      context.$root.$el.addEventListener(eventType, function(e) {
        var $target = $(e.target);

        _this.captrue(e, captrueContexts, eventType);
      }, true); // addEventListener 的第三个参数使用 true ,使用捕获,而不是冒泡
    });

    context.$root.$el._isBindDelegate = true;
  }
}

(3)拿到所有 contexts 的 $el,通过 target 的 node 节点和 vue 的 contexts 里的 $el 进行比较,因为 contexts 的 $el 都是 component 的根节点,不一定会匹配到 target ,所以要从 target一直往上找到他所属的 component ,目的是为了找到这 target 属于哪个context,主要是拿到那个 index ,从而找到所属组件。

methods: {
  captrue: function(e, captrueContexts, eventType) {
    var els = captrueContexts.map(function(context) {
      return context.$el;
    });

    var currentEl = e.target;

    while (currentEl) {
      var index = els.indexOf(currentEl);

      if (index > -1) {
        // 触发事件
        this.$emit('logreport', e, captrueContexts, index, eventType);
        break;
      }

      currentEl = currentEl.parentNode;
    }
  }
}

(4)绑定 logreport 事件,拼装 log 逻辑。

this.$on('logreport', function (e, captrueContexts, index, eventType) {
  var comp = captrueContexts[index]; // 获取当前组件
  var elm = comp._vnode.elm; // 获取组件元素

  // 拼装 log 逻辑...
});  

基于 Vue 的页面无埋点方案实现好处

  • 可以统计页面的多个 Vue 实例,包括后面新生成的 Vue 实例。
  • 基于事件捕获的监听可以解决 @click.stop 阻止冒泡的问题。
  • 页面无侵入,易于维护。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.