@H_301_0@在上一篇文章<a target="_blank" href="//www.jb51.cc/article/81289.htm">使用getBoundingClientRect方法实现简洁的sticky组件的方法介绍了一个sticky组件的简洁实现,经过这两天的思考,发现上次提供的实现还有较多不足的地方,另外跟别的网站上实现的效果在取消固定的时候也有一些不同,上次提供的取消固定的处理方式不好,本文在上文的基础上,提供一个改进版的sticky组件,功能更加完善,希望您有兴趣阅读。
1. 旧版本的问题
上一个sticky组件的实现中,有多个问题存在:
第一,从sticky的效果上来说,sticky元素在固定前后,不会变化的是相对浏览器左边的位置以及sticky元素的整体宽度,可能会变化的是相对浏览器顶部或底部的位置和sticky元素的高度,而上文提供的实现中把后面两个会变化的值都当成了不变的值。为什么在固定的时候top值或bottom值就一定是0?当然可以不是0阿,比如top: 20px,bottom: 15px,在某些场景里,加上一些这样的偏移,sticky的效果会更好看,比如bootstrap官方文档中用到的affix组件实例(这个组件的功能跟本文实现的sticky组件是差不多的):
底部的偏移,用来设置top跟bottom属性的值,默认为0
isFixedWidth: true,//sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false
getStickyWidth: undefined,//用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth
unStickyDistance: undefined,//该参数决定sticky元素何时进入dynamicSticky状态
onSticky: undefined,///sticky元素固定时的回调
onUnSticky: undefined ///sticky元素取消固定时的回调
};
@H_301_0@加粗的几个是新增或有修改的,去掉了原来的height,用unStickyDistance来替代。固定时候相对浏览器顶部或底部的位置,用stickyOffset来指定,这样在.sticky--in-top或.sticky--in-bottom的css里就不用再写top或bottom属性值了。isFixedWidth如果为false,才会去添加resize时刷新sticky元素宽度的回调:
@H_301_0@
0) return 'staticSticky';
else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky';
else return 'unSticky';
},getDynamicOffset: function (rect) {
return -(unStickyDistance - rect.bottom);
}
},bottom: {
getState: function (rect) {
if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky';
else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky';
else return 'unSticky';
},getDynamicOffset: function (rect) {
return -(unStickyDistance + rect.top - docClientHeight);
}
}
}
$win.scroll(throttle(sticky,opts.wait));
function sticky() {
var rect = $target[0].getBoundingClientRect(),curState = rules[opts.type].getState(rect);
states[curState](rect);
}
@H_301_0@有点状态模式的思想在里面,不过更简洁。当我写出这个代码的时候,其实是很想用之前了解的状态机来写的,我想过用状态机来写肯定是可以实现的,不过为了少引用一个类库就算了,等哪天想实践状态机的时候再来尝试一把。
@H_301_0@整体实现如下: