You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HTML/CSS/JS开发视频触摸滑块 拖拽冻结问题修复

视频触摸滑块故障修复方案

故障核心原因

  • 原代码仅针对<img>标签设计,替换为<video>后,视频元素默认会抢占触摸、鼠标事件优先级,未做适配会导致拖动事件丢失,isDragging状态异常卡住
  • 原代码将拖动、释放事件仅绑定在单个slide元素上,当拖动操作滑出当前slide范围时,事件监听无法触发,状态无法重置
  • 未统一阻止所有媒体元素的默认拖拽行为,混排图片、视频时默认交互会打断滑块逻辑
  • 未做视频播放状态管控,切走的视频后台播放会干扰交互、占用资源

修复后完整JS代码

// 提前声明滑块容器(原示例中存在该声明,迁移时易遗漏)
const slider = document.querySelector('.slider-container')
const slides = Array.from(document.querySelectorAll('.slide'))

let isDragging = false,
  startPos = 0,
  currentTranslate = 0,
  prevTranslate = 0,
  animationID = 0,
  currentIndex = 0

slides.forEach((slide, index) => {
  // 统一处理slide内所有媒体元素的默认拖拽行为
  const mediaList = slide.querySelectorAll('video, img')
  mediaList.forEach(media => {
    media.addEventListener('dragstart', e => e.preventDefault())
  })

  // 仅在slide上绑定拖动起始事件
  slide.addEventListener('touchstart', touchStart(index))
  slide.addEventListener('mousedown', touchStart(index))
})

// 拖动、释放事件绑定到window,避免滑出元素范围后事件丢失
window.addEventListener('touchend', touchEnd)
window.addEventListener('mouseup', touchEnd)
window.addEventListener('touchmove', touchMove, { passive: false })
window.addEventListener('mousemove', touchMove)

// 禁用全局右键菜单
window.oncontextmenu = function (event) {
  event.preventDefault()
  event.stopPropagation()
  return false
}

function touchStart(index) {
  return function (event) {
    // 点击视频控制栏区域时不触发滑块拖动,保留视频正常控制能力
    if (event.target.tagName === 'VIDEO' && event.target.controls) {
      const rect = event.target.getBoundingClientRect()
      const clickY = event.clientY - rect.top
      if (clickY > rect.height - 40) return
    }

    currentIndex = index
    startPos = getPositionX(event)
    isDragging = true

    animationID = requestAnimationFrame(animation)
    slider.classList.add('grabbing')
  }
}

function touchEnd() {
  if (!isDragging) return
  isDragging = false
  cancelAnimationFrame(animationID)

  const movedBy = currentTranslate - prevTranslate

  if (movedBy < -100 && currentIndex < slides.length - 1) currentIndex += 1
  if (movedBy > 100 && currentIndex > 0) currentIndex -= 1

  setPositionByIndex()
  updateVideoPlayState()

  slider.classList.remove('grabbing')
}

function touchMove(event) {
  if (!isDragging) return
  // 阻止默认行为,避免页面滚动、视频原生交互打断拖动
  event.preventDefault()
  const currentPosition = getPositionX(event)
  currentTranslate = prevTranslate + currentPosition - startPos
}

function getPositionX(event) {
  return event.type.includes('mouse') ? event.pageX : event.touches[0].clientX
}

function animation() {
  setSliderPosition()
  if (isDragging) requestAnimationFrame(animation)
}

function setSliderPosition() {
  slider.style.transform = `translateX(${currentTranslate}px)`
}

function setPositionByIndex() {
  currentTranslate = currentIndex * -window.innerWidth
  prevTranslate = currentTranslate
  setSliderPosition()
}

// 管控视频播放状态:仅当前激活滑块的视频可播放,其余暂停
function updateVideoPlayState() {
  slides.forEach((slide, idx) => {
    const video = slide.querySelector('video')
    if (!video) return
    if (idx === currentIndex) {
      video.currentTime = 0
      // 需自动播放当前视频可解开下一行注释,注意移动端自动播放要求视频带muted属性
      // video.play()
    } else {
      video.pause()
    }
  })
}

// 初始化视频状态
updateVideoPlayState()

配套CSS适配

在原有样式基础上补充以下规则,避免媒体元素干扰拖动:

.slide {
  user-select: none;
  -webkit-user-select: none;
}
/* 如需完全禁用视频原生交互可开启下面规则,拖动更顺滑但无法直接点击视频控件 */
/*
.slide video {
  pointer-events: none;
}
*/

关键调整说明

  • 事件绑定范围调整:将移动、释放事件从单个slide移到window绑定,彻底解决拖出元素范围后状态冻结的问题
  • 媒体适配:统一处理图片、视频的默认拖拽行为,touchmove阶段阻止默认事件,屏蔽原生交互干扰
  • 交互兼容:新增视频控制栏点击判断,保留视频正常的暂停/播放/音量控制能力,不会误触发滑块拖动
  • 状态管控:新增视频播放状态自动切换逻辑,切换滑块时自动暂停非当前页视频,避免资源占用

内容的提问来源于stack exchange,提问作者Xavier Gallo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 18:54:23