文章目录
本文将详细介绍如何使用Vue3实现移动端流畅的划屏交互效果。我们将深入探讨触摸事件处理、页面动态切换和动画优化的完整实现方案。文章将重点解析三个关键方法:
handleTouchStart
记录触摸起点、 handleTouchMove
实现实时跟手效果、 handleTouchEnd
处理页面切换逻辑,特别会讲解 pages.forEach循环
在重置页面位置和恢复过渡动画中的重要作用。同时会分享性能优化技巧,包括transform动画的优势、直接DOM操作与Vue响应式的平衡,以及滑动阈值的合理设置,帮助开发者掌握移动端滑动交互的核心实现原理。
定位实现滑屏效果
实现了一个典型的移动端页面滑动容器,具有以下特点:
- 支持左右滑动切换不同颜色的页面
- 滑动时有实时跟手效果
- 滑动结束后有平滑的过渡动画
- 边界检测防止越界
前置知识
CSS: touch-action属性
touch-action
是 CSS 的一个属性,用来控制元素如何响应触摸操作(如滑动、缩放等)。
常见取值:
auto
:默认,浏览器自行处理触摸行为。none
:完全禁用浏览器默认的触摸行为(如滚动、缩放)。pan-x
:允许水平滑动,禁止垂直滑动和缩放。pan-y
:允许垂直滑动,禁止水平滑动和缩放。manipulation
:允许滑动,但禁用双击缩放(提高响应速度)。
示例:
/* 禁用默认触摸行为,防止滚动 */
.prevent-scroll {
touch-action: none;
}
/* 只允许垂直滑动 */
.vertical-only {
touch-action: pan-y;
}
适用场景:
- 自定义触摸手势(如画板、游戏)。
- 防止滑动冲突(如地图内嵌滚动列表)。
简单说,它让开发者能精细控制触摸交互,避免浏览器默认行为的干扰。
CSS: transform属性
transform: 是 CSS 中**把元素当成一张图来变形
**的属性。
一次可以写多个 2D/3D 函数,空格隔开,按从左到右的顺序逐个执行。
- 常用 2D 函数
函数 | 说明 | 例子 |
---|---|---|
translate(x, y) |
平移 | transform: translate(20px, -10px) |
translateX(x) / translateY(y) |
单轴平移 | translateX(50%) |
scale(sx, sy) |
缩放 | scale(1.2, .8) |
rotate(angle) |
旋转 | rotate(45deg) |
skew(ax, ay) |
倾斜 | skew(30deg, 10deg) |
- 常用 3D 函数
函数 | 说明 |
---|---|
translate3d(x,y,z) |
三维平移 |
scale3d(sx,sy,sz) |
三维缩放 |
rotateX(a) / rotateY(a) / rotateZ(a) |
绕各轴旋转 |
- 组合示例
.box {
transform: translateX(100px) rotate(45deg) scale(1.2);
}
先右移 100 px,再旋转 45°,最后放大 1.2 倍。
- 性能与注意点
- 不占文档流:变形后原位置仍保留(不像
position:absolute
会脱离)。 - 硬件加速:大多数浏览器对
transform
开 GPU 加速,动画较流畅。 - 不影响兄弟元素:不会像
margin
那样推挤别人。
触摸事件
HTML 的触摸事件有 4 个核心,按触发顺序:
- touchstart:手指刚碰到屏幕
- touchmove:手指在屏幕上滑动(连续触发)
- touchend:手指离开屏幕
- touchcancel:系统中断(如来电、弹窗)
每个事件对象里都带 touches / changedTouches 等列表,可拿到触点坐标。
事件对象属性:
- touches:当前屏幕上所有接触点集合。
- targetTouches:事件绑定元素上的接触点集合。
- changedTouches:触发此事件时状态改变的接触点集合(touchstart时即新出现的点)。
forEach
forEach 是数组的每个元素都跑一次的方法——给它一个回调函数,它把数组里的元素挨个传进去执行,不返回新数组,纯粹“副作用”。
基本语法:
array.forEach((value, index, array) => {
/* 干点啥 */
});
- value:当前元素
- index:当前下标(可选)
- array:原数组本身(几乎不用)
示例:
['a','b','c'].forEach((v,i)=>console.log(i,v));
// 0 a
// 1 b
// 2 c
回调占位符
在 JavaScript 的 forEach 回调里:
pages.forEach((_, i) => { ... })
_
就是占位符:
含义:“这个位置的参数我不打算用,随便起个名字占坑”。
这里
_
对应的是数组元素(page 对象),但循环体里只用索引 i,所以用_
表示“忽略它”。
这是一种常见习惯写法,读代码的人一看就明白“这个值被故意忽略了”。
准备阶段
基本结构代码:
<template>
<div
class="page-container"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
:style="{ backgroundColor: page.color }"
>
{{ page.name }}
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const pages = [
{ color: "#ff7675", name: "屏幕 1", id: 0 },
{ color: "#74b9ff", name: "屏幕 2", id: 1 },
{ color: "#55efc4", name: "屏幕 3", id: 2 },
{ color: "#a29bfe", name: "屏幕 4", id: 3 },
{ color: "#ffcb20", name: "屏幕 5", id: 4 },
];
// 触摸开始
const handleTouchStart = (e) => {};
// 触摸移动
const handleTouchMove = (e) => {};
// 触摸结束
const handleTouchEnd = () => {};
</script>
<style scoped>
.page-container {}
.page {}
</style>
目前没有样式的效果如下:
所有内容都是堆叠在一起的,而目前我们想要的效果是每个屏幕沾满一个页面,然后通过滑动来切换,
先给内容来个沾满全屏的宽度
和高度
:
.page-container {
width: 100vw;
height: 100vh;
}
.page {
width: 100%;
height: 100%;
}
在处理一下page
中的文字样式吧:
.page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
font-weight: 700;
color: aliceblue;
}
现在所有页面都是呈现竖向排列的,与我们横向滑动的效果相违背,所以需要使用定位
效果将 所有页面重叠在一起,然后再做移动效果来实现划屏效果
:
.page-container {
width: 100vw;
height: 100vh;
position: relative;
overflow: hidden;
touch-action: none;
}
.page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
font-weight: 700;
color: aliceblue;
position: absolute;
}
现在,所有屏幕都堆叠在一起了! 接下来使用transform
属性让所有屏幕从左至右以此排列:
:style="{
transform: `translateX(${index * 100}%)`,
backgroundColor: page.color,
}"
可能看不出效果,我们可以调小page
的宽高查看:width: 20%; height: 20%;
实现移动效果
接下来的内容就是重点啦!🫵💯
移动实现原理:视窗不变,让每个page分别向负方向移动
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
:style="{
transform: `translateX(${index * 100 - currentIndex * 100}%)`,
backgroundColor: page.color,
}"
>
{{ page.name }}
</div>
// 声明一个currentIndex
import { ref } from "vue";
const currentIndex = ref(0); // 当前页面索引
可以通过改变const currentIndex = ref(2);
中的变量来查看页面效果。
晓得了切换原理,接下来实现触摸事件
啦🫴
流程示意图:
const currentIndex = ref(0); // 当前页面索引
const startX = ref(0);
const moveX = ref(0);
// 触摸开始:记录起点
const handleTouchStart = (e) => {
startX.value = e.touches[0].clientX;
};
// 触摸移动:实时跟随手指滑动
const handleTouchMove = (e) => {
moveX.value = e.touches[0].clientX - startX.value;
};
// 触摸结束:判断滑动方向并切换页面
const handleTouchEnd = () => {
if (Math.abs(moveX.value)) {
if (moveX.value > 0 && currentIndex.value > 0) {
currentIndex.value--; // 向右滑,上一页
} else if (moveX.value < 0 && currentIndex.value < pages.length - 1) {
currentIndex.value++; // 向左滑,下一页
}
}
};
现在就可以通过滑动屏幕来实现切换效果了:
触发的有点灵敏,我们给handleTouchEnd
添加一个滑动阈值:
const handleTouchEnd = () => {
const threshold = 50; // 滑动阈值(像素)
if (Math.abs(moveX.value) > threshold) {
if (moveX.value > 0 && currentIndex.value > 0) {
currentIndex.value--; // 向右滑,上一页
} else if (moveX.value < 0 && currentIndex.value < pages.length - 1) {
currentIndex.value++; // 向左滑,下一页
}
}
};
再添加一个css平滑过渡效果:
.page {
//...
transition: transform 0.3s ease; /* 平滑过渡 */
}
实现跟手效果
现在就要点丝滑😻效果了,不过还不够还要添加跟手效果:
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
ref="pageRef"
:style="{
transform: `translateX(${index * 100 - currentIndex * 100}%)`,
backgroundColor: page.color,
}"
>
{{ page.name }}
</div>
const pageRef = ref(null);
// 触摸移动:实时跟随手指滑动
const handleTouchMove = (e) => {
moveX.value = e.touches[0].clientX - startX.value;
// 跟手效果
pages.forEach((_, i) => {
const page = pageRef.value[i];
if (page) {
page.style.transform = `translateX(${
i * 100 - currentIndex.value * 100 + moveX.value / 10
}%)`;
page.style.transition = "none"; // 禁用过渡效果,保证跟手效果流畅
}
});
};
实现原理详解
- 获取移动距离:
moveX.value = e.touches[0].clientX - startX.value
计算出手指从触摸开始到当前位置的水平移动距离
- 实时更新页面位置:
- 遍历所有页面元素,为每个页面计算新的
transform
值 - 基础位置:
i * 100 - currentIndex.value * 100
确保页面按顺序排列 - 跟手偏移:
+ moveX.value / 10
添加移动距离的1/10作为跟手效果(除以10是为了降低跟手灵敏度)
- 遍历所有页面元素,为每个页面计算新的
- 禁用过渡效果:
page.style.transition = "none"
临时禁用CSS过渡效果,确保跟手时的即时响应
触摸结束优化
// 触摸结束:判断滑动方向并切换页面
const handleTouchEnd = () => {
//....
// 重置位置并启用过渡动画
pages.forEach((_, i) => {
const page = document.querySelectorAll(".page")[i];
if (page) {
page.style.transform = `translateX(${
i * 100 - currentIndex.value * 100
}%)`;
page.style.transition = "transform 0.3s ease";
}
});
moveX.value = 0;
};
handleTouchEnd
里那段 pages.forEach(...)
只有一句话:把 5 张“页面”一次性摆到正确位置,并补上过渡动画。
计算目标位置
i * 100 - currentIndex.value * 100
i * 100
:第 i 张默认排在“第 i 屏”位置(0%、100%、200% …)。- 减去
currentIndex * 100
:把当前要显示的那一页拉回 0%(即屏幕正中)。
结果:所有页瞬间排成一排,当前页居中,其余页在左右两侧。
设置样式
transform
赋刚才算出的值,让页面“归位”。transition = "transform 0.3s ease"
:把被handleTouchMove
关掉的过渡重新打开,回弹时有动画。
重置
moveX.value = 0
:为下一次手势准备。
最后得到的效果如下:( 对此我们的轮播图实现效果也完成啦🎉)
完整代码
<template>
<div
class="page-container"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
ref="pageRef"
:style="{
transform: `translateX(${index * 100 - currentIndex * 100}%)`,
backgroundColor: page.color,
}"
>
{{ page.name }}
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const pages = [
{ color: "#ff7675", name: "屏幕 1", id: 0 },
{ color: "#74b9ff", name: "屏幕 2", id: 1 },
{ color: "#55efc4", name: "屏幕 3", id: 2 },
{ color: "#a29bfe", name: "屏幕 4", id: 3 },
{ color: "#ffcb20", name: "屏幕 5", id: 4 },
];
const currentIndex = ref(0); // 当前页面索引
const startX = ref(0);
const moveX = ref(0);
// 触摸开始:记录起点
const handleTouchStart = (e) => {
startX.value = e.touches[0].clientX;
};
const pageRef = ref(null);
// 触摸移动:实时跟随手指滑动
const handleTouchMove = (e) => {
moveX.value = e.touches[0].clientX - startX.value;
// 跟手效果
pages.forEach((_, i) => {
const page = pageRef.value[i];
if (page) {
page.style.transform = `translateX(${
i * 100 - currentIndex.value * 100 + moveX.value / 10
}%)`;
page.style.transition = "none"; // 禁用过渡效果,保证跟手效果流畅
}
});
};
// 触摸结束:判断滑动方向并切换页面
const handleTouchEnd = () => {
const threshold = 200; // 滑动阈值(像素)
if (Math.abs(moveX.value) > threshold) {
if (moveX.value > 0 && currentIndex.value > 0) {
currentIndex.value--; // 向右滑,上一页
} else if (moveX.value < 0 && currentIndex.value < pages.length - 1) {
currentIndex.value++; // 向左滑,下一页
}
}
// 重置位置并启用过渡动画
pages.forEach((_, i) => {
const page = pageRef.value[i];
if (page) {
page.style.transform = `translateX(${
i * 100 - currentIndex.value * 100
}%)`;
page.style.transition = "transform 0.3s ease";
}
});
moveX.value = 0;
};
</script>
<style scoped>
.page-container {
width: 100vw;
height: 100vh;
position: relative;
overflow: hidden;
touch-action: none;
}
.page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
font-weight: 700;
color: aliceblue;
position: absolute;
transition: transform 0.3s ease; /* 平滑过渡 */
}
</style>
使用节流
优化性能
方法一: 通过lodash库
安装lodash:
npm install lodash-es
<script setup> import { ref } from "vue"; import { throttle } from 'lodash-es'; // 或者使用自定义节流函数 // ... existing code ... const handleTouchMove = throttle((e) => { moveX.value = e.touches[0].clientX - startX.value; // 跟手效果 pages.forEach((_, i) => { const page = pageRef.value[i]; if (page) { page.style.transform = `translateX(${ i * 100 - currentIndex.value * 100 + moveX.value / 10 }%)`; page.style.transition = "none"; } }); }, 16); // 约60fps的间隔 // ... existing code ... </script>
或者使用自定义的简单节流实现:
<script setup> import { ref } from "vue"; // ... existing code ... let lastTime = 0; const handleTouchMove = (e) => { const now = Date.now(); if (now - lastTime < 16) return; // 约60fps lastTime = now; moveX.value = e.touches[0].clientX - startX.value; // ... rest of the original code ... }; // ... existing code ... </script>
滚动实现滑屏效果
前置知识
CSS: scroll-snap-type属性
scroll-snap-type 是 CSS 中的一个属性,用于控制滚动容器的滚动行为,特别是当用户滚动时,内容是否以及如何“吸附”到特定的停止点。
这个属性定义在滚动容器上,它决定了滚动的轴向以及吸附行为的严格程度。
基本语法:
scroll-snap-type: none | [ x | y | block | inline | both ] [ mandatory | proximity ]?;
取值说明:
- 轴向 (Axis):
none
:不启用滚动吸附。这是默认值。x
:在水平轴上启用滚动吸附。y
:在垂直轴上启用滚动吸附。block
:在块级方向上启用吸附(在大多数书写模式下等同于y
)。inline
:在内联方向上启用吸附(在大多数书写模式下等同于x
)。both
:在两个轴上都启用吸附。
- 严格程度 (Strictness):
mandatory
:强制吸附。当滚动操作结束时(例如用户松开鼠标、手指或滚动停止),视口必须停留在一个吸附点上。如果内容区域不足以滚动到下一个/上一个点,用户可能无法滚动。proximity
:邻近吸附。滚动操作结束时,如果当前视口位置足够接近一个吸附点,浏览器会自动滚动到该点。但如果用户停止在两个吸附点之间较远的位置,可能不会发生吸附。用户体验更灵活。
示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滚动吸附示例</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 30px;
}
/* 垂直方向强制吸附 */
.container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
height: 400px;
border: 2px solid #333;
margin-bottom: 40px;
}
/* 水平方向邻近吸附 */
.horizontal-slider {
display: flex;
scroll-snap-type: x proximity;
overflow-x: scroll;
scroll-behavior: smooth;
border: 2px solid #333;
margin-bottom: 40px;
}
/* 子元素需要设置吸附点 */
.item {
scroll-snap-align: start;
flex: 0 0 100%;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
color: white;
}
/* 垂直容器中的项目样式 */
.container .item {
height: 400px;
}
/* 为不同项目设置不同背景色 */
.item:nth-child(1) {
background-color: #FF5733;
}
.item:nth-child(2) {
background-color: #33FF57;
}
.item:nth-child(3) {
background-color: #3357FF;
}
.item:nth-child(4) {
background-color: #F333FF;
}
</style>
</head>
<body>
<h1>滚动吸附效果演示</h1>
<h2>垂直滚动吸附(强制)</h2>
<div class="container">
<div class="item">垂直项目 1</div>
<div class="item">垂直项目 2</div>
<div class="item">垂直项目 3</div>
<div class="item">垂直项目 4</div>
</div>
<h2>水平滚动吸附(邻近)</h2>
<div class="horizontal-slider">
<div class="item">水平项目 1</div>
<div class="item">水平项目 2</div>
<div class="item">水平项目 3</div>
<div class="item">水平项目 4</div>
</div>
<p>说明:垂直滚动容器设置了强制吸附(y mandatory),滚动结束后会精确停在项目顶部。水平滚动容器设置了邻近吸附(x proximity),滚动结束后可能会停在项目附近。</p>
</body>
</html>
准备阶段
基本结构代码:
<template>
<div class="scroll-container">
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
:style="{ backgroundColor: page.color }"
>
{{ page.name }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const pages = [
{ color: "#ff7675", name: "屏幕 1", id: 0 },
{ color: "#74b9ff", name: "屏幕 2", id: 1 },
{ color: "#55efc4", name: "屏幕 3", id: 2 },
{ color: "#a29bfe", name: "屏幕 4", id: 3 },
{ color: "#ffcb20", name: "屏幕 5", id: 4 },
];
</script>
<style scoped>
.scroll-container {
}
.page {
}
</style>
和上面步骤一样,同样的给内容来个沾满全屏的宽度
和高度
以及page
重点文字样式:
.scroll-container {
height: 100vh;
width: 100vw;
}
.page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
font-weight: 700;
color: aliceblue;
}
实现滑动效果
接下来的布局就是和上面不同的时候啦🤗,我们要给scroll-container
一个flex
属性,让所有的子元素横向排列,排列完之后大家会发现page
子元素并没有想我们想想中的那样沾满这个页面以此排列,而是都挤在一个页面了。
这是因为flex
布局的缘由,使得每个page
的宽度被压缩了。要让页面不被压缩,需要修改 .page 的样式,将 flex-shrink: 0 添加到样式中,这样 flex 容器就不会压缩这些页面了。
.scroll-container {
/*...*/
overflow-x: auto;
}
.page {
/*...*/
flex-shrink: 0; /* 添加这行防止页面被压缩 */
}
现在就解决了页面布局问题啦😄。
实现吸附效果
添加上scroll-snap-type
属性,让滚动条有吸附效果:
.scroll-container {
/*...*/
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch; /* 启用平滑滚动 */
}
.page {
/*...*/
flex-shrink: 0; /* 添加这行防止页面被压缩 */
scroll-snap-align: start;
}
划划屏幕,是不是发现现在就已经实现了我们之前想要实现的划屏效果啦😲!没错,就是这么简单,几乎纯CSS样式就可以搞定啦🤩!
滚动条隐藏
接下来优化一下下细节,先来去掉底部下方的横向滚动条:
要去掉横向滚动条,可以修改 .scroll-container 的样式,将 overflow-x 从 auto 改为 hidden。
.scroll-container {
/*...*/
/* 使用浏览器私有伪元素隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
/* WebKit 浏览器:隐藏滚动条但保留滚动功能 */
.scroll-container::-webkit-scrollbar {
display: none; /* Chrome/Safari/Opera */
}
存在问题
可以很清晰的观察到一个现象——当用力滑动时会一下子跳过多页。
这是因为 scroll-snap-type: x mandatory
在惯性滚动下的典型表现。浏览器的滚动捕捉机制虽然会强制最终停在某个捕捉点上,但它不会限制滚动的“速度”或“距离”。当你用力快速滑动时,系统会产生很大的惯性,滚动容器会高速“掠过”中间的捕捉点,最终可能停在更远的一个点上。
遗憾的是,纯 CSS 目前没有直接的属性可以限制“每次滚动只移动一个捕捉点”。mandatory 只保证“停在点上”,不保证“只移动一步”。小编这里也是还没有找到适合的实现方式👉👈,就当作是这个方法的缺陷吧😖。
完整代码
<template>
<div class="scroll-container" >
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
:style="{ backgroundColor: page.color }"
>
{{ page.name }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const pages = [
{ color: "#ff7675", name: "屏幕 1", id: 0 },
{ color: "#74b9ff", name: "屏幕 2", id: 1 },
{ color: "#55efc4", name: "屏幕 3", id: 2 },
{ color: "#a29bfe", name: "屏幕 4", id: 3 },
{ color: "#ffcb20", name: "屏幕 5", id: 4 },
];
</script>
<style scoped>
.scroll-container {
height: 100vh;
width: 100vw;
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch; /* 启用平滑滚动 */
/* 使用浏览器私有伪元素隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
/* WebKit 浏览器:隐藏滚动条但保留滚动功能 */
.scroll-container::-webkit-scrollbar {
display: none; /* Chrome/Safari/Opera */
}
.page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
font-weight: 700;
color: aliceblue;
flex-shrink: 0; /* 添加这行防止页面被压缩 */
scroll-snap-align: start;
}
</style>
scrollLeft实现滑屏效果(最佳实践)
为了解决上面使用滚动实现的划屏效果
痛点,我们不得不抛弃CSS属性,该用JS
来实现我们想要的效果。具体是那种JS
方法呢? 其实你已经学过了,就是第一种中的方法,通过触摸事件来控制屏幕滑动,不过这一次我们不是控制定位位置
来改变位置,而是通过控制横向滚动条的滚动位置
来改变位置。
前置知识
DOM: scrollLeft
scrollLeft
是一个 JavaScript DOM 属性,它用于获取或设置一个可滚动元素(如 div
, body
等)的水平滚动条相对于最左侧的偏移量。
作用:
- 获取:读取元素当前水平滚动多少像素。
- 设置:将元素的水平滚动移动到指定的位置。
基本语法:
// 获取元素当前的水平滚动偏移量(像素)
let currentScroll = element.scrollLeft;
// 设置元素的水平滚动偏移量(像素)
element.scrollLeft = value;
element
:一个具有滚动能力的 DOM 元素(即其overflow
属性为auto
或scroll
,并且内容超出其宽度)。value
:一个非负的数值,表示希望滚动条距离最左侧的像素数。
PS:
- 一个元素可滚动的最大
scrollLeft
值可以通过element.scrollWidth - element.clientWidth
计算得出。scrollTop
用于垂直方向的滚动。
实例:
<div id="scroller" style="width: 200px; overflow-x: auto; white-space: nowrap;">
<span>这是一个很长很长很长很长很长很长的文本行,需要水平滚动才能看完。</span>
</div>
<button onclick="scrollToRight()">滚动到底部</button>
const scroller = document.getElementById('scroller');
// 获取当前滚动位置
console.log('当前水平滚动位置:', scroller.scrollLeft); // 例如: 0
// 将滚动条滚动到最右侧
scroller.scrollLeft = scroller.scrollWidth - scroller.clientWidth;
// 或者滚动到特定位置
scroller.scrollLeft = 50;
// 滚动一定距离(例如向右滚动 20 像素)
scroller.scrollLeft += 20;
// 检测是否滚动到了最右边
if (scroller.scrollLeft >= scroller.scrollWidth - scroller.clientWidth) {
console.log('已滚动到底部!');
}
DOM: scrollWidth
scrollWidth 是一个 只读 的 JavaScript DOM 属性,它返回一个元素的内容(包括由于溢出而不可见的部分)的整个宽度,以像素为单位。
简单来说,scrollWidth 告诉你这个元素的“内容”到底有多宽,即使这些内容因为容器大小限制而被隐藏(溢出)了。
“scrollWidth = 可视内容宽 + 被卷起来的内容宽”
- 内容区域的总宽度(包括由于溢出而在视口外不可见的部分)。
- 包含 元素的 padding,不包含 border、margin、垂直滚动条的宽度。
- 如果元素没有溢出,scrollWidth 与 clientWidth(可见区域宽度)通常相等;出现水平溢出时,
scrollWidth > clientWidth
。
与相关属性的对比(横向):
属性 | 包含内容 | 包含 padding | 包含 border | 包含 margin | 包含隐藏部分 |
---|---|---|---|---|---|
scrollWidth | ✅ | ✅ | ❌ | ❌ | ✅ |
clientWidth | ✅ | ✅ | ❌ | ❌ | ❌ |
offsetWidth | ✅ | ✅ | ✅ | ❌ | ❌ |
垂直方向把 “Width” 换成 “Height” 即可。
@scroll事件
@scroll 是 Vue.js 框架中的一个事件监听修饰符,用于监听 DOM 元素上的 scroll 事件。
基本语法:
<template>
<!-- 监听某个元素的滚动 -->
<div @scroll="handleScroll" class="scrollable-container">
<!-- 可滚动的内容 -->
</div>
<!-- 或者监听整个窗口的滚动 (通常在 mounted 钩子中用 addEventListener) -->
<!-- 但在 Vue 模板中,@scroll 通常用于具体元素 -->
</template>
<script>
export default {
methods: {
handleScroll(event) {
// event 是原生的 UIEvent 对象
console.log('滚动了!');
// 获取滚动元素
const element = event.target;
// 获取滚动位置
console.log('scrollTop:', element.scrollTop);
console.log('scrollLeft:', element.scrollLeft);
// 其他滚动相关的逻辑...
}
}
}
</script>
核心作用:@scroll
事件在元素的滚动条滚动时被触发。
它可以用于:
- 监听滚动位置:获取
scrollTop
和scrollLeft
,判断用户滚动到了哪里。 - 实现懒加载:当用户滚动到页面底部或某个区域附近时,动态加载更多内容。
- 实现吸顶效果:当页面滚动超过某个元素的位置时,改变该元素的样式(如
position: fixed
)。 - 检测滚动方向:通过比较前后两次的
scrollTop
值来判断用户是向上还是向下滚动。 - 滚动动画控制:根据滚动位置触发动画或改变元素状态。
准备阶段
基本结构代码:
<template>
<div
class="scroll-container"
ref="containerRef"
@touchstart="touchstart"
@touchend="touchend"
>
<!-- 页面内容 -->
<div
v-for="(page, index) in pages"
:key="page.id"
class="page"
:style="{ backgroundColor: page.color }"
>
{{ page.name }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const pages = [
{ color: "#ff7675", name: "屏幕 1", id: 0 },
{ color: "#74b9ff", name: "屏幕 2", id: 1 },
{ color: "#55efc4", name: "屏幕 3", id: 2 },
{ color: "#a29bfe", name: "屏幕 4", id: 3 },
{ color: "#ffcb20", name: "屏幕 5", id: 4 },
];
const containerRef = ref(null);
const touchstart = (e) => {};
const touchend = (e) => {};
</script>
<style scoped>
.scroll-container {
height: 100vh;
width: 100vw;
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch; /* 启用平滑滚动 */
/* 使用浏览器私有伪元素隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
touch-action: none; /* 禁止触摸动作 */
}
/* WebKit 浏览器:隐藏滚动条但保留滚动功能 */
.scroll-container::-webkit-scrollbar {
display: none; /* Chrome/Safari/Opera */
}
.page {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
font-weight: 700;
color: aliceblue;
flex-shrink: 0; /* 添加这行防止页面被压缩 */
scroll-snap-align: start;
}
</style>