vue 自制列表,循环滚动

需求人员表示,超过高度的表格内容需要滚动展示,所以效果图如下:

20250327:新增了config配置,可以调整 scrollSpeed、bottomPause、smoothReturn、minDelta。

const config = {
    scrollSpeed: 0.5,      // 可设置为 0.1~0.9
    bottomPause: 2000,     // 到底部后的暂停时间
    smoothReturn: false,    // 启用平滑返回
    minDelta: 0.1         // 最小移动单位
};

自定义列表样式,主要是通过flex布局,控制 类th 与 类td 的宽度保持一致,标签结构还是参考了table的结构,由thead与tbody包裹tr再包裹最小单元th,td,但是自定义的列表在样式修改、结构调整上会更加灵活。

.th {
    &:nth-child(1) {
        width: 60px;
    }

    &:nth-child(2) {
        width: 90px;
    }

    &:nth-child(3) {
        width: 0;
        flex: 1;
    }

    &:nth-child(4) {
        width: 90px;
    }
}

.td {
    &:nth-child(1) {
        width: 60px;
    }

    &:nth-child(2) {
        width: 90px;
    }

    &:nth-child(3) {
        width: 0;
        flex: 1;
    }

    &:nth-child(4) {
        width: 90px;
    }
}

this.stopAutoScroll() 与 this.startAutoScroll() 都是AI生成的JS代码修改而来

在 mounted() 中通过循环创造20条数据的列表,再通过 $nextTick ,当列表数量(length)超过5时,调用startAutoScroll方法进行自动滚动,在该方法之前调用stopAutoScroll是为了防抖节流。如果有主要思路想法,可以借助通义千问等AI工具进行JS代码的快速生成,节约时间。

mounted() {
    for (let i = 0; i < 20; i++) {
        let obj = {
            id: 'tableData00' + i,
            td1: i + 1,
            td2: 'td2',
            td3: i % 3 == 0 ? '测试测试测试' : i % 3 == 1 ? '测试测试' : '测试',
            td4: '99'
        }
        this.tableData.push(obj)
    }

    this.$nextTick(function () {
        // 当列表超过5条,自动滚动,可自行修改
        if (this.tableData.length > 5) {
            // 自动滚动
            this.stopAutoScroll();
            this.startAutoScroll();
        }
    })
},

【完整代码】

<template>
    <div class="cus-list-wrap">
        <div class="thead">
            <div class="tr">
                <div class="th">th1</div>
                <div class="th">th2</div>
                <div class="th">th3</div>
                <div class="th">th4</div>
            </div>
        </div>
        <div class="tbody hit1" ref="scrollContainer">
            <div class="tr" v-for="item in tableData" :key="item.id">
                <div class="td">
                    {{ item.td1 }}
                </div>
                <div class="td">
                    {{ item.td2 }}
                </div>
                <div class="td">
                    {{ item.td3 }}
                </div>
                <div class="td">
                    {{ item.td4 }}
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            tableData: [],
            isScrollingDown: true,
            scrollAnimationFrameId: null,
            isAutoScrollRunning: false,
        }
    },
    mounted() {
        for (let i = 0; i < 20; i++) {
            let obj = {
                id: 'tableData00' + i,
                td1: i + 1,
                td2: 'td2',
                td3: i % 3 == 0 ? '测试测试测试' : i % 3 == 1 ? '测试测试' : '测试',
                td4: '99'
            }
            this.tableData.push(obj)
        }

        this.$nextTick(function () {
            // 当列表超过5条,自动滚动,可自行修改
            if (this.tableData.length > 5) {
                // 自动滚动
                this.stopAutoScroll();
                this.startAutoScroll();
            }
        })
    },
    methods: {
        // 自动滚动
        startAutoScroll() {
            if (this.isAutoScrollRunning) return;

            this.isAutoScrollRunning = true;
            const scrollContainer = this.$refs.scrollContainer;
            const config = {
                scrollSpeed: 0.5,      // 可设置为 0.1~0.9
                bottomPause: 2000,     // 到底部后的暂停时间
                smoothReturn: false,    // 启用平滑返回
                minDelta: 0.1         // 最小移动单位
            };

            // 平滑滚动方法
            const smoothScrollTo = (target) => {
                if (!config.smoothReturn) {
                    scrollContainer.scrollTop = target;
                    return;
                }

                const start = scrollContainer.scrollTop;
                const duration = 800;
                let startTime = null;

                const animate = (timestamp) => {
                    if (!startTime) startTime = timestamp;
                    const progress = timestamp - startTime;
                    const percent = Math.min(progress / duration, 1);

                    // 缓动函数:easeOutQuad
                    const eased = 1 - Math.pow(1 - percent, 2);
                    scrollContainer.scrollTop = start + (target - start) * eased;

                    if (percent < 1) {
                        requestAnimationFrame(animate);
                    }
                };

                requestAnimationFrame(animate);
            };

            const autoScroll = () => {
                if (!this.isAutoScrollRunning) return;

                const { scrollTop, clientHeight, scrollHeight } = scrollContainer;
                const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
                const isAtTop = scrollTop <= 0;

                // 累积小数增量
                this.accumulatedDelta += config.scrollSpeed;
                const actualMove = Math.floor(this.accumulatedDelta);
                this.accumulatedDelta -= actualMove;

                if (this.isScrollingDown) {
                    if (isAtBottom) {
                        this.isScrollingDown = false;
                        this.accumulatedDelta = 0; // 重置累积值
                        smoothScrollTo(0);
                        setTimeout(() => {
                            if (this.isAutoScrollRunning) {
                                this.isScrollingDown = true;
                                autoScroll();
                            }
                        }, config.bottomPause);
                        return;
                    }
                    scrollContainer.scrollTop += actualMove;
                } else {
                    if (isAtTop) {
                        this.isScrollingDown = true;
                        this.accumulatedDelta = 0;
                        autoScroll();
                        return;
                    }
                    scrollContainer.scrollTop -= actualMove;
                }

                // 根据最小移动单位优化性能
                if (actualMove >= config.minDelta || Math.abs(this.accumulatedDelta) >= config.minDelta) {
                    this.scrollAnimationFrameId = requestAnimationFrame(autoScroll);
                } else {
                    this.scrollAnimationFrameId = requestAnimationFrame(autoScroll);
                }
            };

            autoScroll();
        },

        stopAutoScroll() {
            this.isAutoScrollRunning = false;
            cancelAnimationFrame(this.scrollAnimationFrameId);
        }
    },
    beforeDestroy() {
        // 清除定时器以避免内存泄漏
        this.stopAutoScroll();
    },
}
</script>

<style lang="less" scoped>
.cus-list-wrap {
    margin: 0 16px;
    padding-top: 4px;
}

.hit1 {
    overflow-x: hidden;
    overflow-y: auto;
    height: 400px;
}

.th {
    &:nth-child(1) {
        width: 60px;
    }

    &:nth-child(2) {
        width: 90px;
    }

    &:nth-child(3) {
        width: 0;
        flex: 1;
    }

    &:nth-child(4) {
        width: 90px;
    }
}

.td {
    &:nth-child(1) {
        width: 60px;
    }

    &:nth-child(2) {
        width: 90px;
    }

    &:nth-child(3) {
        width: 0;
        flex: 1;
    }

    &:nth-child(4) {
        width: 90px;
    }
}

.thead {
    .tr {
        display: flex;
        align-items: center;
        height: 30px;
    }

    .th {
        text-align: center;
        font-size: 14px;
        color: #666;
    }
}

.tbody {

    .tr {
        display: flex;
        align-items: center;
        background: #D3DCE6;

        &:nth-child(odd) {
            background: #E9EEF3;
        }
    }

    .td {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 40px;
        font-size: 12px;
        color: #666;
    }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值