使用requestAnimationFrame和IntersectionObserver实现无限平滑滚动
1.2k
类别: 
开发交流

最近在项目中做到一个功能,官网的底部有一块区域,放了很多logo,需要实现的效果就是让这些logo横向无限循环滚动,下面直接讲一下我的思路和做法。 先看一下大概的一个效果,就是让这些小方块在这个区域横向滚动。

image.png
我这里以三行举列。 假设我们初始的小方块有19个,这个区域正好能放下18个,也就是多一个被遮住。 先看下代码

<!-- 横向无限滚动效果 -->
<template>
  <div
    class="container"
    ref="container"
    @mouseover="stopScroll"
    @mouseleave="startScroll"
  >
    <div
      class="list"
      v-for="(item, i) in Math.ceil(arr.length / 3)"
      :key="item"
    >
      <div class="item" v-for="v in arr.slice(i * 3, (i + 1) * 3)">
        {{ v }}
      </div>
    </div>
    <div
      class="more"
      style="width: 20px; height: 20px; background-color: pink"
    ></div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 小方块列表
      animateId: null, // 动画id
      moreDom: null, // 交叉的DOM
      ob: null, // 观察者
    };
  },
  methods: {
    addObserver() {
      this.moreDom = document.querySelector(".more");
      this.ob = new IntersectionObserver(
        (entries) => {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              // moreDom 和 container 交叉 就复制一遍列表 保证无限滚动
              this.arr = [...this.arr, ...this.arr];
            }
          });
        },
        {
          root: document.querySelector(".container"),
          rootMargin: "0px 180px 0px 0px",
        }
      );
      this.ob.observe(this.moreDom);
    },
    startScroll() {
      const dom = document.querySelector(".container");
      dom.scrollLeft += 2; // 这个值越大,滚动越快
      this.animateId = requestAnimationFrame(this.startScroll);
    },
    stopScroll() {
      cancelAnimationFrame(this.animateId);
    },
  },

  mounted() {
    // 开始动画
    this.startScroll();
    // 添加观察者
    this.addObserver();
  },
  beforeDestroy() {
    // 组件卸载时,取消动画
    cancelAnimationFrame(this.animateId);
    // 取消观察
    this.ob.unobserve(this.moreDom);
  },
};
</script>

<style scoped>
.container {
  width: 1200px;
  height: 380px;
  margin: 200px auto;
  border: 1px solid;
  box-sizing: border-box;
  display: flex;
  gap: 20px;
  overflow: auto;
  padding: 20px;
}

.container::-webkit-scrollbar {
  display: none !important;
}

.list {
  box-sizing: border-box;
  flex-shrink: 0;
  width: 180px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.item {
  width: 180px;
  height: 100px;
  background-color: aquamarine;
  align-self: flex-start;
}
</style>

首先说下布局,因为我是三行,所以列的数量就取决于arr的长度,通过计算 Math.ceil(arr.length / 3)算出初始有多少列,不一定整除,所以这里向上取整,arr.slice(i * 3, (i + 1) * 3)表示每一列的三个元素是哪三个,比如:第一列是123,第二列就是456,依次排列。.more这个元素就是被观察的对象,当more和container发生交叉就会触发回调,这里交叉简单理解是就是more元素出现在了container容器中,然后可以去做一些相应的逻辑,这里就是去复制一遍列表。IntersectionObserver就是观察者API,其中第一个参数是一个回调函数,第二个参数就是配置项
root: document.querySelector(".container"),rootMargin: "0px 180px 0px 0px",root表示交叉的容器,rootMargin这四个值分别表示与上右下左的margin值,简单理解就是css中就margin属性,这里也就是.more元素距离container容器的距离,设置这几个值必须要带上单位,0要写成0px(这里需要注意),为什么我这里要写180px,因为我不想.more元素完全出现在.container容器才去复制一遍列表而是提前先复制一遍列表(简单理解也就是预先加载好),这样就没有视察感,提前加载好数据,就不会有空缺的情况出现(这个值越大就越提前加载,同理如果你的列表是向上滚动,那你就设置第三个值(也就是下),最后在滚动方法startScroll中去重复执行requestAnimationFrame(this.startScroll)即可,为什么不使用定时器,因为定时器其实是不准的,而requestAnimationFrame是很精确的,效果上也很丝滑,不会有卡顿的感觉。
今天分享就到这,希望对您有帮助,感谢您的观看。

标签:
评论 0
/ 1000
0
0
收藏