滚动页面区块连续置顶终极解决方案

2,506 阅读3分钟

背景

项目中经常会页面碰到滚动特定距离后区块(如页签,分类标题等)吸顶的场景;本文章记录了开发过程中遇到的各种坑。

案例效果

image

开发思路

step one

首先计算好各个需要置顶区块距离页面顶部的偏移量。

    var me = this;
    me.options.topArray = [];
    // 要用容器来计算offset
    $.each(me.$elements.$pathTitlesWrap, function (index, item) {
        var top = $(item).offset().top;
        me.options.topArray.push(top);
    });
    me.options.topRegion = [];
    $.each(me.options.topArray, function (index, item) {
        var region = {};
        region.start = me.options.topArray[index];
        region.end = me.options.topArray[index + 1] ? me.options.topArray[index + 1] : me.options.topArray[index];
        me.options.topRegion.push(region);
    });

计算好后形成了两个数组:一个是偏移量数组如[100, 200, 300, 400];另外一个是偏移量区间如:[{start: 100, end: 200}, {start: 200, end: 300}, {start: 300, end: 400}, {start: 400, end: 400}]

step two

通常的思路就是监听window.onscroll事件,判断滚动条的滚动距离,在哪个区间内然后修改相应区块的position属性由relative改为fixed

    function handler () {
    	// 滚动条偏移量
       var scrollTop = $(window).scrollTop();
       var topArray = me.options.topArray;
		
       // 滚动条偏移量小于第一个区块的offset就隐藏一个区块
       if (scrollTop <= me.options.topRegion[0] - 1) {
            me.$elements.$pathTitleWrapFixed[0].style.display = 'none';
       }

       $.each(me.options.topRegion, function(index, item) {
          if (scrollTop >= item.start && scrollTop < item.end) {
               me._setPathTitleFixedByIndex(index);
               return false;
           }
           else {
               // me.$elements.$pathTitles.css('position', 'relative');
                me.$elements.$pathTitleWrapFixed[0].style.display = 'none';
           }
       });
       // 滚动条偏移量大于最后一个区块的offset就置顶最后一个区块
       if (scrollTop >= me.options.topArray[me.options.topArray.length - 1]) {
            me._setPathTitleFixedByIndex(me.options.topArray.length - 1);
       }
   }

step three

大功告成;开心的提测;但是。。。。。都是泪。。。。。

填坑

  • 坑one

    • 现象:区块置顶时页面会跳动

    • 原因:区块元素由relative改为fixed时,脱离文档流,会导致页面高度变化,所以出现页面跳动

    • 解决思路:置顶区块套个容器,容器高度为区块高度,这样区块脱离文档流的时候,页面高度不会发生变化,就没跳动的现象了。

  • 坑two

    • 现象:两个区块碰撞时,交互定位时,仍会跳动

    • 原因:relative改为fixed,fixed改为relative导致

    • 解决思路:在页面顶部预置一个置顶的区块,滚动条到达偏移量区间时替换文字即可。

  • 坑three

    • 现象:ios平台的浏览中页面快速滑动,惯性滚动中window.onscroll事件不会触发;只有惯性滚动停止时才会执行脚本;导致不能及时的吸顶

    • 原因:在ios,当一个手指按住屏幕并且在屏幕上移动不会发生任何事件,直到停止移动,才触发onscroll事件

    • 解决思路一:

    采用touch事件来触发handler方法

    // 解决ios下面页面滚动时禁止所有事件,但是仍然解决不了惯性滚动
      $('body').on('touchstart', function (e) {
          handler.apply(me);
      });
      $('body').on('touchmove', function (e) {
          handler.apply(me);
      });
      $('body').on('touchend', function (e) {
          handler.apply(me);
          setTimeout(function() {
              handler.apply(me);
          }, 1000);
      });

    但是,但是,解决了滑动拖动过程中实时计算的问题;注意touchend的时候,用两个定时器来从新计算,知识解决了快速惯性滚动时,有的时候置顶的fixed块回不到relative的情况。但是仍然没有解决window.onscroll导致的问题。

    • 解决思路二:

    采用iscoll组件的probe,监听onScroll事件,可以实时监听到滚动距离

    例子:lab.cubiq.org/iscroll5/de…

    • 解决思路三:

    使用 position: sticky 达到粘性元素区域悬浮效果

sticky: 对象在常态时遵循常规流。它就像是relative和fixed的合体,当在屏幕中时按常规流排版,当卷动到屏幕外时则表现如fixed。该属性的表现是现实中你见到的吸附效果。(CSS3)

重点了解sticky属性值 当该元素在屏幕中时并不脱离文档流,仍然保留元素原本在文档流中的位置。

当元素在容器中被滚动超过指定的偏移值时,该元素将固定在容器指定的位置。例如:设置了top:50px;那么在sticky元素到达距离相对定位的元素顶部50px的位置时固定,不再向上移动

元素固定的相对偏移是相对于离它最近的具有滚动框的祖先元素,如果祖先元素都不可以滚动,那么是相当于viewport(视口)来计算元素的偏移量

当目标元素在屏幕中可见时,他的行为就像position:relative;而当页面滚动超出目标区域时,他的表现就像position:fixed;它会固定在目标位置。

需要注意的是,如果同时定义了left和right值,那么left生效,right会无效,同样,同时定义了top和bottom,top赢

.sticky {  
    position: -webkit-sticky;  
    position: -moz-sticky;  
    position: -ms-sticky;  
    position: sticky;  
    top: 15px; // 使用和 Fixed 同样的方式设定位置  
}

完美解决,ios上的问题完美解决,并且还会有推出去的效果。但是android平台有兼容性问题。需要做兼容性处理。

终极方案

区分平台Android平台用之前的脚本;ios平台用css实现.

if (/(Android)/i.test(navigator.userAgent)) {
        // 页面滚动事件
        me.bindEvent(window, 'scroll', handler);
        // 解决ios下面页面滚动时禁止所有事件,但是仍然解决不了惯性滚动
        $('body').on('touchstart', function (e) {
            handler.apply(me);
        });
        $('body').on('touchmove', function (e) {
            handler.apply(me);
        });
        $('body').on('touchend', function (e) {
            handler.apply(me);
            setTimeout(function() {
                handler.apply(me);
            }, 1000);
        });
    }

也可以判断css3属性是否支持,如果支持就用css样式,不支持脚本来实现。

/** 
* 判断浏览器是否支持某一个CSS3属性 
* @param {String} 属性名称 
* @return {Boolean} true/false 
* @version 1.0 
* @author ydr.me 
* 2014年4月4日14:47:19 
*/
 
function supportCss3(style) { 
	var prefix = ['webkit', 'Moz', 'ms', 'o'], 
		i, 
		humpString = [], 
		htmlStyle = document.documentElement.style, 
		_toHumb = function (string) { 
				return string.replace(/-(\w)/g, function ($0, $1) { 
					return $1.toUpperCase(); 
				}); 
		}; 
 
		for (i in prefix) 
			humpString.push(_toHumb(prefix[i] + '-' + style)); 
 
			humpString.push(_toHumb(style)); 
 
		for (i in humpString) 
			if (humpString[i] in htmlStyle) return true; 
 
		return false; 
}