短视频平台前端:Vue 3 + Tailwind CSS
基于 Vue 3 + Vite + Tailwind CSS 的短视频平台前端项目,响应式移动端设计。
短视频平台前端:Vue 3 + Tailwind CSS 实现类抖音体验
项目背景
这个项目是一个 Web 端的短视频浏览平台,目标是在浏览器中复刻抖音的滑动浏览体验。和大多数短视频 App 不同,我们选择 Web 端而非原生 App,核心考量是跨平台即时可用——用户打开浏览器就能刷视频,无需安装。技术栈选择了 Vue 3(Composition API)+ Tailwind CSS + Vite。
核心交互:滑动切换
短视频最核心的交互是上下滑动切换视频。我们放弃了传统的 scroll 容器方案,改用 touch 事件 + CSS transform 动画 来实现:
<script setup>
const currentIndex = ref(0)
const startY = ref(0)
const isDragging = ref(false)
const translateY = ref(0)
const onTouchStart = (e) => {
startY.value = e.touches[0].clientY
isDragging.value = true
}
const onTouchMove = (e) => {
if (!isDragging.value) return
const diff = e.touches[0].clientY - startY.value
translateY.value = diff
}
const onTouchEnd = () => {
isDragging.value = false
// 滑动超过屏幕 20% 就切换
if (Math.abs(translateY.value) > window.innerHeight * 0.2) {
if (translateY.value < 0 && currentIndex.value < videos.length - 1) {
currentIndex.value++
} else if (translateY.value > 0 && currentIndex.value > 0) {
currentIndex.value--
}
}
translateY.value = 0
}
</script>
每个视频卡片占据 100vh,通过 transform: translateY() 实现滑动跟随,松手后判断是否超过阈值(屏幕高度的 20%)。超过则切换到下一视频,不到则回弹。
为什么不直接使用原生 scroll?因为我们需要更精细的滑动控制(比如阻尼效果、边缘吸附、手势方向锁定),原生 scroll 在这些方面自定义空间有限。
自动播放管理
使用 IntersectionObserver 管理视频播放:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const video = entry.target as HTMLVideoElement
if (entry.isIntersecting) {
video.play().catch(() => {})
} else {
video.pause()
video.currentTime = 0
}
})
}, { threshold: 0.5 })
关键设计决策:只有当视频进入视口超过 50% 时才播放,离开就暂停并回到开头。这样保证用户切走时不会浪费带宽和内存。
虚拟列表与性能优化
如果 DOM 中同时存在几十个视频元素,浏览器内存会暴涨。我们采用 虚拟列表 + 仅渲染可见卡片 的策略:
- 只渲染
currentIndex - 1到currentIndex + 1的 3 个卡片 - 上方和下方的卡片用占位 DIV 占据空间
- 视频 DOM 元素的创建和销毁通过
v-if控制
<template>
<div class="snap-container" ref="containerRef">
<div :style="{ height: currentIndex * 100 + 'vh' }" />
<div v-for="i in visibleRange" :key="videos[i].id" class="video-card">
<video v-if="Math.abs(i - currentIndex) <= 1" :src="videos[i].url" />
</div>
<div :style="{ height: (videos.length - 1 - currentIndex) * 100 + 'vh' }" />
</div>
</template>
流量优化
移动端流量敏感,做了三个优化:
- 网络类型检测:通过
navigator.connection.effectiveType判断网络(4G/WiFi/2G),4G 以上才预加载下一视频的封面图 - 预加载控制:只预加载下一个视频的元数据(
preload="metadata"),不是整个视频文件 - 内存回收:离开视频页面时主动销毁所有视频元素、释放 URL.createObjectURL 创建的对象
踩坑记录
1. iOS Safari 视频自动播放限制
iOS 上 <video> 标签的 autoplay 必须同时满足 muted + playsinline 才能生效。没加 playsinline 时视频会全屏播放,体验很糟糕。
2. Touch 事件与滚动冲突
页面存在纵向滚动区域时,touchmove 事件的 preventDefault() 需要谨慎使用,否则页面其他部分无法滚动。解决方案:只在视频容器区域内阻止默认行为。
3. 内存泄漏
初期发现刷 30-50 个视频后页面卡顿。排查发现是 URL.createObjectURL 创建的 Blob URL 没有在视频销毁时 revoke。添加组件 onUnmounted 钩子统一清理。
同类方案对比
如果追求更低成本的实现,可以考虑使用 @vueuse/core 的 useSwipe composable,它封装了 touch 事件的细节。但我们这套手动实现更适合需要精细自定义的场景。