Vue3中实现自然滚动表格列表,可调整滚动快慢

实现效果如图所示,可以调整滚动的快慢,可以实现内容高度不够的时候停止滚动的效果。

1.实现原理:

创建一个dom为ul,赋值为当前列表数据,然后拷贝这个dom赋值给第二个ul,然后判断屏幕高度跟滚动高度对比,利用requestAnimationFrame动画实现滚动

2.注意事项:

这是基于react的滚动列表改造过来的,所以本身留了render口子,可以自定义表内的内容,不只是文字,但是对于vue3跟jsx的结合,我还是不是很熟,所以目前还没有用到render写法

3.对比:

最开始实现我是想用vue3-seamless-scroll这个插件的,但是使用起来发现,首先它不支持数据少的情况下自动停止滚动,需要传入step为0,感觉不是很方便(也可能是我没有很会用这个插件),所以感觉直接写了一个。

组件代码:

javascript 复制代码
<template>
    <div
        class="scrollContainer"
        :key="currentTime"
        :style="{ height: `${height}px` }"
    >
        <div
            class="scrollHead"
            :style="{
                height: headerHeight + 'px',
            }"
        >
            <div
                v-for="l in columns"
                :key="l.key"
                :style="{ width: `${l.width}px` }"
            >
                {{ l.title }}
            </div>
        </div>
        <ul
            class="scrollBody"
            ref="wrapperDom"
            :style="{ height: `${height - headerHeight}px` }"
        >
            <ul
                ref="childDom1"
                @mouseenter="handleEnter"
                @mouseleave="handleLeave"
            >
                <li
                    v-for="(l, i) in dataSource"
                    :data-key="rowKey ? l[rowKey] : `list${i}`"
                    :key="rowKey ? l[rowKey] : `list${i}`"
                    :style="{ height: `${rowHeight}px` }"
                >
                    <div
                        v-for="(p, c) in columns"
                        :key="`p${c}`"
                        :style="getStyle(p, l)"
                        @click="
                            (e) => {
                                e.stopPropagation()
                                onCellClick(l, p)
                                onRowClick?.(l)
                            }
                        "
                    >
                        {{ p?.render?.(i, l, l[p.key]) || l[p.key] }}
                    </div>
                </li>
            </ul>
            <ul ref="childDom2"></ul>
        </ul>
    </div>
</template>

<script setup lang="ts">
import { onMounted, watch, ref, onBeforeUnmount, computed, nextTick } from 'vue'

interface ViewProps {
    height: number
    dataSource: Record<string, any>[]
    columns: TableColumn[]
    headerHeight?: number
    rowHeight?: number
    onRowClick?: (l: Record<string, any>) => void
    rowKey?: string
    scroll?: boolean
}

export interface TableColumn {
    key: string
    title: string
    width: number
    render?: (index: number, data: Record<string, any>, text: any) => any
    onClick?: (data: Record<string, any>) => void
}

const props = defineProps<ViewProps>()
const { height, columns, rowHeight = 27.5, headerHeight = 36, rowKey } = props

const wrapperDom = ref<any>()
const childDom1 = ref<any>()
const childDom2 = ref<any>()
const currentTime = ref(new Date().getTime())
let count = 0
let reqAnimation: number

onMounted(() => {
    nextTick(() => {
        reqAnimation = window.requestAnimationFrame(taskStart)
    })
})
onBeforeUnmount(() => {
    handleEnter()
})
const dataSource = computed(() => {
    console.log('dataSource', dataSource)
    return props.dataSource
})
watch(
    () => props.dataSource,
    () => {
        currentTime.value = new Date().getTime()
    }
)

const getStyle = (p, l) => {
    let pStyle = { width: `${p.width}px` }
    if (l.lineColor) {
        pStyle['color'] = l.lineColor
    }
    return pStyle
}

var startTime = null,
    stepInMs = 100,
    drawCount = 0
const taskStart = (timestamp: any) => {
    var progress
    if (startTime === null) {
        startTime = timestamp
    }
    progress = timestamp - startTime!
    if (progress > stepInMs) {
        startTime = timestamp
        if (
            childDom1.value?.clientHeight >= wrapperDom.value?.clientHeight &&
            childDom2.value?.clientHeight < 10
        ) {
            childDom2.value.innerHTML = childDom1.value.innerHTML
        }
        if (wrapperDom.value?.scrollTop >= childDom1.value?.scrollHeight) {
            wrapperDom.value.scrollTop = 0
            count = 0
        } else {
            count += 1
            wrapperDom.value.scrollTop = count
        }
    }
    if (props.scroll) {
        reqAnimation = window.requestAnimationFrame(taskStart)
    }
}

const handleEnter = () => {
    window.cancelAnimationFrame(reqAnimation)
}
const handleLeave = () => {
    reqAnimation = window.requestAnimationFrame(taskStart)
}
const onCellClick = (l: Record<string, any>, p: TableColumn) => {
    p?.onClick?.(l)
}
</script>

<style lang="scss" scoped>
.scrollContainer {
    width: 100%;

    div {
        text-align: center;
        display: inline-block;
        margin: 0;
        font-size: 14px;
        font-weight: normal;
        font-stretch: normal;
        letter-spacing: 0;
        opacity: 0.9;
    }

    .scrollHead {
        display: flex;
        align-items: center;
        background-color: rgba(33, 60, 93, 0.55);

        div {
            font-size: 14px;
            font-stretch: normal;
            letter-spacing: 0;
            font-family: MicrosoftYaHei, sans-serif;
            font-weight: bold;
            color: #ffffff;
            opacity: 0.47;
        }
    }

    .scrollBody {
        overflow-y: scroll;
        width: 100%;
        padding: 0;
        scrollbar-width: none;
        -ms-overflow-style: none;

        ul {
            height: auto;
            padding: 0;
            margin: 0;
        }

        li {
            list-style: none;
            position: relative;
            cursor: pointer;
            display: flex;
            height: 36px;
            color: #fff;
            align-items: center;
        }

        li div {
            line-height: 36px;
            color: #24acef;
            white-space: nowrap; /* 文本不换行 */
            overflow: hidden; /* 溢出部分隐藏 */
            text-overflow: ellipsis; /* 溢出部分用"..."代替 */
        }

        li:hover {
            background: rgba(43, 143, 171, 0.52);
            > div {
                color: #fff;
            }
        }

        &::-webkit-scrollbar {
            display: none;
        }

        li:nth-child(even) {
            background-color: rgba(43, 143, 171, 0.13);
        }

        li:nth-child(even):hover {
            background: rgba(43, 143, 171, 0.52);
            color: #fff;
        }
    }
}
</style>

父组件调用:

javascript 复制代码
<template>
    <div>
        <ScrollTable
            :height="300"
            :dataSource="dataSource"
            :columns="columns"
            :scroll="true"
        ></ScrollTable>
    </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import ScrollTable from '../../components/ScrollTable.vue'

const dataSource = [
    {
        name: '张三',
        age: 18,
        gender: '男',
        address: '北京市',
        phone: '12345678901',
    },
    {
        name: '李四',
        age: 20,
        gender: '女',
        address: '上海市',
        phone: '12345678902',
    },
    {
        name: '王五',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '赵六',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '王思聪',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '王健林',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马云',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马化腾',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马1',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马2',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马3',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马4',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马5',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马6',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马7',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马8',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
    {
        name: '马9',
        age: 22,
        gender: '男',
        address: '广州市',
        phone: '12345678903',
    },
]

const columns = [
    {
        title: '姓名',
        dataIndex: 'name',
        width: 50,
        key: 'name',
    },
    {
        title: '年龄',
        dataIndex: 'age',
        width: 50,
        key: 'age',
    },
    {
        title: '性别',
        dataIndex: 'gender',
        width: 50,
        key: 'gender',
    },
    {
        title: '地址',
        dataIndex: 'address',
        width: 50,
        key: 'address',
    },
    {
        title: '电话',
        dataIndex: 'phone',
        width: 50,
        key: 'phone',
    },
]
</script>

<style lang="scss" scoped></style>
相关推荐
y先森35 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy35 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891138 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端