position:sticky 踩坑记录

position:sticky 踩坑记录

背景

最近在移动端需要实现导航栏的粘性布局,看到原型的时候也就想了下怎么实现。不外乎是通过js监听window的scroll事件,当需要固定的元素滚动到窗口顶部时,把元素的position属性设置为fixed,否则,取消fixed,简单代码如下:

var nav = document.querySelector('.navBar');
function onScroll(e) {
window.scrollY >= nav.offsetTop ? nav.classList.add('fixed') : nav.classList.remove('fixed');
}
window.addEventListener('scroll', onScroll);

开发的时候一直在用Chrome的模拟器,滑动起来很顺畅,后来测试的时候用真机发现粘顶效果很不好。ios滑动的时候会暂停所有的js执行,等待滚动完成之后才会执行,所以上面代码的监听并没有在滑动时响应。安卓机下面相对好一点。但是作为一个水果机,竟然有如此不优雅的展现方式,怎么对得起乔帮主的在天之灵啊!

既然浏览器自带滚动条不适用,就尝试用类似iscroll插件模拟滚动条,但是考量了一下,滚动页面需要上拉加载更多,需要对整页进行iscroll,损耗性能不说还要重新实现iscroll(或者引入插件)。所以准备使用position的新属性 Sticky。

##Sticky positioning
sticky布局是一个类似relative和fixed的混合布局。在视窗范围内,元素会以relative布局显示,当超过阀值时会以fixed布局显示。参照

例如:

#one { position: sticky; top: 10px; }

当页面滚动到sticky在文档流中位置元素离视窗顶端的距离<=10px时,sticky元素就开始固定了,当sticky元素在文档流中位置离viewport顶端的距离>10px时,元素就不再固定。

至此,sticky属性完全符合我们的要求。但是很遗憾,作为一个实验属性,有很多浏览器并没有实现该属性。大家看看下图:

各浏览器兼容情况

chrome下全红,安卓下也基本没有实现。庆幸的是,我现在只需要照顾到移动端方面,ios6+都已经支持了该属性,安卓下就使用fixed属性好了。

// 判断是否支持sticky属性
function isSupportSticky() {
var prefixTestList = ['', '-webkit-', '-ms-', '-moz-', '-o-'];
var stickyText = '';
for (var i = 0; i < prefixTestList.length; i++ ) {
stickyText += 'position:' + prefixTestList[i] + 'sticky;';
}
// 创建一个dom来检查
var div = document.createElement('div');
var body = document.body;
div.style.cssText = 'display:none;' + stickyText;
body.appendChild(div);
var isSupport = /sticky/i.test(window.getComputedStyle(div).position);
body.removeChild(div);
div = null;
return isSupport;
}

##生效条件
按照上面的方法设置好,很大程度上还是不会实现粘顶的效果,查了很多资料,都没有给出很好的原因,然后我就硬着头皮去看官方文档。发现sticky属性是有生效条件的。

  • 任何一级父节点都不能overflow为非visible,否则都不会生效
  • 设置了postion:sticky的元素要生效必须要至少设置top,bottom,left,right中的一个属性,并且同时设置时,top>bottom,left>right。

参考:
CSS Positioned Layout Module Level 3
CSS “position: sticky” – Introduction and Polyfills