解决iOS 环境下,使用 position: sticky 的元素在滚动时可能会突然消失或表现异常(如不吸顶、闪动等)

811 阅读2分钟

1、首先简单介绍一下 position: sticky:

position: sticky 是 CSS 定位属性中的一个值,它结合了相对定位(relative)和固定定位(fixed)的特点,允许元素在滚动到特定位置时"粘住"在视口中。

其中和 fixed 属性的本质区别是 sticky 保留了原来的位置,类似 relative 的效果没有跳出文本流,而 fixed 属于跳出原本的文本流,类似于 absolute。

2、那么问题出现了:

问题一:在 ios 环境下,通过JS方法锚点滚动,如果没有加 平滑滚动 behavior:smooth,滚动时会出现闪烁

image.png

问题二:众所周知,在 ios 环境下,元素滑动到最顶部和最底部会出现弹性效果(ios自带的,安卓没有),导致可以一直上划和一直下滑,此时会出现 sticky 的元素异常消失

转存失败,建议直接上传图片文件

image.png

3、ios出现问题的原因是什么呢?为什么安卓就不会有问题

主要原因其实就是 iOS 的渲染机制 和 Safari 对 sticky 的特定处理 导致的,其次就是安卓上划下滑到顶部底部没有弹性效果,所以不能一直划动。

4、怎么解决呢?

有说强制让浏览器gpu加速的,有说不按sticky正确使用的,试了都没有效果

然后突然想到,fixed 状态下好像没有这个问题,如果滑动的时候是 fixed,暂停的时候是sticky不就好了。

下面是具体的实现代码:

针对问题一:在点击滚动的时候将sticky元素设置成fixed,然后通过setTimeout还原回sticky

// 解决精选tab切换时,顶部tab导航sticky在ios环境下闪缩问题
  const setTopTabFixed = () => {
    const stickyEl = document.querySelector('.root-top-tabs');
    const rootStickyEl = document.querySelector('.root-page-main');
    stickyEl.style.position = 'fixed';
    rootStickyEl.style.marginTop = 'var(--header-tab-height)';

    setTimeout(() => {
      stickyEl.style.position = 'sticky';
      rootStickyEl.style.marginTop = '0';
    }, 100);
  };
```

针对问题二:在上划或者下划的时候监听手指滑动,滑动时将sticky元素设置成fixed(我这里是用上划还原,下划设置fixed,具体情况具体分析)。

```
useEffect(() => {
    let startY = 0;
    const touchstartFunc = e => {
      startY = e.touches[0].clientY;
    };
    const touchmoveFunc = e => {
      const currentY = e.touches[0].clientY;
      const isScrollingUp = currentY < startY; // 上划(手指向上移动)

      const optTopEle = document.getElementById('optTopEle');
      const optListEl = document.getElementById('optListEle');
      const stickyEl = document.querySelector('.root-top-tabs');
      const rootStickyEl = document.querySelector('.root-page-main');
      // console.log(
      //   '111111111optTopEle.clientHeight',
      //   optTopEle.clientHeight,
      //   optTopEle.offsetHeight
      // );

      // 总高度
      const headerHeightValue =
        parseFloat(document.documentElement.style.getPropertyValue('--header-tab-height') || '0') +
          optTopEle?.clientHeight || 0;

      if (isScrollingUp) {
        optTopEle.style.position = 'fixed';
        stickyEl.style.position = 'fixed';
        rootStickyEl.style.marginTop = 'var(--header-tab-height)';
        optListEl.style.marginTop = `${headerHeightValue}px`;
      } else {
        optTopEle.style.position = 'sticky';
        stickyEl.style.position = 'sticky';
        rootStickyEl.style.marginTop = '0';
        optListEl.style.marginTop = '0';
      }
    };
    window.addEventListener('touchstart', touchstartFunc);
    window.addEventListener('touchmove', touchmoveFunc);

    return () => {
      window.removeEventListener('touchstart', touchstartFunc);
      window.removeEventListener('touchmove', touchmoveFunc);
    };
  }, []);
```

总结:

以上就是完美的解决方案了,由于ios 的渲染机制和安卓的不同,导致 sticky 的效果会在滑动等情况下显示异常,这时候通过设置 fixed 过度解决 sticky 闪缩,消失等问题。

做H5端的平时总会遇到一些兼容性问题,只要安卓和ios没有统一,做兼容的工作任重道远啊