以前经常遇到这样一种情况:

很多网站上的幻灯轮播,尤其是那种拉动切换的,当打开一段时间后(未关闭,但是切到别的标签或最小化)再切换到它的时候,会突然切换好多张,感觉好像是从切换到别的标签之前的状态,一下子切换到切换回来的状态了。这不科学啊,写的好好的一个效果,就这样被糟蹋了。

但是由于之前自己写过的这种幻灯轮播,基本也就存在这一个问题,并且还是大众化的问题,也就未再追究。但后来发现,这种遇到问题不求甚解的心态,会给我们带来很多的潜在危险甚至是不定时炸弹。说不定哪天就给你来个迎头痛击,让你后悔当初遇到这个问题的时候,为什么没有好好抓住它搞清楚。解决问题的过程才是成长的过程。

先来看一个轮播:原生JS轮播

打开后发现也没什么问题,那么用 chrome/FF 切换到别的标签继续浏览别的东西。某一时刻你又切换回去的时候发现,这个轮播像抽风了一样,一次闪过好多张。为什么会这样呢?

当时我给出的设想是 chrome/FF 为了节省浏览器资源,当视窗不可见的时候(这种说法不严谨,当打开的窗口一直是当前标签的时候,即使焦点在别的windows窗口上,切换也不会出问题,只有切到别的标签或者最小化了才会出现这种问题),就停止页面渲染,但定时器还是在运行的,只记录了状态和位置,当再次切回去的时候,就从视窗不可见之前的状态渲染到它本应达到的页面状态。所以就出现了一次切换好多帧的问题。

但是经测试 IE9/Opera/safari 都没有这样的问题,即使视窗不可见,页面 GUI 渲染也正常。反思这个问题可能引发的 bug, 当然节省了 CPU 资源,省下页面渲染的资源留给你当前浏览的窗口,这可能也是为什么 chrome 浏览器比其他浏览器页面渲染速度快的原因之一吧。但是当你的定时器用到了 currentStyle/defaultView 等即时页面样式的时候,可能会让你的程序出现预料以外的 bug

比如我第一次写的上面的那个轮播,在播放完完整的一轮后,我是通过判断了滚动容器距离外容器左边的距离 left (当然是绝对值)是否等于整个滚动元素的宽 width 来实现的。这样当切换回来的时候,定时器所保存的状态和位置肯定是和当前样式有很大差异的,甚至可能大于整个滚动元素的宽,这样就跨过我判断是否滚动完一轮的条件了。所以不能用 == 而应该用 >=

我个人觉得,web 程序员应该有权利决定当视窗不可见的时候是否还进行页面渲染。或许是有别的办法可以避免页面暂停渲染,我暂时还不知道,待解决。

以后再用定时器来动态修饰页面元素的时候,一定要注意 chrome/FF 的这种特性,从而避免一些奇怪的 bug