效果
方式一
通过实现两个item 进行循环
<!--
* @Author: Jackie
* @Date: 2023-08-16 21:27:42
* @LastEditTime: 2023-08-16 21:41:51
* @LastEditors: Jackie
* @Description: scroll 水平滚动 - 效果基本满足需求
* @FilePath: /vue3-swiper-demo/src/components/scroll/Scroll12.vue
* @version:
-->
<template>
<div class="ticker-container">
<div class="ticker-viewer">
<div class="ticker-scroll">
<div
class="ticker-scroll-item"
:key="index"
v-for="(item, index) in items"
>
<span v-if="item">{{ item }} ${{ item }}</span>
</div>
</div>
<div class="ticker-scroll">
<div
class="ticker-scroll-item"
:key="index"
v-for="(item, index) in items"
>
<span v-if="item">{{ item }} ${{ item }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const items = ref(Array.from({ length: 26 }, (_, index) => index + 1));
</script>
<style lang="scss" scoped>
.ticker-container {
align-items: center;
background: linear-gradient(90deg, #86eef1, #bcff2f 138.82%);
display: flex;
height: 50px;
white-space: nowrap;
width: 100%;
.ticker-viewer {
align-items: center;
cursor: pointer;
display: flex;
height: 100%;
overflow: hidden;
position: relative;
width: 100%;
.ticker-scroll {
animation-delay: 8s;
opacity: 0;
animation: tickerScroll 240s linear infinite normal;
transform: translateX(0);
transition: all linear;
will-change: transform, opacity;
.ticker-scroll-item {
display: inline-block;
font-size: 18px;
line-height: 22px;
color: #fff;
padding: 0 24px;
}
}
}
}
@keyframes tickerScroll {
0% {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(-100%);
}
}
@keyframes positionScroll {
0% {
transform: translateX(3.33333333%);
}
to {
transform: translateX(0);
}
}
</style>
方式二
通过实现两个item 进行循环
<!--
* @Author: Jackie
* @Date: 2023-08-16 21:02:42
* @LastEditTime: 2023-08-16 21:42:04
* @LastEditors: Jackie
* @Description: 过渡 - 通过两次滚动实现 flex - 效果基本满足需求
* @FilePath: /vue3-swiper-demo/src/components/scroll/Scroll11.vue
* @version:
-->
<template>
<div class="scroll">
<div class="price-band-group" ref="group">
<div
class="price-band-item DIN-medium"
:key="index"
v-for="(item, index) in items"
>
<span v-if="item">{{ item }} ${{ item }}</span>
</div>
</div>
<div class="price-band-group" ref="group">
<div
class="price-band-item DIN-medium"
:key="index"
v-for="(item, index) in items"
>
<span v-if="item">{{ item }} ${{ item }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const scroller = ref(null);
const group = ref(null);
const timerId = ref(null);
const items = ref(Array.from({ length: 26 }, (_, index) => index + 1));
</script>
<style lang="scss" scoped>
.scroll {
display: flex;
overflow: hidden;
width: 100%;
height: 48px;
background: linear-gradient(90deg, #31daff 0.47%, #316bff 100%);
padding: 13px 0;
box-sizing: border-box;
overflow: auto;
.price-band-group {
display: flex;
white-space: nowrap;
/* animation: index_loop 100s linear infinite normal;
animation-delay: 0.5s; */
animation: tickerScroll 240s linear infinite normal;
transform: translateX(0);
.price-band-item {
display: inline-block;
font-size: 18px;
line-height: 22px;
color: #fff;
padding: 0 24px;
}
}
&::-webkit-scrollbar {
display: none;
}
}
@keyframes index_loop {
0% {
transform: translateZ(0);
}
to {
transform: translate3d(-50%, 0, 0);
}
}
@keyframes tickerScroll {
0% {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(-100%);
}
}
</style>
方式三
通过尾部填充重复数据实现
<template>
<div class="scroll" ref="scroller" :style="style">
<div class="price-band-group" v-if="items.length">
<div
class="price-band-item DIN-medium"
v-for="(item, index) in itemsWithTail"
:key="index"
>
<span v-if="item !== null"> {{ item }} ${{ item }} </span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
const scroller = ref(null);
const timerId = ref(null);
const items = ref(Array.from({ length: 12 }, (_, index) => index + 1));
const pauseAnimate = () => {
timerId.value && clearInterval(timerId.value);
timerId.value = null;
};
const playAnimate = () => {
pauseAnimate();
const maxScrollLeft = scroller.value.scrollWidth - scroller.value.clientWidth;
console.log(maxScrollLeft);
timerId.value = setInterval(() => {
if (scroller.value.scrollLeft >= maxScrollLeft) {
scroller.value.scrollLeft -= maxScrollLeft;
}
scroller.value.scrollLeft += 1;
}, 33);
};
const itemsWithTail = computed(
() =>
items.value.length >= 10 ? items.value.concat(items.value) : items.value
// items.value.concat(items.value.slice(0, 4))
);
onMounted(() => {
setTimeout(() => {
scroller.value.scrollLeft = 0;
playAnimate();
}, 100);
});
onUnmounted(() => {
pauseAnimate();
});
</script>
<style lang="scss" scoped>
.scroll {
width: 100%;
height: 48px;
background: linear-gradient(90deg, #31daff 0.47%, #316bff 100%);
padding: 13px 0;
box-sizing: border-box;
overflow: hidden;
transition: scroll-left 1s ease-in-out; /* 添加过渡效果 */
.price-band-group {
display: inline-block;
white-space: nowrap;
.price-band-item {
display: inline-block;
font-size: 18px;
line-height: 22px;
color: #fff;
padding: 0 24px;
}
}
&::-webkit-scrollbar {
display: none;
}
}
</style>
方式四
滚动到头后,直接归0
<template>
<div class="scroll" ref="scroller" :style="style">
<div class="price-band-group">
<div class="price-band-item DIN-medium" v-for="item in items" :key="item">
<span v-if="item"> {{ item }} ${{ item }} </span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const scroller = ref(null);
const timerId = ref(null);
const scrollLeftEnd = ref(false);
const items = ref(Array.from({ length: 22 }, (_, index) => index + 1));
const pauseAnimate = () => {
timerId.value && clearInterval(timerId.value);
timerId.value = null;
};
const playAnimate = () => {
pauseAnimate();
const maxScrollLeft = scroller.value.scrollWidth - scroller.value.clientWidth;
console.log(maxScrollLeft);
timerId.value = setInterval(() => {
if (scroller.value.scrollLeft >= maxScrollLeft) {
scroller.value.scrollLeft -= maxScrollLeft;
}
scroller.value.scrollLeft += 1;
}, 33);
};
onMounted(() => {
console.log(111);
// 必须在100ms后进行,否则计算不准确
setTimeout(() => {
scroller.value.scrollLeft = 0;
playAnimate();
}, 100);
});
onUnmounted(() => {
pauseAnimate();
});
</script>
<style lang="scss" scoped>
.scroll {
width: 100%;
height: 48px;
background: linear-gradient(90deg, #31daff 0.47%, #316bff 100%);
padding: 13px 0;
box-sizing: border-box;
overflow: auto;
.price-band-group {
display: inline-block;
white-space: nowrap;
.price-band-item {
display: inline-block;
font-size: 18px;
line-height: 22px;
color: #fff;
padding: 0 24px;
}
}
&::-webkit-scrollbar {
display: none;
}
}
</style>
方式五
来回滚动方式
<template>
<div class="scroll" ref="scroller" :style="style">
<div class="price-band-group">
<div class="price-band-item DIN-medium" v-for="item in 26" :key="item">
<span v-if="item"> {{ item.item }} ${{ item }} </span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const scroller = ref(null);
const timerId = ref(null);
const scrollLeftEnd = ref(false);
const pauseAnimate = () => {
timerId.value && clearInterval(timerId.value);
timerId.value = null;
};
const playAnimate = () => {
pauseAnimate();
const maxScrollLeft = scroller.value.scrollWidth - scroller.value.clientWidth;
console.log(maxScrollLeft);
timerId.value = setInterval(() => {
if (scroller.value.scrollLeft >= maxScrollLeft - 1) {
scrollLeftEnd.value = true;
}
if (scroller.value.scrollLeft <= 1) {
scrollLeftEnd.value = false;
}
if (scrollLeftEnd.value) {
scroller.value.scrollTo({
top: 0,
left: (scroller.value.scrollLeft -= 1),
behavior: 'smooth'
}); //scrollLeft -= 1;
} else {
scroller.value.scrollTo({
top: 0,
left: (scroller.value.scrollLeft += 1),
behavior: 'smooth'
}); //scrollLeft += 1;
}
}, 33);
};
onMounted(() => {
console.log(111);
// 必须在100ms后进行,否则计算不准确
setTimeout(() => {
scroller.value.scrollLeft = 0;
playAnimate();
}, 100);
});
onUnmounted(() => {
pauseAnimate();
});
</script>
<style lang="scss" scoped>
.scroll {
width: 100%;
height: 48px;
background: linear-gradient(90deg, #31daff 0.47%, #316bff 100%);
padding: 13px 0;
box-sizing: border-box;
overflow: auto;
.price-band-group {
display: inline-block;
white-space: nowrap;
.price-band-item {
display: inline-block;
font-size: 18px;
line-height: 22px;
color: #fff;
padding: 0 24px;
}
}
&::-webkit-scrollbar {
display: none;
}
}
</style>
方式六
scroll滚动 - 循环滚动,不超出不滚动
<!--
* @Author: Jackie
* @Date: 2023-08-17 10:55:21
* @LastEditTime: 2023-08-17 11:39:17
* @LastEditors: Jackie
* @Description: scroll滚动 - 循环滚动,不超出不滚动
* @FilePath: /vue3-swiper-demo/src/components/scroll/Scroll13.vue
* @version:
-->
<template>
<div class="ticker-container">
<div class="ticker-viewer" ref="scrollWiewer">
<div
class="ticker-scroll"
ref="scrollContainer"
:class="{ 'scroll-animation': !shouldScroll }"
:style="{ animationDuration: animationDuration }"
>
<div
class="ticker-scroll-item"
:key="index"
v-for="(item, index) in items"
>
<span v-if="item">{{ item }} ${{ item }}</span>
</div>
</div>
<div
class="ticker-scroll"
v-if="shouldScroll"
:style="{ animationDuration: animationDuration }"
>
<div
class="ticker-scroll-item"
:key="index"
v-for="(item, index) in items"
>
<span v-if="item">{{ item }} ${{ item }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
const items = ref(Array.from({ length: 26 }, (_, index) => index + 1));
const shouldScroll = ref(false);
const scrollContainer = ref(null);
const scrollWiewer = ref(null);
// 计算动画持续时间
const animationDuration = computed(() => {
const itemCount = items.value.length;
// 根据元素数量调整倍数
const durationMultiplier = 2; // 调整这个值以适应您的需求
return `${itemCount * durationMultiplier}s`;
});
onMounted(() => {
const wiewerWidth = scrollWiewer.value.offsetWidth;
const containerWidth = scrollContainer.value.offsetWidth;
const contentWidth = scrollContainer.value.scrollWidth;
console.log(containerWidth, wiewerWidth, containerWidth > wiewerWidth);
shouldScroll.value = containerWidth > wiewerWidth;
});
onUnmounted(() => {
shouldScroll.value = false;
});
</script>
<style lang="scss" scoped>
.ticker-container {
align-items: center;
background: linear-gradient(90deg, #86eef1, #bcff2f 138.82%);
display: flex;
height: 50px;
white-space: nowrap;
width: 100%;
.ticker-viewer {
align-items: center;
cursor: pointer;
display: flex;
height: 100%;
overflow: hidden;
position: relative;
width: 100%;
.ticker-scroll {
/* opacity: 0; */
transition: all linear;
will-change: transform, opacity;
/* animation: tickerScroll 20s linear infinite normal; */
animation-name: tickerScroll;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: normal;
transform: translateX(0);
/* 没有超出不滚动 */
&.scroll-animation {
animation: none; // stopScroll 1s linear 1 normal;
transform: translateX(0);
}
.ticker-scroll-item {
display: inline-block;
font-size: 18px;
line-height: 22px;
color: #fff;
padding: 0 24px;
}
}
}
}
@keyframes tickerScroll {
0% {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(-100%);
}
}
@keyframes stopScroll {
0% {
transform: translateX(0);
}
}
@keyframes positionScroll {
0% {
transform: translateX(3.33333333%);
}
to {
transform: translateX(0);
}
}
</style>