xml地图|网站地图|网站标签 [设为首页] [加入收藏]
高性能滚动,该何去何从
分类:web前端

什么是BFC

2016/01/09 · CSS · 2 评论 · BFC

原文出处: 大搜车前端团队博客   

这是我10个月前写的一篇关于BFC的文章,因为接下来要写一篇关于FFC的文章了,然而很多人对BFC都还不了解,所以我先把这篇文章拿出来让大家先了解下BFC。

说到BFC,就会涉及到CSS布局的一些基本概念,然后,首先来讲一下关于块级盒的一些概念:

下面是来自w3.org中的一句话(参考链接:http://www.w3.org/TR/CSS2/visuren.html#block-formatting):%EF%BC%9A)

Block-level elements are those elements of the source document that are formatted visually as blocks (e.g., paragraphs). The following values of the ‘display’ property make an element block-level: ‘block’, ‘list-item’, and ‘table’.

大概的意思是块级元素是那种源文档被格式化为可视块了的元素,然后使这个元素变成块级元素的display属性取值如下: ‘block’, ‘list-item’, 和 ‘table’。

然后还有一段话:

Block-level boxes are boxes that participate in a block formatting context. Each block-level element generates a principal block-level box that contains descendant boxes and generated content and is also the box involved in any positioning scheme

块级盒block-level box是这种参与了块级排版上下文的一种盒子,每个块级元素都生成了一个包含后代盒子和生成的内容的主要块级盒,并且这个盒子参与了任何定位的计算

来自w3c官网的一张图:

图片 1

块级元素会自动生成一个块级盒block-level box,这是块级盒block-level-box的盒模型构成,它表明的是块级盒自身的结构构成。再来看w3.org的这样一段话( 参考:http://www.w3.org/TR/CSS2/box.html):%EF%BC%9A)

content edge or inner edge
The content edge surrounds the rectangle given by the width and height of the box, which often depend on the element’s rendered content. The four content edges define the box’s content box.
padding edge
The padding edge surrounds the box padding. If the padding has 0 width, the padding edge is the same as the content edge. The four padding edges define the box’s padding box.
border edge
The border edge surrounds the box’s border. If the border has 0 width, the border edge is the same as the padding edge. The four border edges define the box’s border box.
margin edge or outer edge
The margin edge surrounds the box margin. If the margin has 0 width, the margin edge is the same as the border edge. The four margin edges define the box’s margin box.

从上面这段话可以看出,margin、border、padding、content分别定义了元素四种边,然后每种类型的边的四条边定义了一个盒子,分别是content box、padding box、border box、margin box,而决定块盒在包含块中与相邻块盒的垂直间距的便是margin-box,这个margin-box是始终存在的,即使它的margin为0,试看下面的代码:

XHTML

<div style="width: 400px;height: 400px; background:#ff4400;"></div>

1
<div style="width: 400px;height: 400px; background:#ff4400;"></div>

生成了一个块级的元素,同时元素也生成了一个块级盒:

图片 2

此时我没有为这个div设置margin值,但是这个div还是有一个默认为0的margin的,试看下面的浏览器生成的computed style:

图片 3

可以看到div默认的margin为0,再看看w3.org的相关文档(参考:http://www.w3.org/TR/WD-CSS2/visudet.html):%EF%BC%9A)

图片 4

上面说了元素的四个margin的默认值都是0(见红色部分),然后应用对象是所有的元素(见蓝色部分),所有元素当然包括块级元素,因此所有的块级盒子无论怎样都会有一个margin-box,在BFC中,margin-box会与其相邻的margin-box的对边相折叠(关于BFC的margin折叠这里先不讨论)。margin-box是参与块级盒在上下文中的布局的,但是参与BFC布局的盒子却是块级盒block-level box,并且还有一点需要明确的是参与布局的是盒子而不是元素本身。下面这张图是我自己理解的块级盒block-level box的构成:

图片 5

那么block-level box在页面中是怎样布局的呢。

我们通常的描述是这样的:

普通流中的块元素独占一行,然后从上往下一个接一个的排布,相邻元素间会有外边距折叠

确实是这样(NOTE:这个说法也非绝对严谨,试看DEMO:,那为什么会这样呢,原因就是因为BFC的存在。试看下面的关于BFC的官方文档描述(参考:http://www.w3.org/TR/CSS21/visuren.html):%EF%BC%9A)

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with ‘overflow’ other than ‘visible’ (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. The vertical distance between two sibling boxes is determined by the ‘margin’ properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.

In a block formatting context, each box’s left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch). This is true even in the presence of floats (although a box’s line boxes may shrink due to the floats), unless the box establishes a new block formatting context (in which case the box itself may become narrower due to the floats).

大致意思如下: 第一段:浮动元素、绝对定位元素,不是块级盒的块级包含块(比如inline-block、table-cell、table-capation)和overflow值不为visible的块级盒子为它们的内容建立了一个新的块级排版上下文。

第二段:在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的,相邻两个盒子之间的垂直的间距是被margin属性所决定的,在一个块级排版上下文中相邻的两个块级盒之间的垂直margin是折叠的。参与BFC的布局的只有普通流normal flow中的块级盒,而float、position值不为relativestatic的元素是脱离BFC这一布局环境的,不参与BFC的布局

第三段:在一个块级排版上下文中,每个盒子的左外边是触碰到包含块的左边的(对于从右向左的排版,则相反),即使在有浮动元素参与的情况下也是如此(即使一个盒子的行盒是因为浮动而收缩了的),除非这个盒子新建了一个块级排版上下文(在某些情况下这个盒子自身会因为floats而变窄)。

先不解释第一段所描述的内容,先看第二段和第三段话,然后我们再来看一个很常见的实例:   代码如下:

XHTML

<div style="width: 200px;height: 200px;background: #cccccc;"></div> <div style="width: 200px;height: 200px;background: #333333;"></div> <div style="width: 200px;height: 200px;background: #666666;"></div>

1
2
3
<div style="width: 200px;height: 200px;background: #cccccc;"></div>
<div style="width: 200px;height: 200px;background: #333333;"></div>
<div style="width: 200px;height: 200px;background: #666666;"></div>

结果如下:图片 6

三个块级盒,从上往下排列,看样子是遵循如上面第二段所述的这一条BFC布局规则的:“在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的”,那么这里的盒子要从上往下排列的话,肯定是得要一个包含块containing block和一个块级排版上下文BFC的,:root元素是会在其下创建一个BFC的,那么在这个BFC下的所有块级盒都是会在它的包含块中垂直的一个接一个的排布的。但是元素的包含块是什么呢,试看来自w3.org的一段话(参考:http://www.w3.org/TR/CSS2/visudet.html):%EF%BC%9A)

The position and size of an element’s box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element:

意思是:元素盒子的位置和尺寸往往是根据一个矩形计算出来的,我们称这个矩形为元素的包含块 然后它下面还有一句:

if the element’s position is ‘relative’ or ‘static’, the containing block is formed by the content edge of the nearest block container ancestor box.

意思是:如果这个元素的position值是relative或static,这个元素的包含块是由离其最近的块级的祖先盒子的内容的边content-edge构成的。就是离其最近的块级祖先盒子的content-box。

搞清了包含块containing block的概念,再来看看这个实例中的3个div的包含块,很显然是其父级元素body,同时还在canvas所设立的BFC下,按照“在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的”的这个定义,我们已经确定了,这里的div是会垂直的一个接一个的排列的,但是你要注意到,第二段话只是定义了垂直方向的排布规则,还没说水平方向的,那么水平方向的又如何呢,试看第三段话的前两句:“在一个块级排版上下文中,每个盒子的左外边是触碰到包含块的左边的(对于从右向左的排版,则相反)”,而在这里,我尚未为其定义从右向左的排版(对于从右向左的排版,参考:,所以这个div的左外边是会触碰到包含块body的左边的。

本文只讨论什么是BFC,因此上面那段话中的某些原文就不一 一解释了。

再来看上面那段话第二段中的一句话:“相邻两个盒子之间的垂直的间距是被margin属性所决定的,在一个块级排版上下文中相邻的两个块级盒之间的垂直margin是折叠的”,这是参与BFC布局的块级盒的又一特性,试看以下实例:

代码:

XHTML

<div style="width: 200px;height: 200px;background: #cccccc;margin-bottom: 30px;"></div> <div style="width: 200px;height: 200px;background: #333333;margin-top: 30px;margin-bottom:30px;"> </div> <div style="width: 200px;height: 200px;background: #666666;margin-top: 50px;"></div>

1
2
3
<div style="width: 200px;height: 200px;background: #cccccc;margin-bottom: 30px;"></div>
<div style="width: 200px;height: 200px;background: #333333;margin-top: 30px;margin-bottom:30px;">   </div>
<div style="width: 200px;height: 200px;background: #666666;margin-top: 50px;"></div>

如图:

图片 7

可以看到上下两个div的margin折叠了,第一个div的margin-bottom和第二个div的margin-top折叠为了30px,第二个div的margin-bottom和第三个div的margin-top折叠为了50px,而这个折叠本质是块级盒block-level box下的margin-box的折叠,后面会讲到如果再到这几个div的外面一层再包裹一个拥有BFC的元素的话,他们之间的margin便不会折叠了,因为BFC里面的盒子和其外面的盒子间是不会有任何影响的,你可能会疑惑那这里的三个div不是也在:root所设立的BFC下吗,那为什么还会折叠,原因很简单,就是因为你那个BFC在:root那里去了,BFC相当于一堵墙,你这个墙在这里应该在每个div的外面才会起到隔离这几个div的作用啊,而:root下的那个BFC则是隔离的:root下的直接子元素了。关于margin折叠方面的还有很多细节,可以参考官方文档:

至此,我们就搞清楚普通流中的块级盒的一些基本特性的来龙去脉了。

上面说的都是:root下的BFC。那么,假设你在不知道比如float的这些特性能用于创建BFC的时候,你会不会很好奇的去想:root下的盒子会不会也有可以用于创建BFC的方法与对应的盒子呢?上下文套上下文可是一件很令人感到愉悦的事情,因为我可以把那个能创建BFC的盒子当作那个:root,而这个创建了BFC的盒子则是一个独立的容器,里面的参与BFC的块级盒不会影响到盒子外面的盒子,外面的盒子也不会影响到里面参与了BFC的块级盒。试看以下demo:  :; :;代码如下:

XHTML

<div style="overflow: auto;width: 100%;"> <div style="float: left;width: 100%;"> <div style="margin-bottom: 30px;width: 100px;height: 100px;background-color: #cccccc;"></div> </div> <div style="float: left;width: 100%;"> <div style="margin-top: 50px;width: 100px;height: 100px;background-color: #333333;"></div> </div> </div>

1
2
3
4
5
6
7
8
<div style="overflow: auto;width: 100%;">
    <div style="float: left;width: 100%;">
        <div style="margin-bottom: 30px;width: 100px;height: 100px;background-color: #cccccc;"></div>
    </div>
    <div style="float: left;width: 100%;">
        <div style="margin-top: 50px;width: 100px;height: 100px;background-color: #333333;"></div>
    </div>
</div>

 :; :;结果如下:

图片 8

可以看到,上面示例中的上面灰色div和下面黑色div的margin并没有重叠,这是因为那两个float的父盒子在为它下面的盒子创建了一个BFC,从而将float盒子里面的子盒子给隔离了起来,因此也就不会margin折叠了,这只是创建BFC的一个方法,其它的还有overflow:hidden等,而在这个BFC下的盒子都是遵循BFC的布局规则的。

然后我们来从盒模型的角度来理解下,BFC是如何将其下的盒子与外界隔离起来的,首先,最基本的盒子构成我们上面已经说过了,见上面的描述block-level box的构成的那张图:

然后,当块级盒block-level box外层没有BFC作保护,有margin折叠时,是这样的:

图片 9

而当块级盒block-level box外层有BFC作保护时(比如给下图灰色边框线盒子一个float:left),则是这样的:

图片 10

同时BFC下的盒子是按照BFC的规则从上往下一个接一个的排列,并且存在外边距折叠的,你可以通过在这层BFC下再去嵌套BFC来阻止下面盒子的外边距折叠

图片 11

最后,我们通过一张图来了解一个页面中的BFC的构成(有红色虚线的代表的是拥有BFC的元素):

图片 12

BFC描述的是页面中的,严格来说是页面的:root下的一个布局上下文,它下面的盒子遵循BFC中的布局规则,它是描述的是块级盒的布局规则,那么行盒,行级盒等盒子又是遵循什么样的布局规则的呢?这里就不细讲了,可以看看winter老师的两张图,有助于对BFC的理解,也有助于对CSS布局的理解:

图片 13

图片 14

最后要说的就是,本文只对BFC作了一个初步的介绍,如果要了解更多的细节,可以参考以下相关标准:



1 赞 15 收藏 2 评论

图片 15

前端框架这么多,该何去何从?

2017/08/05 · 基础技术 · 2 评论 · 前端, 框架

本文作者: 伯乐在线 - ThoughtWorks 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

作为一个软件开发者,最大的挑战就是在不断涌现的新技术中进行取舍,持续学习是从事这一行业的必备技能。在这个领域里,技术更新最快地又非前端莫属了。各种框架的出现、版本的更新此起彼伏,呈现出一派欣欣向荣之景。

在项目中必不可少的便是前端,它是系统的门面、是用户对系统最直接的体验,颜值高低也是决定系统好坏的关键,那么作为一名软件开发攻城狮,怎么能放弃这片蓝海呢?没准了解了解前端的世界,就能多一项技能、升职加薪、赢取白(gao)富(fu)美(shuai)、走向人生巅峰呢?

图片 16

那么,在一个项目中,我们要回答的第一个问题就是“这么多的框架,该使用哪一个?”这篇文章将尝试从项目实施相关方面入手,对目前大热框架的特点进行分析,帮助你选择最合适的一款。

由于篇幅有限、框架众多,在分析之前,我们从版本更新频度和社区活跃度来进行初步的筛选。已经出现了比较久的Backbone和Knockout, 目前流行度正在持续衰退,说明市场已经做出了选择,市面上出现了更有竞争力的替代品; 还有aurelia这类的新涌现者,需要等待时间的检验。

但是在一个商业项目中,一个有活跃社区支撑,并能得到长期支持的框架无疑能给我们更大的信心。因此,Angular, Ember,Vue或者React都是理想的选择。

高性能滚动 scroll 及页面渲染优化

2016/05/18 · JavaScript · 2 评论 · 网页渲染

本文作者: 伯乐在线 - chokcoco 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

最近在研究页面渲染及web动画的性能问题,以及拜读《CSS SECRET》(CSS揭秘)这本大作。

本文主要想谈谈页面优化之滚动优化。

主要内容包括了为何需要优化滚动事件,滚动与页面渲染的关系,节流与防抖,pointer-events:none 优化滚动。因为本文涉及了很多很多基础,可以对照上面的知识点,选择性跳到相应地方阅读。

滚动优化的由来

滚动优化其实也不仅仅指滚动(scroll 事件),还包括了例如 resize 这类会频繁触发的事件。简单的看看:

var i = 0; window.addEventListener('scroll',function(){ console.log(i++); },false);

1
2
3
4
var i = 0;
window.addEventListener('scroll',function(){
console.log(i++);
},false);

输出如下:

图片 17

在绑定 scroll 、resize 这类事件时,当它发生时,它被触发的频次非常高,间隔很近。如果事件中涉及到大量的位置计算、DOM 操作、元素重绘等工作且这些工作无法在下一个 scroll 事件触发前完成,就会造成浏览器掉帧。加之用户鼠标滚动往往是连续的,就会持续触发 scroll 事件导致掉帧扩大、浏览器 CPU 使用率增加、用户体验受到影响。

在滚动事件中绑定回调应用场景也非常多,在图片的懒加载、下滑自动加载数据、侧边浮动导航栏等中有着广泛的应用。

当用户浏览网页时,拥有平滑滚动经常是被忽视但却是用户体验中至关重要的部分。当滚动表现正常时,用户就会感觉应用十分流畅,令人愉悦,反之,笨重不自然卡顿的滚动,则会给用户带来极大不舒爽的感觉。

滚动与页面渲染的关系

为什么滚动事件需要去优化?因为它影响了性能。那它影响了什么性能呢?额……这个就要从页面性能问题由什么决定说起。

我觉得搞技术一定要追本溯源,不要看到别人一篇文章说滚动事件会导致卡顿并说了一堆解决方案优化技巧就如获至宝奉为圭臬,我们需要的不是拿来主义而是批判主义,多去源头看看。

从问题出发,一步一步寻找到最后,就很容易找到问题的症结所在,只有这样得出的解决方法才容易记住。

说教了一堆废话,不喜欢的直接忽略哈,回到正题,要找到优化的入口就要知道问题出在哪里,对于页面优化而言,那么我们就要知道页面的渲染原理:

浏览器渲染原理我在我上一篇文章里也要详细的讲到,不过更多的是从动画渲染的角度去讲的:《【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理》 。

想了想,还是再简单的描述下,我发现每次 review 这些知识点都有新的收获,这次换一张图,以 chrome 为例子,一个 Web 页面的展示,简单来说可以认为经历了以下下几个步骤:

图片 18

  • JavaScript:一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如做一个动画或者往页面里添加一些 DOM 元素等。
  • Style:计算样式,这个过程是根据 CSS 选择器,对每个 DOM 元素匹配对应的 CSS 样式。这一步结束之后,就确定了每个 DOM 元素上该应用什么 CSS 样式规则。
  • Layout:布局,上一步确定了每个 DOM 元素的样式规则,这一步就是具体计算每个 DOM 元素最终在屏幕上显示的大小和位置。web 页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如, 元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。
  • Paint:绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个 DOM 元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。
  • Composite:渲染层合并,由上一步可知,对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

这里又涉及了层(GraphicsLayer)的概念,GraphicsLayer 层是作为纹理(texture)上传给 GPU 的,现在经常能看到说 GPU 硬件加速,就和所谓的层的概念密切相关。但是和本文的滚动优化相关性不大,有兴趣深入了解的可以自行 google 更多。

简单来说,网页生成的时候,至少会渲染(Layout+Paint)一次。用户访问的过程中,还会不断重新的重排(reflow)和重绘(repaint)。

其中,用户 scroll 和 resize 行为(即是滑动页面和改变窗口大小)会导致页面不断的重新渲染。

当你滚动页面时,浏览器可能会需要绘制这些层(有时也被称为合成层)里的一些像素。通过元素分组,当某个层的内容改变时,我们只需要更新该层的结构,并仅仅重绘和栅格化渲染层结构里变化的那一部分,而无需完全重绘。显然,如果当你滚动时,像视差网站(戳我看看)这样有东西在移动时,有可能在多层导致大面积的内容调整,这会导致大量的绘制工作。

防抖(Debouncing)和节流(Throttling)

scroll 事件本身会触发页面的重新渲染,同时 scroll 事件的 handler 又会被高频度的触发, 因此事件的 handler 内部不应该有复杂操作,例如 DOM 操作就不应该放在事件处理中。

针对此类高频度触发事件问题(例如页面 scroll ,屏幕 resize,监听用户输入等),下面介绍两种常用的解决方法,防抖和节流。

防抖(Debouncing)

防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。

通俗一点来说,看看下面这个简化的例子:

// 简单的防抖动函数 function debounce(func, wait, immediate) { // 定时器变量 var timeout; return function() { // 每次触发 scroll handler 时先清除定时器 clearTimeout(timeout); // 指定 xx ms 后触发真正想进行的操作 handler timeout = setTimeout(func, wait); }; }; // 实际想绑定在 scroll 事件上的 handler function realFunc(){ console.log("Success"); } // 采用了防抖动 window.addEventListener('scroll',debounce(realFunc,500)); // 没采用防抖动 window.addEventListener('scroll',realFunc);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 简单的防抖动函数
function debounce(func, wait, immediate) {
// 定时器变量
var timeout;
return function() {
// 每次触发 scroll handler 时先清除定时器
clearTimeout(timeout);
// 指定 xx ms 后触发真正想进行的操作 handler
timeout = setTimeout(func, wait);
};
};
 
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
 
// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);

上面简单的防抖的例子可以拿到浏览器下试一下,大概功能就是如果 500ms 内没有连续触发两次 scroll 事件,那么才会触发我们真正想在 scroll 事件中触发的函数。

上面的示例可以更好的封装一下:

// 防抖动函数 function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate & !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; var myEfficientFn = debounce(function() { // 滚动中的真正的操作 }, 250); // 绑定监听 window.addEventListener('resize', myEfficientFn);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 防抖动函数
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate & !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
 
var myEfficientFn = debounce(function() {
// 滚动中的真正的操作
}, 250);
 
// 绑定监听
window.addEventListener('resize', myEfficientFn);

节流(Throttling)

防抖函数确实不错,但是也存在问题,譬如图片的懒加载,我希望在下滑过程中图片不断的被加载出来,而不是只有当我停止下滑时候,图片才被加载出来。又或者下滑时候的数据的 ajax 请求加载也是同理。

这个时候,我们希望即使页面在不断被滚动,但是滚动 handler 也可以以一定的频率被触发(譬如 250ms 触发一次),这类场景,就要用到另一种技巧,称为节流函数(throttling)。

节流函数,只允许一个函数在 X 毫秒内执行一次。

与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件 handler。

与防抖相比,节流函数多了一个 mustRun 属性,代表 mustRun 毫秒内,必然会触发一次 handler ,同样是利用定时器,看看简单的示例:

// 简单的节流函数 function throttle(func, wait, mustRun) { var timeout, startTime = new Date(); return function() { var context = this, args = arguments, curTime = new Date(); clearTimeout(timeout); // 如果达到了规定的触发时间间隔,触发 handler if(curTime - startTime >= mustRun){ func.apply(context,args); startTime = curTime; // 没达到触发间隔,重新设定定时器 }else{ timeout = setTimeout(func, wait); } }; }; // 实际想绑定在 scroll 事件上的 handler function realFunc(){ console.log("Success"); } // 采用了节流函数 window.addEventListener('scroll',throttle(realFunc,500,1000));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 简单的节流函数
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
 
return function() {
var context = this,
args = arguments,
curTime = new Date();
 
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if(curTime - startTime >= mustRun){
func.apply(context,args);
startTime = curTime;
// 没达到触发间隔,重新设定定时器
}else{
timeout = setTimeout(func, wait);
}
};
};
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了节流函数
window.addEventListener('scroll',throttle(realFunc,500,1000));

上面简单的节流函数的例子可以拿到浏览器下试一下,大概功能就是如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。

使用 rAF(requestAnimationFrame)触发滚动事件

上面介绍的抖动与节流实现的方式都是借助了定时器 setTimeout ,但是如果页面只需要兼容高版本浏览器或应用在移动端,又或者页面需要追求高精度的效果,那么可以使用浏览器的原生方法 rAF(requestAnimationFrame)。

requestAnimationFrame

window.requestAnimationFrame() 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。

rAF 常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器)

通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。)

简单而言,使用 requestAnimationFrame 来触发滚动事件,相当于上面的:

throttle(func, xx, 1000/60) //xx 代表 xx ms内不会重复触发事件 handler

1
throttle(func, xx, 1000/60) //xx 代表 xx ms内不会重复触发事件 handler

简单的示例如下:

var ticking = false; // rAF 触发锁 function onScroll(){ if(!ticking) { requestAnimationFrame(realFunc); ticking = true; } } function realFunc(){ // do something... console.log("Success"); ticking = false; } // 滚动事件监听 window.addEventListener('scroll', onScroll, false);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var ticking = false; // rAF 触发锁
 
function onScroll(){
  if(!ticking) {
    requestAnimationFrame(realFunc);
    ticking = true;
  }
}
 
function realFunc(){
// do something...
console.log("Success");
ticking = false;
}
// 滚动事件监听
window.addEventListener('scroll', onScroll, false);

上面简单的使用 rAF 的例子可以拿到浏览器下试一下,大概功能就是在滚动的过程中,保持以 16.7ms 的频率触发事件 handler。

使用 requestAnimationFrame 优缺点并存,首先我们不得不考虑它的兼容问题,其次因为它只能实现以 16.7ms 的频率来触发,代表它的可调节性十分差。但是相比 throttle(func, xx, 16.7) ,用于更复杂的场景时,rAF 可能效果更佳,性能更好。

总结一下

  • 防抖动:防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。
  • 节流函数:只允许一个函数在 X 毫秒内执行一次,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。
  • rAF:16.7ms 触发一次 handler,降低了可控性,但是提升了性能和精确度。

简化 scroll 内的操作

上面介绍的方法都是如何去优化 scroll 事件的触发,避免 scroll 事件过度消耗资源的。

但是从本质上而言,我们应该尽量去精简 scroll 事件的 handler ,将一些变量的初始化、不依赖于滚动位置变化的计算等都应当在 scroll 事件外提前就绪。

建议如下:

避免在scroll 事件中修改样式属性 / 将样式操作从 scroll 事件中剥离**

图片 19

输入事件处理函数,比如 scroll / touch 事件的处理,都会在 requestAnimationFrame 之前被调用执行。

因此,如果你在 scroll 事件的处理函数中做了修改样式属性的操作,那么这些操作会被浏览器暂存起来。然后在调用 requestAnimationFrame 的时候,如果你在一开始做了读取样式属性的操作,那么这将会导致触发浏览器的强制同步布局。

滑动过程中尝试使用 pointer-events: none 禁止鼠标事件

大部分人可能都不认识这个属性,嗯,那么它是干什么用的呢?

pointer-events 是一个 CSS 属性,可以有多个不同的值,属性的一部分值仅仅与 SVG 有关联,这里我们只关注 pointer-events: none 的情况,大概的意思就是禁止鼠标行为,应用了该属性后,譬如鼠标点击,hover 等功能都将失效,即是元素不会成为鼠标事件的 target。

可以就近 F12 打开开发者工具面板,给 <body>标签添加上 pointer-events: none 样式,然后在页面上感受下效果,发现所有鼠标事件都被禁止了。

那么它有什么用呢?

pointer-events: none 可用来提高滚动时的帧频。的确,当滚动时,鼠标悬停在某些元素上,则触发其上的 hover 效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。对 body 元素应用 pointer-events: none ,禁用了包括 hover 在内的鼠标事件,从而提高滚动性能。

.disable-hover { pointer-events: none; }

1
2
3
.disable-hover {
    pointer-events: none;
}

大概的做法就是在页面滚动的时候, 给 添加上 .disable-hover 样式,那么在滚动停止之前, 所有鼠标事件都将被禁止。当滚动结束之后,再移除该属性。

可以查看这个 demo 页面。

上面说 pointer-events: none 可用来提高滚动时的帧频 的这段话摘自 pointer-events-MDN ,还专门有文章讲解过这个技术:

使用pointer-events:none实现60fps滚动 。

这就完了吗?没有,张鑫旭有一篇专门的文章,用来探讨 pointer-events: none 是否真的能够加速滚动性能,并提出了自己的质疑:

pointer-events:none提高页面滚动时候的绘制性能?

结论见仁见智,使用 pointer-events: none 的场合要依据业务本身来定夺,拒绝拿来主义,多去源头看看,动手实践一番再做定夺。

其他参考文献(都是好文章,值得一读):

  • 实例解析防抖动(Debouncing)和节流阀(Throttling)
  • 无线性能优化:Composite
  • Javascript高性能动画与页面渲染
  • Google Developers–渲染性能
  • Web高性能动画

到此本文结束,如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

打赏支持我写出更多好文章,谢谢!

打赏作者

那么在项目实施中,我们一般会关注哪些方面呢?

有过前端开发经验的同僚们可能会想到:要有可复用的组件,要控制质量做测试和静态检查,要有组件隔离的样式方便实现Responsive,要打包部署方便,最好学起来不要太复杂,方便能力建设、节省招聘成本等等。

接下来,我们将从组件复用、测试和学习曲线这三个主要的方面对Angular4,Ember.js,Vue.js和React这四个当前最流行的框架来做更深入的分析,提供更具体的参考。

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 20 图片 21

1 赞 8 收藏 2 评论

1. 可复用的组件

组件复用是每个项目都会重点关注的一个维度。合适的、职责单一的组件会大大提升新特性的开发效率和工程的可维护性,也能方便地进行测试。那么他们的表现都如何呢:

图片 22

概括起来,就是Vue、Component的组件相对灵活、轻量,添加依赖就可以无缝集成到遗留系统中。对于从0到1的系统,也可以使用新的实践来构建工程,例如ES6、Webpack等技术。Angular4官方推荐使用TypeScript,这需要单独构建和打包,不方便与遗留系统集成。Ember.js自定义了一整套生态,基于CoC理念的设计,采用了前端工程中比较前沿的实践和标准,很难与遗留系统集成,更适合在新项目中使用。至于数据绑定,各有优势。双向绑定在表单交互多的场景中更便捷,单向绑定在管理跟踪记录组件状态时更高效。组件状态更新,各有不同的实现:

  • Vue2.js通过定义setter来监听状态变化,特殊场景需要特殊的API支持, 基于virtual DOM的视图更新。
  • React在组件的状态或属性的变化后,也是基于virtual DOM的视图更新。
  • Angular4在引起状态变化的时刻,框架自动触发脏检查,也可以手动执行脏检查,直接操作HTML DOM更新视图。
  • Ember.js提供数据模型,所有数据的操作通过API执行,使用Glimmer引擎进行HTML渲染和更新。

其中,主要的区别是Angular4是通过事件监听,对比数据更新,直接操作DOM来更新视图,而其它都通过Virtual DOM的思路来更新视图。

关于作者:chokcoco

图片 23

经不住流年似水,逃不过此间少年。 个人主页 · 我的文章 · 63 ·    

图片 24

本文由澳门新葡亰手机版发布于web前端,转载请注明出处:高性能滚动,该何去何从

上一篇:前者底蕴进级,要求计划的步骤 下一篇:没有了
猜你喜欢
热门排行
精彩图文