效果

requestAnimationFrame是一个由浏览器提供的 JavaScript 方法,用于在下一次浏览器重绘之前执行指定的回调函数。它接受一个回调函数作为参数,并返回一个整数值,可以用于取消动画循环。
使用 requestAnimationFrame 可以创建平滑的动画效果,并且能够有效地控制动画的帧率,以适应不同的设备和浏览器性能。
与使用定时器(如 setTimeout 或 setInterval)相比,requestAnimationFrame 具有以下优势:
自动适应设备刷新率:requestAnimationFrame 会自动根据当前设备的刷新率来调整动画的帧率,以提供更平滑的动画效果。
避免资源浪费:当页面处于非激活状态时,requestAnimationFrame 会暂停动画循环,从而避免不必要的资源浪费。
与浏览器的绘制周期同步:requestAnimationFrame 会在浏览器的绘制周期内执行回调函数,以确保动画的更新和渲染在最佳时机进行。
function animate() {
  // 动画逻辑...
  requestAnimationFrame(animate);
}
// 开始动画循环
requestAnimationFrame(animate);
const animationId = requestAnimationFrame(animate);
// 取消动画循环
cancelAnimationFrame(animationId);实现代码
循环滚动
<template>
  <div class="wrap" ref="wrapRef">
    <div v-for="item in items" :key="item" class="item">${{ item }}</div>
    <template v-if="isShowItems">
      <div v-for="item in items" :key="item" class="item">${{ item }}</div>
    </template>
  </div>
</template><script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
const items = ref(Array.from({ length: 26 }, (_, index) => index + 1));
const wrapRef = ref(null);
const isShowItems = ref(false);
const animationId = ref(null);
// scrollWidth 总宽度,包括被隐藏的部分
// clientWidth 可视宽度,不包括被隐藏的部分 window.innerWidth
// scrollLeft 滚动距离
const playAnimate = () => {
  const wrapElement = wrapRef.value;
  if (wrapElement === null) return;
  const maxScrollLeft = wrapElement.scrollWidth - wrapElement.clientWidth;
  if (maxScrollLeft) {
    console.log(
      maxScrollLeft,
      wrapElement.scrollWidth,
      wrapElement.clientWidth
    );
    isShowItems.value = true;
    if (wrapElement.scrollLeft >= maxScrollLeft - 2) {
      wrapElement.scrollLeft -= maxScrollLeft;
    }
    wrapElement.scrollLeft += 1;
    // 动画循环
    animationId.value = requestAnimationFrame(playAnimate);
  }
};
onMounted(() => {
  console.log('总宽度', wrapRef.value.scrollWidth);
  console.log('可视宽度', wrapRef.value.clientWidth);
  //   playAnimate();
  // 开始动画循环
  requestAnimationFrame(playAnimate);
});
onUnmounted(() => {
  //   wrapRef.value = null;
  console.log('取消动画循环', animationId.value);
  // 在需要取消动画循环时
  cancelAnimationFrame(animationId);
});
</script><style lang="scss" scoped>
.wrap {
  background: linear-gradient(90deg, #9da0a0, #818181 138.82%);
  align-items: center;
  display: flex;
  height: 50px;
  white-space: nowrap;
  width: 100%;
  overflow: hidden;
  overflow: auto;
  &::-webkit-scrollbar {
    display: none;
  }
  .item {
    font-size: 18px;
    color: #fff;
    padding: 0 24px;
  }
}
</style>来回滚动
<template>
  <div class="wrap" ref="wrapRef">
    <div v-for="item in items" :key="item" class="item">${{ item }}</div>
  </div>
</template><script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
const items = ref(Array.from({ length: 26 }, (_, index) => index + 1));
const wrapRef = ref(null);
const isShowItems = ref(false);
const scrollLeftEnd = ref(false);
const animationId = ref(null);
// scrollWidth 总宽度,包括被隐藏的部分
// clientWidth 可视宽度,不包括被隐藏的部分 window.innerWidth
// scrollLeft 滚动距离
const playAnimate = () => {
  const wrapElement = wrapRef.value;
  if (wrapElement === null) return;
  const maxScrollLeft = wrapElement.scrollWidth - wrapElement.clientWidth;
  if (maxScrollLeft) {
    console.log(
      maxScrollLeft,
      wrapElement.scrollWidth,
      wrapElement.clientWidth
    );
    isShowItems.value = true;
    // 右到左
    // if (wrapElement.scrollLeft >= maxScrollLeft - 2) {
    //   wrapElement.scrollLeft -= maxScrollLeft;
    // }
    // wrapElement.scrollLeft += 1;
    // 来回滚动
    if (wrapElement.scrollLeft >= maxScrollLeft - 2) {
      scrollLeftEnd.value = true;
    }
    if (wrapElement.scrollLeft <= 1) {
      scrollLeftEnd.value = false;
    }
    if (scrollLeftEnd.value) {
      wrapElement.scrollLeft -= 1;
    } else {
      wrapElement.scrollLeft += 1;
    }
    // 动画循环
    animationId.value = requestAnimationFrame(playAnimate);
  }
};
onMounted(() => {
  console.log('总宽度', wrapRef.value.scrollWidth);
  console.log('可视宽度', wrapRef.value.clientWidth);
  //   playAnimate();
  // 开始动画循环
  requestAnimationFrame(playAnimate);
});
onUnmounted(() => {
  //   wrapRef.value = null;
  console.log('取消动画循环', animationId.value);
  // 在需要取消动画循环时
  cancelAnimationFrame(animationId);
});
</script>
<style lang="scss" scoped>
.wrap {
  background: linear-gradient(90deg, #9da0a0, #818181 138.82%);
  align-items: center;
  display: flex;
  height: 50px;
  white-space: nowrap;
  width: 100%;
  overflow: hidden;
  overflow: auto;
  &::-webkit-scrollbar {
    display: none;
  }
  .item {
    font-size: 18px;
    color: #fff;
    padding: 0 24px;
  }
}
</style>








![[Redis实战]分布式锁](https://img-blog.csdnimg.cn/direct/c9a2ed94483143cb83a26cc9d9294c68.png)






![[SSD 测试 1.3] 消费级SSD全生命周期测试](https://img-blog.csdnimg.cn/direct/88279155b5c246dcad9ce33710bf1452.png)


