Vue3 + TypeScript 实现 PC 端鼠标横向拖动滚动

功能说明

​​拖动功能​​:

  • 鼠标按下时记录初始位置和滚动位置
  • 拖动过程中计算移动距离并更新滚动位置
  • 松开鼠标后根据速度实现惯性滚动

​​滚动控制​​:

  • 支持鼠标滚轮横向滚动(通过 wheel 事件)
  • 自动边界检测防止滚动超出内容范围

实现代码

html 复制代码
<template>
  <div 
    ref="scrollContainer" 
    class="horizontal-scroll-container"
    @mousedown="startDrag"
    @mousemove="onDrag"
    @mouseup="stopDrag"
    @mouseleave="stopDrag"
  >
    <div class="scroll-content">
      <div v-for="(item, index) in items" :key="index" class="item">
        {{ item }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'

// 定义滚动容器引用
const scrollContainer = ref<HTMLElement | null>(null)

// 定义滚动内容数据
const items = ref<string[]>(Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`))

// 拖动状态变量
let isDragging = false
let startX = 0
let scrollLeft = 0
let lastTime = 0
let velocity = 0

// 开始拖动
const startDrag = (e: MouseEvent): void => {
  if (!scrollContainer.value) return
  
  isDragging = true
  startX = e.pageX - scrollContainer.value.getBoundingClientRect().left
  scrollLeft = scrollContainer.value.scrollLeft
  lastTime = performance.now()
  scrollContainer.value.style.cursor = 'grabbing'
}

// 拖动中
const onDrag = (e: MouseEvent): void => {
  if (!isDragging || !scrollContainer.value) return
  
  const x = e.pageX - scrollContainer.value.getBoundingClientRect().left
  const walk = (x - startX) * 1.5 // 调整滚动速度系数
  scrollContainer.value.scrollLeft = scrollLeft - walk
  
  // 计算速度(用于惯性滚动)
  const now = performance.now()
  velocity = (x - startX) / (now - lastTime)
  lastTime = now
}

// 停止拖动
const stopDrag = (): void => {
  if (!isDragging || !scrollContainer.value) return
  
  isDragging = false
  if (Math.abs(velocity) > 0.1) {
    requestAnimationFrame(inertiaScroll)
  }
  scrollContainer.value.style.cursor = 'grab'
}

// 惯性滚动
const inertiaScroll = (): void => {
  if (!scrollContainer.value || Math.abs(velocity) < 0.01) return
  
  scrollContainer.value.scrollLeft += velocity * 10
  velocity *= 0.95 // 摩擦系数
  
  requestAnimationFrame(inertiaScroll)
}

// 边界检测
const checkBounds = (): void => {
  if (!scrollContainer.value) return
  
  const containerWidth = scrollContainer.value.clientWidth
  const contentWidth = scrollContainer.value.scrollWidth
  
  if (scrollContainer.value.scrollLeft < 0) {
    scrollContainer.value.scrollLeft = 0
  } else if (scrollContainer.value.scrollLeft > contentWidth - containerWidth) {
    scrollContainer.value.scrollLeft = contentWidth - containerWidth
  }
}

// 鼠标滚轮横向滚动
const handleWheel = (e: WheelEvent): void => {
  if (!scrollContainer.value) return
  
  e.preventDefault()
  scrollContainer.value.scrollLeft += e.deltaY
  checkBounds()
}

// 生命周期钩子
onMounted(() => {
  if (scrollContainer.value) {
    scrollContainer.value.addEventListener('wheel', handleWheel, { passive: false })
  }
})

onUnmounted(() => {
  if (scrollContainer.value) {
    scrollContainer.value.removeEventListener('wheel', handleWheel)
  }
})
</script>

<style scoped>
.horizontal-scroll-container {
  width: 100%;
  overflow-x: auto;
  white-space: nowrap;
  cursor: grab;
  height: 220px; /* 确保容器有固定高度 */
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 10px;
  box-sizing: border-box;
}

.horizontal-scroll-container:active {
  cursor: grabbing;
}

.scroll-content {
  display: inline-block;
}

.item {
  display: inline-block;
  width: 200px;
  height: 200px;
  margin-right: 10px;
  background: #f0f0f0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  border: 1px solid #ddd;
  box-sizing: border-box;
  border-radius: 4px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.item:hover {
  transform: scale(1.02);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
相关推荐
我是小路路呀3 小时前
element级联选择器:已选中一个二级节点,随后又点击了一个一级节点(仅浏览,未确认选择),此时下拉框失去焦点并关闭
javascript·vue.js·elementui
JIngJaneIL3 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
澄江静如练_4 小时前
列表渲染(v-for)
前端·javascript·vue.js
Loo国昌4 小时前
Vue 3 前端工程化:架构、核心原理与生产实践
前端·vue.js·架构
前端白袍5 小时前
Vue:如何实现一个具有复制功能的文字按钮?
前端·javascript·vue.js
new code Boy5 小时前
escape谨慎使用
前端·javascript·vue.js
爱分享的鱼鱼9 小时前
对比理解 Vue 响应式 API:data(), ref、reactive、computed 与 watch 详解
前端·vue.js
JS_GGbond9 小时前
【性能优化】给Vue应用“瘦身”:让你的网页快如闪电的烹饪秘籍
前端·vue.js
刘一说10 小时前
Vue Router:官方路由解决方案解析
前端·javascript·vue.js
计算机学姐10 小时前
基于php的摄影网站系统
开发语言·vue.js·后端·mysql·php·phpstorm