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

  • 相关阅读:
    Fiddler filter 过滤隐藏css、js、图片等
    十三、单元测试
    十二、文件操作
    Go_客户信息管理系统
    十一、面向对象编程_下
    十、面向对象编程_上
    九、map
    八、排序和查找
    七、数组和切片
    六、函数、包和错误处理
  • 原文地址:https://www.cnblogs.com/nangezi/p/15708016.html
Copyright © 2011-2022 走看看