整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript 滚轮(卷动)事件

轮事件:滚轮

滚动(卷动)事件:滚轮、拖拽滚动条、键盘方向键

<script type="text/javascript">
//滚轮事件:滚轮
//卷动事件:滚轮、拖拽滚动条、键盘↕键

//IE和Chrome
gunlun.onmousewheel = function(){
this.innerHTML += "IE和Chrome<br/>";
}
//Firefox
gunlun.addEventListener("DOMMouseScroll", function(){
this.innerHTML += "Firefox<br/>";
})
</script>

判断滚轮方向

<script type="text/javascript">
//滚轮事件:滚轮
//卷动事件:滚轮、拖拽滚动条、键盘↕键

//IE和Chrome
gunlun.onmousewheel = function(e){
var ev = e || window.event;
console.log(ev.wheelDelta);//判断滚轮方向的
//上120
//下-120

this.innerHTML += "IE和Chrome<br/>";
}
//Firefox
gunlun.addEventListener("DOMMouseScroll",function(e){
var ev = e || window.event;
console.log(ev.detail);//滚轮方向
//上-3
//下3
this.innerHTML += "Firefox<br/>";
})
</script>

兼容性封装

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

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

滚动优化的由来

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

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

输出如下:

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

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

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

滚动与页面渲染的关系

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

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

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

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

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

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

JavaScript:一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如做一个动画或者往页面里添加一些 DOM 元素等。

Style:计算样式,这个过程是根据 CSS 选择器,对每个 DOM 元素匹配对应的 CSS 样式。这一步结束之后,就确定了每个 DOM 元素上该应用什么 CSS 样式规则。

Layout:布局,上一步确定了每个 DOM 元素的样式规则,这一步就是具体计算每个 DOM 元素最终在屏幕上显示的大小和位置。web 页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,<body> 元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。

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,监听用户输入等),之前我有JavaScript什么是防抖和节流?和JavaScript什么是防抖和节流(下部)?两篇文章详细的介绍了两者的概念和自己实现的方法,这也是大多数教程推荐且常用的解决方法。

但是,采用它们也有一些问题,例如

1、如果列表页有很多图片,我们采用懒加载方式,如果配合防抖函数,当我们不停的滚动鼠标的时候(不暂停),就会发现图片看不到,只有停下来过了delay ms之后才会被加载出来,这样的体验不是很友好

2、这时候如果我们采用节流函数就可以解决上面的问题,即使鼠标在滚动 handler 也可以以一定的频率被触发(譬如 250ms 触发一次),在下滑过程中图片不断的被加载出来,而不是只有当我停止下滑时候,图片才被加载出来。又或者下滑时候的数据的 ajax 请求加载也是同理。

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

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

requestAnimationFrame

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

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

通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。

rAF并不是一个定时器,我们通常递归调用rAFt以达到连续repaint的目的。rAF的本质是为我们提供了主动申请一帧的能力,以往的setInterval帧刷新都是浏览器根据你的操作来触发。而使用rAF让我们掌握了主动权。这就意味着使用rAF可以保证不丢帧,但是并不能保证帧率。

rAF 更多的是用于动画制作替代 setInterval(func,1000/60) 更加精确的控制动画及保证动画的流畅性,确保动画不会掉帧

(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。)

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

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

简单的示例如下:

上面简单的使用 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 事件中剥离

输入事件处理函数,比如 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;
}

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

上面说 pointer-events: none 可用来提高滚动时的帧频 的这段话摘自 pointer-events-MDN

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

pointer-events:none提高页面滚动时候的绘制性能?(如有兴趣,可以自己去查看)

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

参考文章:

  • 实例解析防抖动(Debouncing)和节流阀(Throttling)
  • 无线性能优化:Composite
  • Javascript高性能动画与页面渲染
  • Google Developers--渲染性能
  • Web高性能动画
  • https://www.cnblogs.com/coco1s/p/5499469.html

SS 是一种美丽且复杂的技术,我们每天在工作中都会用到。然而,包括我在内的许多开发者都忽略了它的一些重要方面。

这很明显,因为在互联网上很难找到关于 CSS 的新知识或高级内容。大多数内容创作者只写一些热门话题,比如新语言、框架和库。

个人对 CSS 的了解仅限于让它正常工作。而这特别令人遗憾,因为我们从未尝试深入研究这个主题。认识到这一点后,做了一些研究,并列出了一些新发现的内容。

CSS 性能

CSS 性能是一个非常重要的话题,因为它直接影响网站的效率。由于现代应用程序包含大量的 CSS 代码,即使是微小的错误也可能导致显著的性能下降。

子选择器

首先,下面赂你展示一些令我惊讶的东西。只需看看下面的例子,试着找出可能的问题。

正如你所看到的,这个例子一点都不复杂。这段 CSS 代码移除了标题中所有段落内链接的文本装饰。这不是普通的 CSS 吗?

但你知道使用这样的选择器会导致浏览器执行大量额外工作吗?这个问题适用于我们代码中所有类似的子选择器。

这是因为浏览器在解析 CSS 时,从右向左读取选择器。有了这个新知识,我们将这个过程分解成几个步骤:

  1. 浏览器找到页面上所有的 <a> 标签。
  2. 然后它找到包含在段落中的所有锚点。
  3. 最后,它将找到的集合缩小到仅包含在 #header 元素内的锚点。

通过使用更具体的选择器,我们可以帮助浏览器避免所有这些额外的工作。我们可以将 .header__link 类应用于锚元素,并用它替换选择器 #header p a。这样,浏览器会更快地找到所需的元素。

昂贵的 CSS 属性

所谓昂贵的 CSS 属性,是指这些属性在我们的应用程序中可能会耗费大量性能。但这并不意味着你完全不能使用它们。你只需要理解,如果元素使用了这些属性并且经常渲染——它肯定会影响性能。

需要注意的一件棘手事情是,改变某些 CSS 属性需要更新整个布局。几何属性如宽度、高度、顶部等可能导致整个树的重新绘制。

还有一些属性很难渲染。虽然这些属性的清单很短,但你可以在这里了解更多关于它们的信息。

  1. border-radius
  2. box-shadow
  3. filter
  4. :nth-child
  5. position: fixed

重绘和重排

重绘(Repaint)和重排(Reflow)是在浏览器中渲染网页过程中两个重要的概念。当页面加载或更新时,浏览器会通过一系列步骤将内容显示在屏幕上,而重绘和重排在这个过程中起着重要作用。

为了更好地理解这个过程,我们可以将其分解为几个精确的步骤:

  • 从服务器下载的 HTML 文档用于构建文档对象模型(DOM)。
  • 样式被加载和识别,创建 CSS 对象模型(CSSOM)。
  • 基于 DOM 和 CSSOM,形成一个渲染树,这是一个渲染对象的集合。渲染树复制了 DOM 的结构,但不可见的元素(例如 <head> 或 display: none 的元素)不包含在内。换句话说,渲染树描述了 DOM 的视觉表示。
  • 对于渲染树中的每个元素,计算其在页面上的位置,并进行布局。浏览器使用流方法,通常一个操作就足以放置所有元素。例如,表格需要更多的操作。
  • 最后,所有元素在浏览器中被渲染,并且绘制完成。

当页面首次加载时,如果不是空的,至少会执行一次重排和重绘。但它们也会在以下情况下继续发生:

  • 当由于节点的宽度、高度或其他坐标的变化,树的一部分需要重新计算时,会触发重排事件。
  • 当由于变化,一些显示的内容必须更新时,这主要适用于诸如背景颜色、半径等样式属性,会触发重绘事件。
  • 此外,当发生重排事件时,总是会伴随一个重绘事件。而重绘则可以独立于重排触发。

关于重排

重排是计算网页上元素布局的过程。它根据元素的内容和样式来确定每个元素的大小和位置。

在现代应用程序中,特别是页面上有成千上万个元素和频繁的布局更新时,它很快会变成一个资源消耗大的操作。即使是对文档中单个元素的重排,也可能需要重排其父元素和随后的任何其他元素。

由于它是一个用户阻塞操作,了解如何改进重排以及各种文档属性对其持续时间的影响是非常有用的。典型的引发重排过程的情况通常有:

  • 对 DOM 的不同操作。例如,添加、删除和更改节点。
  • 元素内容的变化,甚至包括表单字段中的文本。
  • 计算(getComputedStyle)或修改 CSS 属性。
  • 对元素类的操作。
  • 浏览器窗口的操作(调整大小、滚动)。
  • 伪类的激活。:hover 就是一个很好的例子。

关于重绘

重绘是将像素绘制到屏幕上的简单过程。在重排期间确定布局后,浏览器将每个元素绘制到屏幕上。

重绘通常比重排消耗的资源少,但它仍然会影响网页的性能。

最典型的情况是当元素样式的变化不影响其在屏幕上的大小或位置时。例如,当背景颜色改变时,浏览器只需用新样式重新绘制(或重绘)节点。

以下是一些会触发重绘的操作的例子:

  • 改变元素的可见性。
  • 改变元素的轮廓。
  • 改变背景。

CSS 可以替代 JS 的地方

JavaScript 和 CSS 是非常棒的技术,尤其是在一起使用时。每种技术都有其独特的优势,结合使用时可以创造出令人难以置信的效果。

然而,我可以自信地说,UI 控制中使用 CSS 越多,Web 应用程序就越容错和可靠。

观点很简单:

  • CSS 本身是一种安全技术。当 CSS 解析器遇到无效属性时,它会简单地忽略并继续。
  • JavaScript 不是一种安全技术。JS 代码中的单个语法错误可能导致整个应用程序失败。换句话说,当使用 JS 控制样式时,确保相应构造的功能正常工作是至关重要的。

现在让我们考虑一些你可以轻松用 CSS 替代 JS 的情况。

平滑滚动

以前,要实现平滑滚动,需要使用几行 JavaScript 代码。但现在,这个任务可以完全通过 CSS 的力量来解决。

现在你可以通过使用 CSS 属性 scroll-behavior 来实现平滑滚动。

文本处理

CSS 提供了两个很棒的属性:text-overflowline-clamp。它们允许我们修剪文本,同时让我们摆脱使用 JavaScript 代码或其他复杂方法来执行此类任务。这两个属性虽然不是新的,但非常有用。

text-overflow 属性

这个属性控制文本在不适合一行的情况下如何显示。这个属性的一个完美例子是在卡片标题中使用 text-overflow: ellipsis,它会在被截断的文本末尾显示一个 Unicode 字符

请记住,text-overflow: ellipsis 只有在与 white-space: nowrapoverflow: hidden 属性配合使用时才有效。

line-clamp 属性


line-clamp 属性在需要处理多行文本时非常有用。上面示例中的卡片描述展示了它的效果。

这个属性是 CSS Overflow Module Level 3 标准的一部分,虽然目前还处于草案阶段,但已经有大约 95% 的浏览器支持这个属性,但需要加上 -webkit- 前缀。

在使用它之前,重要的是要提到它不允许控制显示字符的数量。但它仍然非常有用。

要使用 line-clamp 属性,我们需要将其与 display: -webkit-box-webkit-box-orient: vertical 结合使用。示例如下:

总结

现代前端开发的世界正在迅速变化,我们不断得到新的机会来帮助我们更快、更好地完成工作。尝试使用 CSS 是非常有趣且有帮助的。如果在不使用 JS 的情况下可以实现相同的结果,请考虑使用 CSS。