zoukankan      html  css  js  c++  java
  • vue / js scrollIntoView的使用和替代方法(无jquery的滚动动画效果)

    scrollIntoView: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView


    背景

    笔者想要实现一个页面,该页面包括如下功能:

    1. 顶部Tab
      -需要置顶;
      -超出则左右可滑动;
      -点击时将选中Tab高亮,且自动居中
    2. 内容滚动区域
      -需和顶部Tab联动,即点击Tab,内容滚动至该Tab对应的锚点处;相应的,滚动内容,如到达该锚点时,对应的Tab也需要切换为高亮选中状态

    模块、功能拆分:

    1. 页面容器内容,分为两个模块:
      tab等置顶模块;
      滚动区域模块
    2. tab模块实现切换高亮和选中时滚动居中功能
    3. 内容滚动区域,实现与顶部tab的联动

    功能实现设计:

    1. 顶部Tab置顶,内容滚动区域选择
      方案一: 严格区分置顶区域和滚动区域,使用overflow:hidden 固定页面容器,将滚动区域的overflow设置为可滚动(注意此时的滚动容器)
      方案二: 直接使用position: fixed固定顶部Tab,滚动区域不做安排,直接使用window容器滚动
    2. 点击Tab时滚动内容导到指定锚点处:
      方案一: 使用scrollIntoView
      方案二: 使用基础的scrollTop赋值的方式
    3. 滚动内容时,如到达该埋点时,自动切换Tab
      方案: 监听scroll(根据功能1的方案确定滚动容器)

    注意: 其实滚动容器的选择就决定了功能2、功能3的方案选择


    方案比较:

    1. 滚动容器选择方案1,即部分区域可滚动
      缺点1: 需要配置好、区分好可滚动区域,且外部需要设置 overflow: hidden 阻止滚动(注意外部不可有滚动区域)
      缺点2: 后续的scroll监听需要依赖于选择的滚动容器节点
      缺点3: scrollIntoView的使用兼容性问题(smooth平滑动画无效果)

    2. 滚动容器选用方案2,即使用window容器进行滚动
      缺点1: scrollIntoView的效果,是将节点滚动到可滚动容器的顶端,而我们是有置顶内容的

    所以:
    最终选择: 1. 使用window作为滚动区域,不使用scrollIntoView,而是直接通过scrollTop赋值来实现滚动效果


    方案一CSS代码: 可实现 顶部Tab 和 滚动区域的严格区分

    .page-container {
      position: relative;
      overflow: hidden;
      height: 100%;
      display: flex;
      flex-direction: column;
    
      header {
        // 顶部固定模块
         100%;
        height: 1.8rem;
      }
    
      .scroll-container {
        // 滚动容器
        flex: 1;
        overflow: auto;
    
        .scroll-content {
          // 滚动内容
          .first {}
          .second {}
          .third {}
          .fourth {}
          .fifth {}
        }
      }
    }
    

    方案一JS代码: 可实现 点击Tab 滚动到指定锚点

    methods: {
        setActiveIndex(index) {
          // 滚动tab到可视区域中间
          // 使用 smooth 或者 center 有问题?
          this.$refs[`topTab${index}`][0]?.scrollIntoView({ inline: 'center' });
          if (this.activeIndex === index) {
            return;
          }
          this.activeIndex = index;
          // 滚动到指定位置
          this.$refs[`scrollView${index}`]?.scrollIntoView();
        },
    }
    

    scrollIntoView 平滑滚动
    element.scrollIntoView({behavior: "smooth"})


    最终实现方案:

    切换Tab实现内容滚动到指定锚点:

    methods: {
       // 滚动到指定位置
        scrollToElePosition(index) {
          const { first, second, third, fourth, fifth } = this.$refs;
          // 记录当前滚动状态
          this.isSmoothScrolling = true;
          // 动画时间
          const SCROLL_DURATION = 500;
          if (index === TAB_INDEX_ZERO) {
            scrollToY(first.offsetTop, SCROLL_DURATION);
          } else if (index === TAB_INDEX_ONE) {
            scrollToY(second.offsetTop, SCROLL_DURATION);
          } else if (index === TAB_INDEX_TWO) {
            scrollToY(third.offsetTop, SCROLL_DURATION);
          } else if (index === TAB_INDEX_THREE) {
            scrollToY(fourth.offsetTop, SCROLL_DURATION);
          } else if (index === TAB_INDEX_FOUR) {
            scrollToY(fifth.offsetTop, SCROLL_DURATION);
          }
        },
    }
    

    使用scrollTop赋值实现滚动动画:

    /*
     * y: the y coordinate to scroll, 0 = top
     * duration: scroll duration in milliseconds; default is 0 (no transition)
     * element: the html element that should be scrolled ; default is the main scrolling element
     */
    const scrollToY = function (y, duration = 0, element = document.scrollingElement) {
      // cancel if already on target position
      if (element.scrollTop === y) {
        return;
      }
    
      const NUMBER_TWO = 2;
      const cosParameter = (element.scrollTop - y) / NUMBER_TWO;
      let scrollCount = 0;
      let oldTimestamp = null;
    
      const step = function (newTimestamp) {
        if (oldTimestamp !== null) {
          // if duration is 0 scrollCount will be Infinity
          scrollCount += Math.PI * (newTimestamp - oldTimestamp) / duration;
          if (scrollCount >= Math.PI) {
            element.scrollTop = y;
            return;
          }
          element.scrollTop = cosParameter + y + cosParameter * Math.cos(scrollCount);
        }
        oldTimestamp = newTimestamp;
        window.requestAnimationFrame(step);
      };
      window.requestAnimationFrame(step);
    };
    

    监听内容滚动,切换Tab

    实现方式: 主要是通过scroll监听来实现,通过获取当前的scrollTop 来和 每个锚点模块的offsetTop 进行比较
    注意点:

    1. 注意throttle节流的使用
    2. 需要使用 isSmoothScrolling 来避免 和 切换Tab时滚动到指定锚点的功能相互影响
    mounted() {
      // 基准offset - first模块
        const { offsetTop: firstOffsetTop } = first;
        // 节流时间间隔
        const THROTTLE_INTERVAL = 200;
        window.addEventListener(
          'scroll',
          !this.isSmoothScrolling &&
            throttle(() => {
              // 正在平滑滚动,无需监听
              if (this.isSmoothScrolling) return;
              // 当前的滚动距离
              const scrollTop =
                window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
              // 各个临界值,可能会变
              const secondOffsetTop = second.offsetTop;
              const thirdOffsetTop = third.offsetTop;
              const fourthOffsetTop = fourth.offsetTop;
              const fifthOffsetTop = fifth.offsetTop;
              // scroll计算当前的activeTab
              this.scrollCalcCurrentActiveTab({
                scrollTop,
                firstOffsetTop,
                secondOffsetTop,
                thirdOffsetTop,
                fourthOffsetTop,
                fifthOffsetTop,
              });
            }, THROTTLE_INTERVAL)
        );
    },
    methods: {
      // scroll计算当前的activeTab
        scrollCalcCurrentActiveTab({
          scrollTop,
          firstOffsetTop,
          secondOffsetTop,
          thirdOffsetTop,
          fourthOffsetTop,
          fifthOffsetTop,
        }) {
          // 此处向上取整,因为offsetTop获取的是整数,这会导致滚动时tab位置错误
          const currentOffsetTop = firstOffsetTop + Math.ceil(scrollTop);
          if (currentOffsetTop < secondOffsetTop) {
            this.activeIndex = 0;
          } else if (currentOffsetTop >= secondOffsetTop && currentOffsetTop < thirdOffsetTop) {
            this.activeIndex = 1;
          } else if (currentOffsetTop >= thirdOffsetTop && currentOffsetTop < fourthOffsetTop) {
            this.activeIndex = 2;
          } else if (currentOffsetTop >= fourthOffsetTop && currentOffsetTop < fifthOffsetTop) {
            this.activeIndex = 3;
          } else if (currentOffsetTop >= fifthOffsetTop) {
            this.activeIndex = 4;
          }
        },
    }
    

    最后:

    1. 需要注意 动画滚动和 window.addEventListener('scroll') 监听相互干扰的情况,需要使用变量 isSmoothScrolling 来规避
      实现:
      在切换tab,内容滚动至锚点时 this.isSmoothScrolling = true;
      监听touchmove事件,将 this.isSmoothScrolling = false;
      scroll监听是通过判断isSmoothScrolling,来确定是否执行scroll监听的回调方法
    mounted() {
        // 此处通过 
        window.addEventListener(
          'touchmove',
          throttle(() => {
            if (this.isSmoothScrolling) {
              this.isSmoothScrolling = false;
            }
          })
        );
    }
    
    1. 注意使用节流来节省性能

    最后的最后:

    无jquery 的 滚动动画效果:
    https://stackoverflow.com/questions/21474678/scrolltop-animation-without-jquery
    https://github.com/Robbendebiene/Sliding-Scroll/blob/master/sliding-scroll.js

  • 相关阅读:
    iSCSI又称为IPSAN
    文档类型定义DTD
    HDU 2971 Tower
    HDU 1588 Gauss Fibonacci
    URAL 1005 Stone Pile
    URAL 1003 Parity
    URAL 1002 Phone Numbers
    URAL 1007 Code Words
    HDU 3306 Another kind of Fibonacci
    FZU 1683 纪念SlingShot
  • 原文地址:https://www.cnblogs.com/nangezi/p/15708016.html
Copyright © 2011-2022 走看看