起因
建行公众号内嵌的充值页面,在三星Note3,三星S4,S5以及Nexus下无法切换产品。
话费及流量产品是根据手机号动态添加的,在上述手机中option无法正常点击切换。开始想使用的方法是js重新添加元素后重新绑定change事件,但是有个非常奇怪的现象。
var index = $('#select_id').get(0).selectedIndex;
$('#select_id option:eq(' + index + ')').attr("selected", "selected");
上面的写法是无法达到切换效果的,但是如果index的值写死就可以成功切换。问题是测试过index的type就是num,且“#select_id option:eq(‘ + index + ‘)“的值也没有错,简直无解 %>_<%。
调试
chrome layout触发
张鑫旭最近的博文访问里提到了chrome haslayout触发的bug。在js动态添加option后,使用-webkit-transform: translateZ(0)搞定了瑞雪的Nexus和同事的Note3,但是建行领导的Note3和S5居然还是跪了。
没有测试机真是忧伤的要死
找不到测试机啊,bug无法复现啊,然后只能杀到建行现场解决了。第一次出现场(历史上的一天😂),mark一下。
新技能get
-
人建行没有wifi,局域网还设限,调试的时候只能牺牲一下自己开个wifi热点了==
-
之前在公司的时候为了测试不同型号的手机挨个加了同事的微信,这个草料二维码很方便,扫一扫就好了
所以到底是什么鬼
跑到建行发现还真不是领导XX,js加了版本号也不是缓存问题,select的change事件都失效了。最后只能一块一块代码测过去发现是fastclick这个库在捣鬼。
其他相关
页面重绘,重排相关
google的时候看到了这篇博文访问
浏览器页面呈现流程
-
浏览器把获取到的html代码解析成1个Dom树,html中的每个tag都是Dom树中的1个节点,根节点就是我们常用的document对象(注意不是<body>而是<html>)。dom树里面包含了所有的html tag,包括display:none隐藏,还有用JS动态添加的元素等。
-
浏览器把所有样式(主要包括css和浏览器的样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而firefox会去掉_开头的样式。
-
dom tree和样式结构体结合后构建呈现树(render tree),render tree有点类似于dom tree,但其实区别有很大,render tree能识别样式,render tree中每个node都有自己的style,而且render tree不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到render tree中。注意 visibility:hidden隐藏的元素还是会包含到render tree中的,因为visibility:hidden 会影响布局(layout)(visibility: hidden和display: none很大的一个差别),会占有空间。
-
一旦render tree构建完毕后,浏览器就可以根据render tree来绘制页面了。
重绘与重排
回流必将引起重绘,而重绘不一定会引起重排。
-
当render tree中的一部分(或全部)因为元素的尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(重新布局)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
-
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
什么会影响页面重绘和重排
(是否有影响到页面的布局) 其实任何对render tree中元素的操作都会引起回流或者重绘,比如:
1. 添加、删除元素(回流+重绘)
2. 隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)
3. 移动元素,比如改变top,left(jquery的animate方法就是,改变top,left不一定会影响回流),或者移动元素到另外1个父元素中。(重绘+回流)
4. 对style的操作(对不同的属性操作,影响不一样)
5. 还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
如何减少重绘和重排
其实就是减少对render tree的操作。具体方法有:
-
不要一个个改变元素样式,可以直接改变className。或者像jquery里.css方法直接传对象好了。
-
让要操作的元素进行”离线处理”,处理完后一起更新,这里所谓的”离线处理”即让元素不存在于render tree中,比如
a) 使用documentFragment或div等元素进行缓存操作,这个主要用于添加元素的时候,大家应该都用过,就是先把所有要添加到元素添加到1个div(这个div也是新加的),最后才把这个div append到body中(<script type=”template”></script>也一样?)。
b) 先display:none 隐藏元素,然后对该元素进行所有的操作,最后再显示该元素。因对display:none的元素进行操作不会引起回流、重绘。所以所有操作只会有2次回流。
-
不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,就先读取到变量中进行缓存,以后用的时候直接读取变量就可以了
// 别这样写 for(循环) { el.style.left = el.offsetLeft + 5 + "px"; el.style.top = el.offsetTop + 5 + "px"; } // 这样写好点 var left = el.offsetLeft,top = el.offsetTop,s = el.style; for(循环) { left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
-
考虑你的操作会影响到render tree中的多少节点以及影响的方式,影响越多,花费肯定就越多.
移动端300ms延迟
最终这个切换产品无效的bug定位到fastclick这个库的使用。
当初引入这个库的目的是为了解决click事件300毫秒延迟的问题。
为什么会有300ms延迟
当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此浏览器就等待 300 毫秒,以判断用户是否再次点击了屏幕。300 毫秒点击延迟的来龙去脉
解决方案
- fastClick->但是这次踩的坑给我的教训是不要乱用库!!!好多手机有无法切换产品的bug😂
- zepto->zepto的tap事件可以解决这个问题,但是zepto自身还是有一些问题Zepto 使用中的一些注意点