CSS实现web无缝横向滚动,hover时停止滚动

254 阅读1分钟

背景:web网页,实现列表项无缝横向滚动,hover时,列表项可以上移并且滚动暂停

正常滚动 image.png

hover时 image.png

列表项子组件scss

.excellent-cases__item {
    flex: 0 0 auto; // 不会放大也不缩小
    margin: 0 10px; // 左右平分间距
    width: 338px;
    height: 433px;
    border-radius: 12px;
    transition-property: all !important;
    transition-duration: 0.3s !important;
    transition-timing-function: ease !important;
    /* 平滑过渡效果 */
    box-shadow: -4px 0px 7.9px 0px rgba(0, 0, 0, 0.05);
    &:hover {
        /* 上移 */
        transform: translateY(-20px);
    }
}

父组件html,ts

<template>
    <div class="excellent-cases">
        <div class="excellent-cases__content" style="--t: 60s" v-if="dataList.length">
            <div class="excellent-cases__content-list">
                <ExcellentCasesItem :sourceData="dataList" />
            </div>
            <div class="excellent-cases__content-list">
                <ExcellentCasesItem :sourceData="dataList" />
            </div>
        </div>
    </div>
</template>
<script lang="ts">
    export default {
        name: 'ExcellentCases',
    }
</script>
<script setup lang="ts">
    import { ref, watch } from 'vue'
    import ExcellentCasesItem from './excellent-cases-item/index.vue'

    const props = defineProps({
        sourceData: {
            type: Array<{ [key: string]: any }>,
            default: () => [],
        },
    })

    const dataList = ref([]) // 数据源

    watch(
        () => props.sourceData,
        newVal => {
           dataList.value = initData(newVal)
        },
        {
            deep: true,
            immediate: true,
        }
    )

    // 初始化数据
    const initData = (list: []) => {
        const listLen = list.length
        if (!listLen) {
            return []
        }
        // 若小于8个,需要补充到长度为8
        if (listLen < 8) {
            return fillList(list, 8)
        }
        // 超出8个,但为奇数 => 补充到偶数
        if (listLen % 2 !== 0) {
            return fillList(list, listLen + 1)
        }
        return list
    }

    // 填充数组
    const fillList = (list: [], minCount = 8) => {
        // 计算需要复制的次数
        const timesToCopy = minCount / list.length

        // 创建新数组并填充内容
        const newArray = []
        for (let i = 0; i < timesToCopy; i++) {
            newArray.push(...list)
        }

        // 如果新数组的长度超过了minCount,截取前minCount个元素
        if (newArray.length > minCount) {
            newArray.length = minCount // 截取前minCount个元素
        }
        return newArray
    }
</script>

<style lang="scss" scoped>
    @import './index.scss';
</style>

父组件scss

.excellent-cases {
    margin-bottom: 100px;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    .excellent-cases__content {
        padding-top: 60px;
        display: flex;
        width: 100%;
        overflow: hidden;
        .excellent-cases__content-list {
            animation: animate var(--t) linear infinite;
            animation-delay: calc(var(--t) * -1);
            display: flex;
            @keyframes animate {
                0% {
                    transform: translateX(100%);
                }

                100% {
                    transform: translateX(-100%);
                }
            }
            &:nth-child(2) {
                animation: animate2 var(--t) linear infinite;
                animation-delay: calc(var(--t) / -2);
            }
            @keyframes animate2 {
                0% {
                    transform: translateX(0);
                }

                100% {
                    transform: translateX(-200%);
                }
            }
        }
    }
    // 暂停
    .excellent-cases__content:hover > .excellent-cases__content-list {
        animation-play-state: paused !important;
    }
}