文章目錄
- 定位實現滑屏效果
- 前置知識
- CSS: touch-action屬性
- CSS: transform屬性
- 觸摸事件
- forEach
- 回調占位符
- 準備階段
- 實現移動效果
- 實現跟手效果
- 觸摸結束優化
- 完整代碼
- 滾動實現滑屏效果
- 前置知識
- CSS: scroll-snap-type屬性
- 準備階段
- 實現滑動效果
- 實現吸附效果
- 滾動條隱藏
- 存在問題
- 完整代碼
- scrollLeft實現滑屏效果(最佳實踐)
- 前置知識
- DOM: scrollLeft
- DOM: scrollWidth
- @scroll事件
- 準備階段
????本文將詳細介紹如何使用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><divclass="page-container"@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"><!-- 頁面內容 --><divv-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分別向負方向移動
<!-- 頁面內容 --><divv-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; /* 平滑過渡 */
}
實現跟手效果
現在就要點絲滑😻效果了,不過還不夠還要添加跟手效果:
<!-- 頁面內容 -->
<divv-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><divclass="page-container"@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"><!-- 頁面內容 --><divv-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; // 約60fpslastTime = 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"><!-- 頁面內容 --><divv-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" ><!-- 頁面內容 --><divv-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><divclass="scroll-container"ref="containerRef"@touchstart="touchstart"@touchend="touchend"><!-- 頁面內容 --><divv-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>