前端开发中涉及到内容滚动常用的方法指令

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插件,多适用于数组循环遍历展示,但上一个循环未完成之前,下一个循环的内容中,鼠标不能执行点击操作。

相关推荐
帅帅在睡觉28 分钟前
组件的创建与挂载
javascript·vue.js·elementui
幽络源小助理31 分钟前
基于SpringBoot+Vue的实验室管理系统源码 | 教育类JavaWeb项目免费下载 – 幽络源
vue.js·spring boot·后端
qq_4061761434 分钟前
JavaScript闭包:从底层原理到实战
开发语言·前端·javascript
冰暮流星34 分钟前
javascript之Math对象——绝对值,开次方,四舍五入
前端·javascript
啊啊啊啊懒37 分钟前
vite创建完项目之后vue文件中有标签报错
前端·javascript·vue.js
心机boy2291 小时前
CSS3网格布局、过渡及2D效果
前端·javascript·css3
oak隔壁找我1 小时前
使用 json-server 快速创建一个完整的 REST API
前端·javascript
laplace01231 小时前
Part 4. LangChain 1.0 Agent 开发流程(Markdown 笔记)
前端·javascript·笔记·python·语言模型·langchain
Aliex_git1 小时前
性能优化 - 渲染优化
前端·javascript·笔记·学习·性能优化·html
千寻girling2 小时前
面试官 : “ 说一下 Vue 的 8 个生命周期钩子都做了什么 ? ”
前端·vue.js·面试