1.表格内容滚动,标题置顶,内容滚动,鼠标可暂停,可点击
/**
* v-tableScroll el-table自动滚动
*/
export default {
inserted(el, binding) {
const tableBodyWrapper = el.querySelector('.el-table__body-wrapper')
const tableBody = el.querySelector('.el-table__body')
if (!tableBodyWrapper || !tableBody) return
// 获取配置参数
const config = Object.assign({
speed: 100, // 滚动速度,数值越大滚动越慢
direction: 'top', // 滚动方向:top/left
hoverStop: true, // 鼠标悬停时是否停止滚动
interval: 40 // 定时器间隔(ms)
}, binding.value || {})
let timer = null
let scrollStep = 1
// 开始滚动
const startScroll = () => {
if (timer) clearInterval(timer)
timer = setInterval(() => {
if (config.direction === 'top') {
// 垂直滚动
// 检测是否是宽屏(内容不足以滚动)
const isWideScreen = tableBody.scrollHeight <= tableBodyWrapper.clientHeight
if (isWideScreen) {
// 宽屏环境下:强制启用滚动
if (tableBodyWrapper.scrollTop >= tableBody.scrollHeight) {
tableBodyWrapper.scrollTop = 0;
} else {
tableBodyWrapper.scrollTop += Math.max(scrollStep, 1);
}
} else {
// 正常滚动逻辑
if (tableBodyWrapper.scrollTop >= tableBody.scrollHeight - tableBodyWrapper.clientHeight) {
tableBodyWrapper.scrollTop = 0
} else {
tableBodyWrapper.scrollTop += scrollStep
}
}
} else {
// 水平滚动
const isWideScreen = tableBody.scrollWidth <= tableBodyWrapper.clientWidth;
if (isWideScreen) {
// 宽屏环境下:强制启用滚动
if (tableBodyWrapper.scrollLeft >= tableBody.scrollWidth) {
tableBodyWrapper.scrollLeft = 0;
} else {
tableBodyWrapper.scrollLeft += Math.max(scrollStep, 1);
}
} else {
// 正常滚动逻辑
if (tableBodyWrapper.scrollLeft >= tableBody.scrollWidth - tableBodyWrapper.clientWidth) {
tableBodyWrapper.scrollLeft = 0
} else {
tableBodyWrapper.scrollLeft += scrollStep
}
}
}
}, config.interval)
}
// 停止滚动
const stopScroll = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
// 初始化滚动
startScroll()
// 鼠标悬停事件
if (config.hoverStop) {
tableBodyWrapper.addEventListener('mouseenter', stopScroll)
tableBodyWrapper.addEventListener('mouseleave', startScroll)
}
// 保存清理函数到元素上,方便unbind时调用
el._autoScrollCleanup = () => {
stopScroll()
if (config.hoverStop) {
tableBodyWrapper.removeEventListener('mouseenter', stopScroll)
tableBodyWrapper.removeEventListener('mouseleave', startScroll)
}
}
},
unbind(el) {
if (el._autoScrollCleanup) {
el._autoScrollCleanup()
}
}
}
表格引入v-tableScroll指令,注:表格要设置高度
<el-table
v-loading="loading"
:data="propertyList"
header-cell-class-name="table-header-style"
row-class-name="table-row-style"
stripe
size="mini"
v-tableScroll="config"
height="100%"
>
<el-table-column label="序号" width="50" type="index">
</el-table-column>
<template v-for="(column, childIndex) in propertyColumn">
<el-table-column
:prop="column.propName"
:width="column.tWidth ? column.tWidth : ''"
:label="column.label"
:show-overflow-tooltip="column.tooltip"
:key="childIndex"
:align="column.align ? column.align : 'center'"
>
</el-table-column>
</template>
</el-table>
config配置
config: {
speed: 100, // 滚动速度,数值越大滚动越慢
direction: 'top', // 滚动方向:top/left
hoverStop: true, // 鼠标悬停时是否停止滚动
interval: 40 // 定时器间隔(ms)
},
2.仿marquee滚动指令,可暂停,可循环滚动,多适用于一整块内容滚动展示
/**
* 滚动展示指令(无缝循环版本)
* 使用方式:
* <div v-scroll-pause="{ speed: 50, direction: 'vertical', gap: 20 }">
* 你的内容
* </div>
*/
export default {
bind(el, binding) {
const options = binding.value || {}
const speed = options.speed || 40 // 滚动速度,数值越大越快
const direction = options.direction || 'vertical' // 'vertical' 或 'horizontal'
const gap = options.gap || 20 // 内容间隔(像素)
const pauseOnHover = options.pauseOnHover !== false // 默认悬停暂停
// 容器样式
el.style.overflow = 'hidden'
el.style.position = 'relative'
// 保存原始内容
const originalHTML = el.innerHTML
// 创建外层包装
const wrapper = document.createElement('div')
wrapper.className = 'scroll-pause-wrapper'
// 创建三个相同的内容块用于无缝滚动
const content1 = document.createElement('div')
content1.className = 'scroll-content'
content1.innerHTML = originalHTML
const content2 = document.createElement('div')
content2.className = 'scroll-content'
content2.innerHTML = originalHTML
const content3 = document.createElement('div')
content3.className = 'scroll-content'
content3.innerHTML = originalHTML
// 根据方向设置样式
if (direction === 'horizontal') {
wrapper.style.display = 'flex'
wrapper.style.position = 'absolute'
wrapper.style.top = '0'
wrapper.style.left = '0'
content1.style.flexShrink = '0'
content2.style.flexShrink = '0'
content3.style.flexShrink = '0'
content1.style.marginRight = `${gap}px`
content2.style.marginRight = `${gap}px`
content3.style.marginRight = `${gap}px`
} else {
wrapper.style.position = 'absolute'
wrapper.style.top = '0'
wrapper.style.left = '0'
wrapper.style.width = '100%'
content1.style.marginBottom = `${gap}px`
content2.style.marginBottom = `${gap}px`
content3.style.marginBottom = `${gap}px`
}
wrapper.appendChild(content1)
wrapper.appendChild(content2)
wrapper.appendChild(content3)
// 清空原始内容并添加包装
el.innerHTML = ''
el.appendChild(wrapper)
// 动画相关变量
let animationId = null
let isPaused = false
let position = 0
let contentSize = 0 // 单个内容块的尺寸
let containerSize = 0 // 容器的尺寸
// 初始化尺寸
function initSizes() {
const content = content1
if (direction === 'vertical') {
containerSize = el.clientHeight
contentSize = content.scrollHeight + gap
wrapper.style.height = `${contentSize * 3}px`
} else {
containerSize = el.clientWidth
contentSize = content.scrollWidth + gap
wrapper.style.width = `${contentSize * 3}px`
}
}
// 创建动画
function createAnimation() {
let startTime = null
function animateStep(timestamp) {
if (!startTime) startTime = timestamp
if (!isPaused) {
// 计算经过的时间
const elapsed = timestamp - startTime
if (direction === 'vertical') {
// 垂直滚动:计算新的位置
position = -((elapsed * speed) / 1000) % contentSize
// 当position小于负的内容尺寸时,需要调整到开始位置
if (position < -contentSize) {
startTime = timestamp - ((position + contentSize) * 1000) / speed
position = -((timestamp - startTime) * speed) / 1000
}
wrapper.style.transform = `translateY(${position}px)`
} else {
// 水平滚动
position = -((elapsed * speed) / 1000) % contentSize
if (position < -contentSize) {
startTime = timestamp - ((position + contentSize) * 1000) / speed
position = -((timestamp - startTime) * speed) / 1000
}
wrapper.style.transform = `translateX(${position}px)`
}
}
animationId = requestAnimationFrame(animateStep)
}
return animateStep
}
// 更简单的动画实现
function simpleAnimate() {
if (!isPaused) {
if (direction === 'vertical') {
position -= speed / 60
// 当滚动超过一个内容的高度时,重置到开始位置
if (position <= -contentSize) {
position = 0
}
wrapper.style.transform = `translateY(${position}px)`
} else {
position -= speed / 60
if (position <= -contentSize) {
position = 0
}
wrapper.style.transform = `translateX(${position}px)`
}
}
animationId = requestAnimationFrame(simpleAnimate)
}
// 优化版本:使用CSS动画(性能更好)
function cssAnimation() {
// 计算动画持续时间
const duration = contentSize / speed // 单位:秒
// 移除之前的动画
wrapper.style.animation = 'none'
// 强制重绘
void wrapper.offsetWidth
// 设置动画
if (direction === 'vertical') {
wrapper.style.animation = `verticalScroll ${duration}s linear infinite`
} else {
wrapper.style.animation = `horizontalScroll ${duration}s linear infinite`
}
// 控制动画播放状态
if (isPaused) {
wrapper.style.animationPlayState = 'paused'
} else {
wrapper.style.animationPlayState = 'running'
}
}
// 动态创建CSS动画
function setupCSSAnimations() {
// 如果已经存在,先移除
const existingStyle = document.getElementById('scroll-pause-animations')
if (existingStyle) {
existingStyle.remove()
}
const style = document.createElement('style')
style.id = 'scroll-pause-animations'
if (direction === 'vertical') {
style.textContent = `
@keyframes verticalScroll {
0% { transform: translateY(0); }
100% { transform: translateY(-${contentSize}px); }
}
`
} else {
style.textContent = `
@keyframes horizontalScroll {
0% { transform: translateX(0); }
100% { transform: translateX(-${contentSize}px); }
}
`
}
document.head.appendChild(style)
}
// 初始化并开始动画
function initAndStart() {
initSizes()
// 如果内容高度小于容器高度,不需要滚动
if (contentSize <= containerSize && direction === 'vertical') {
// 重置为静态显示
wrapper.style.transform = 'none'
wrapper.style.position = 'relative'
wrapper.style.height = 'auto'
return
}
if (contentSize <= containerSize && direction === 'horizontal') {
wrapper.style.transform = 'none'
wrapper.style.position = 'relative'
wrapper.style.width = 'auto'
wrapper.style.display = 'flex'
wrapper.style.flexWrap = 'wrap'
return
}
// 使用CSS动画(性能更好)
setupCSSAnimations()
cssAnimation()
}
// 鼠标事件
if (pauseOnHover) {
el.addEventListener('mouseenter', () => {
isPaused = true
if (wrapper.style.animation) {
wrapper.style.animationPlayState = 'paused'
}
})
el.addEventListener('mouseleave', () => {
isPaused = false
if (wrapper.style.animation) {
wrapper.style.animationPlayState = 'running'
}
})
}
// 初始化
setTimeout(() => {
initAndStart()
}, 100)
// 窗口大小变化时重新计算
const resizeHandler = () => {
initAndStart()
}
// 使用防抖优化resize事件
let resizeTimeout
const debouncedResizeHandler = () => {
clearTimeout(resizeTimeout)
resizeTimeout = setTimeout(resizeHandler, 200)
}
window.addEventListener('resize', debouncedResizeHandler)
// 保存清理函数
el._scrollPauseCleanup = () => {
if (animationId) {
cancelAnimationFrame(animationId)
}
window.removeEventListener('resize', debouncedResizeHandler)
// 移除动态创建的样式
const style = document.getElementById('scroll-pause-animations')
if (style) {
style.remove()
}
}
},
update(el, binding) {
// 当绑定值变化时,重新初始化
const newOptions = binding.value || {}
const oldOptions = binding.oldValue || {}
// 如果选项发生变化,重新绑定
if (JSON.stringify(newOptions) !== JSON.stringify(oldOptions)) {
// 清理旧的
if (el._scrollPauseCleanup) {
el._scrollPauseCleanup()
}
// 重新绑定
this.bind(el, binding)
}
},
unbind(el) {
if (el._scrollPauseCleanup) {
el._scrollPauseCleanup()
}
}
}
前端引入, 注:如果涉及多模块切换,切换后内容没变化,可加key,
<div v-scroll-pause="{speed: 20,direction: 'vertical',gap: 120}"
class="scroll-box vertical-scroll" :key="dataInfo.regionIntroduce">
滚动内容.....
</div>
3.vue-seamless-scroll插件,多适用于数组循环遍历展示,但上一个循环未完成之前,下一个循环的内容中,鼠标不能执行点击操作。