likes
comments
collection
share

[组件封装]Vue3无缝滚动组件,支持鼠标悬浮停止,且上下左右四个方向使用Vue3技术开发,支持上下左右四个方向的移动更

作者站长头像
站长
· 阅读数 12

前言

大家好,我是辉夜真是太可爱啦。

文本主要讲述封装一个无限滚动展示的组件,效果图如下,也可以传入字符串,和中奖播报一样。

[组件封装]Vue3无缝滚动组件,支持鼠标悬浮停止,且上下左右四个方向使用Vue3技术开发,支持上下左右四个方向的移动更

使用Vue3技术开发,支持上下左右四个方向的移动更改,并且支持鼠标悬浮停止,还有速度的调节,为了方便过渡效果,在最右侧做了一个遮罩。并且已封装为一个组件。

下面,我们将从源码分享,组件的使用,开发思路等三个方面来给大家讲讲这个组件。

组件使用

<template>
  <InfiniteScroll :content="urlList">
    <template v-slot="{ item}">
      <img :src="item.url" alt="" />
    </template>
  </InfiniteScroll>
</template>

<script setup>
import InfiniteScroll from '@/components/InfiniteScroll.vue'

let urlList = [
  {
    url: '',
  },
  // ...
]

  • content 传入数组或者字符串
  • direction 可控制方向,上下左右
  • mask 是否显示遮罩
  • speedRate 控制速率

源码

下面就是组件的整体代码:

<template>
  <div
    ref="scrollBox"
    :class="`infinite-scroll-component-box-${direction}`"
    :style="{
      '--speed-': `${speed}s`,
      mask: `linear-gradient(${degList[direction]}, #000 70%, transparent)`,
    }"
  >
    <div class="scroll-content" ref="scrollContent">
      <template v-if="Array.isArray(content)">
        <div
          v-for="(item, index) in content"
          :key="'arrayFirst' + index"
          class="loop-item"
        >
          <slot :item="item" :index="index"></slot>
        </div>
        <template v-if="loop">
          <div
            v-for="(item, index) in content"
            class="loop-item"
            :key="'arrayCopy' + index"
          >
            <slot :item="item" :index="index"></slot>
          </div>
        </template>
      </template>
      <template v-else>
        <slot></slot>
        <slot v-if="loop"></slot>
      </template>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref, nextTick, defineProps } from 'vue'

const props = defineProps({
  direction: {
    type: String,
    default: 'left',
    validate: (value) =>
      ['left', 'top', 'right', 'bottom'].findIndex(value) !== -1,
  },

  content: {
    type: [String, Array],
    default: '',
  },

  mask: {
    type: Boolean,
    default: true,
  },

  // 速率倍速,越大越快,默认值20
  speedRate: {
    type: Number,
    default: 20,
  },
})

onMounted(() => {
  init()
})

let degList = {
  left: '90deg',
  top: '180deg',
  right: '270deg',
  bottom: '0deg',
}
// 滚动速度
let speed = ref(0)
// 是否需要无限滚动
let loop = ref(false)
// 容器ref
let scrollBox = ref()

watch(
  () => props.content,
  () => {
    loop.value = false
    init()
  },
  { deep: true }
)

// 初始化速度,以及是否需要无限滚动
const init = async () => {
  await nextTick()
  if (props.direction === 'left' || props.direction === 'right') {
    // 可视区域的宽度
    const boxWidth = scrollBox.value.offsetWidth
    // 滚动内容的宽度
    const itemWidth =
      scrollBox.value.getElementsByClassName('scroll-content')[0].offsetWidth
    if (itemWidth >= boxWidth) {
      loop.value = true
      speed.value = itemWidth / props.speedRate
    } else {
      speed.value = 0
      scrollBox.value.getElementsByClassName(
        'scroll-content'
      )[0].style.transform = 'translateX(0)'
      loop.value = false
    }
  } else {
    const boxHeight = scrollBox.value.offsetHeight
    const itemHeight =
      scrollBox.value.getElementsByClassName('scroll-content')[0].offsetHeight
    if (itemHeight >= boxHeight) {
      loop.value = true
      speed.value = itemHeight / props.speedRate
    } else {
      speed.value = 0
      scrollBox.value.getElementsByClassName(
        'scroll-content'
      )[0].style.transform = 'translateY(0)'
      loop.value = false
    }
  }
}
</script>

<style lang="scss" scoped>
.infinite-scroll-component-box-left,
.infinite-scroll-component-box-right {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;

  .scroll-content {
    position: absolute;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: center;

    &:hover {
      animation-play-state: paused;
    }
  }
}
.infinite-scroll-component-box-top,
.infinite-scroll-component-box-bottom {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;

  .scroll-content {
    width: 100%;
    position: absolute;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: center;

    &:hover {
      animation-play-state: paused;
    }

    .loop-item {
      width: 100%;
    }
  }
}
.infinite-scroll-component-box-left .scroll-content {
  flex-direction: row;
  animation: var(--speed-) move-left linear infinite;

  @keyframes move-left {
    0% {
      transform: translateX(0);
    }
    100% {
      transform: translateX(-50%);
    }
  }
}

.infinite-scroll-component-box-right .scroll-content {
  flex-direction: row-reverse;
  animation: var(--speed-) move-right linear infinite;

  @keyframes move-right {
    0% {
      transform: translateX(-50%);
    }
    100% {
      transform: translateX(0);
    }
  }
}

.infinite-scroll-component-box-top .scroll-content {
  flex-direction: column;
  animation: var(--speed-) move-top linear infinite;

  @keyframes move-top {
    0% {
      transform: translateY(0);
    }
    100% {
      transform: translateY(-50%);
    }
  }
}

.infinite-scroll-component-box-bottom .scroll-content {
  flex-direction: column-reverse;
  animation: var(--speed-) move-bottom linear infinite;

  @keyframes move-bottom {
    0% {
      transform: translateY(-50%);
    }
    100% {
      transform: translateY(0);
    }
  }
}
</style>

开发思路

下面的思路都以方向为 left 作为

1. 滚动动画

实现滚动的思路主要有js和css,但是css相对更加方便,因为可以使用 css3animation-play-state: paused 让动画停止。将它加在元素的 :hover 伪类上即可实现悬浮停止。

2. 无缝滚动

实现无限滚动的思路,首先就是需要将现有的元素重复一遍, slot 插槽用于插入父组件中的元素,当我们写两遍 slot 时,即可轻松实现元素的复制操作。

我们将0-5的元素进行复制一遍,即可得到下图,黑框为可见区域。

[组件封装]Vue3无缝滚动组件,支持鼠标悬浮停止,且上下左右四个方向使用Vue3技术开发,支持上下左右四个方向的移动更

当我们从第一排的0开始滚动,当第二排的0滚动到可视区域的头部时,我们直接返回到图1的状态,即可实现无缝滚动啦。

[组件封装]Vue3无缝滚动组件,支持鼠标悬浮停止,且上下左右四个方向使用Vue3技术开发,支持上下左右四个方向的移动更

那么,按照这个思路,也就是总元素从 transform: translateX(0); 状态开始滚动,当滚动到 transform: translateX(-50%); 之后,将它再次还原到 transform: translateX(0);

那就是以下动画:

@keyframes move-left {
    0% {
      transform: translateX(0);
    }
    100% {
      transform: translateX(-50%);
    }
  }

为了让动画不断重复,那就是 animation: var(--speed-) move-left linear infinite;

3. 是否需要无限滚动

我们考虑一下,当我们复制了之后,总元素的宽度仍然不及可视区域的宽度时,我们都不需要滚动。

所以我们使用了一个 loop 变量来控制是否需要滚动。

4. 过渡更加平滑

为了让过渡更加平滑,这个也是现在很多无缝滚动都使用的效果,那就是右侧增加一个渐隐的效果。

我们使用的是 mask 属性,mask: linear-gradient(${degList[direction]}, #000 70%, transparent)

mask 属性中,有颜色代表可见, transparent 代表不可见。

然后为了过渡平滑,我们使用了一个渐变来让过渡更加的平滑。

5. 均匀的速度

还有一个问题,animation 设置的动画秒数如果通过 props 传入,很明显会有问题。

因为当秒数固定,然后元素内容的长度不固定时,比方说秒数控制在10s, content 长度为10时,1s一个,content 长度为20时,0.5s一个,会造成数据不一样的时候,无缝滚动的速度也不一样。

所以, props 不能传入秒数,而传入一个速率,不同长度,它的动画完成时间也不一样。

代码如下, speed.value = itemWidth / props.speedRate

转载自:https://juejin.cn/post/7388635438741159945
评论
请登录