JavaScript實現(xiàn)手寫循環(huán)滑動效果
最近一直在做業(yè)務(wù),遇到一個需求是: 頁面頂部需要展示圖片,可以拖動,拖動到最后一張的時候需要無縫切換到第一張,可以實現(xiàn)循環(huán)滑動。
這種需求場景挺常見的,比如淘寶,網(wǎng)易云,頁面頂部都是這個模式:

如果不要求循環(huán)滑動,css就能實現(xiàn):
父元素定寬,同時設(shè)置overflow-x:auto。子元素有寬度,橫向排列即可。
一旦要求循環(huán)滑動,或者不能用overflow,就會有點麻煩:
需要手動實現(xiàn)滑動的效果:
1.監(jiān)聽 touch 事件,監(jiān)聽touchStart,touchMove,touchEnd,計算位移
let start = 0, move = 0, end = 0
??????? const touchStart = (e) => {
// 計算滑動的起點,同時需要保存上一次的停留的位置
start = e.touches[0].clientX - end
}
const touchMove = (e) => {
// 記錄滑動的距離
move = e.touches[0].clientX - start
}
const touchEnd = () => {
// 記錄結(jié)束滑動的位置
end = move
}2.給父元素綁定位移,比如: transform: translateX(${transform}px)
<div className={cx('swapper')}
onTouchStart={touchStart}
onTouchMove={touchMove}
onTouchEnd={touchEnd}
style={{ transform: `translateX(${transform}px)`, transition: transition ? 'transform 0.3s' : 'none' }}
>
{
list.map((item, index) => {
return <div></div>
})
}
</div>當(dāng)滑動到最后一張的時候,跳回第一張,實現(xiàn)首尾銜接:
1.將列表的第一張復(fù)制一份,放到最后一張后面。把最后一張復(fù)制一份,放到第一張前面
if (length > 1) {
const startItem = list[0]
const endItem = list[length - 1]
list.push(startItem)
list.unshift(endItem)
}2.滑動的時候設(shè)置彈性回彈,比如卡片滑動了一半,需要就近回彈到最近的卡片計算下卡片的滑動節(jié)點:比如[-750,-375,0,375]
const length = list.length
const boundary = []
const width = document.body.clientWidth
for (let i = -length; i <= length - 1; i++) {
boundary.push(i * width)
}判斷下滑動時,距離左右卡片哪個最近:
for (let i = 1; i < boundary.length; i++) {
// 最后卡片結(jié)束的位置在某個區(qū)間之內(nèi)
if (end > boundary[i - 1] && end < boundary[i]) {
// 通過減法判斷下距離左邊還是距離右邊更近
if (end - boundary[i - 1] < boundary[i] - end) {
end = boundary[i - 1]
// 設(shè)置最終的位置
setTransform(boundary[i - 1])
} else {
end = boundary[i]
setTransform(boundary[i])
}
break
}
}回彈卡片的時候需要過渡效果,但滑動卡片的時候不需要過渡效果,所以在touchMove階段移除transition,在touchEnd階段開啟過渡效果。
滑動結(jié)束后,需要判斷邊界情況:是否是最后一張,需要跳轉(zhuǎn)到第一張。需要注意,這個時候是不需要過渡效果的:
if (end < boundary[0]) {
end = boundary[0]
setTransform(end)
}
if (end > boundary[boundary.length - 1]) {
end = boundary[boundary.length - 1]
setTransform(end)
}
setTimeout(() => {
// 關(guān)閉過渡效果
setTransition(false)
// 如果是第二張,需要跳轉(zhuǎn)到倒數(shù)第二張
if (end < boundary[1]) {
end = boundary[boundary.length - 2]
setTransform(end)
}
// 如果是倒數(shù)第二張,需要跳轉(zhuǎn)到第二張
// 確?;瑒拥膱D片都在中間,而不是在列表的第一張和最后一張
if (end > boundary[boundary.length - 2]) {
end = boundary[1]
setTransform(end)
}
// 300 是因為需要等待回彈動畫結(jié)束
}, 300);完整實現(xiàn)如下:
// 必須要放在組件外
let start = 0, move = 0, end = 0
const cx = classBind.bind(styles);
const Component = (props) => {
const { arr = [] } = props
const [list, setList] = useState(arr)
// 控制位移
const [transform, setTransform] = useState(0)
// 控制過渡效果
const [transition, setTransition] = useState(false)
// 記錄初始位置
const touchStart = (e) => {
start = e.touches[0].clientX - end
}
// 計算位移
const touchMove = (e) => {
move = e.touches[0].clientX - start
setTransition(false)
setTransform(move)
}
// 結(jié)束后計算回彈,和邊界情況
const touchEnd = () => {
setTransition(true)
const length = list[0].listLength
const boundary = []
const width = document.body.clientWidth
for (let i = -length; i <= length - 1; i++) {
boundary.push(i * width)
}
end = move
if (end < boundary[0]) {
end = boundary[0]
setTransform(end)
}
if (end > boundary[boundary.length - 1]) {
end = boundary[boundary.length - 1]
setTransform(end)
}
setTimeout(() => {
setTransition(false)
if (end < boundary[1]) {
end = boundary[boundary.length - 2]
setTransform(end)
}
if (end > boundary[boundary.length - 2]) {
end = boundary[1]
setTransform(end)
}
}, 300);
for (let i = 1; i < boundary.length; i++) {
if (end > boundary[i - 1] && end < boundary[i]) {
if (end - boundary[i - 1] < boundary[i] - end) {
end = boundary[i - 1]
setTransform(boundary[i - 1])
} else {
end = boundary[i]
setTransform(boundary[i])
}
break
}
}
}
useEffect(() => {
const { length } = list
list.forEach((item, index) => {
// 保存真實的位置,因為后面會補充前后兩張卡片
item.activeKey = index
// 保存真實的列表長度,不直接減2是因為有1張卡片的情況
item.listLength = length
})
if (length > 1) {
const startItem = list[0]
const endItem = list[length - 1]
list.push(startItem)
list.unshift(endItem)
}
setList([...list])
})
}, [arr]);
return <div className={cx('resource-detail')}>
<div className={cx('swapper')}
onTouchStart={touchStart}
onTouchMove={touchMove}
onTouchEnd={touchEnd}
style={{ transform: `translateX(${transform}px)`, transition: transition ? 'transform 0.3s' : 'none' }}
>
{
list.map((item, index) => {
return <div key={index}>這是卡片</div>
})
}
</div>
</div>
}到此這篇關(guān)于JavaScript實現(xiàn)手寫循環(huán)滑動效果的文章就介紹到這了,更多相關(guān)JavaScript循環(huán)滑動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用JSON.parse將json字符串轉(zhuǎn)換成json對象的時候會出錯
使用JSON.parse將json字符串轉(zhuǎn)換成json對象的時候會出錯,主要是雙引號,回車換行等影響明顯,左尖括號和右尖括號也會導(dǎo)致顯示問題2014-09-09
javascript與asp.net(c#)互相調(diào)用方法
js與C#之間相互調(diào)用的一些方法2009-12-12
用js實現(xiàn)before和after偽類的樣式修改的示例代碼
本篇文章主要介紹了用js實現(xiàn)before和after偽類的樣式修改的示例代碼,具有一定的參考價值,有興趣的可以了解一下2017-09-09
關(guān)于JavaScript中高階函數(shù)的魅力詳解
高階函數(shù):英文叫Higher-order function。JavaScript的函數(shù)其實都指向某個變量。下面這篇文章主要給大家介紹了關(guān)于JavaScript中高階函數(shù)的魅力,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09
讓bootstrap的carousel支持滑動滾屏的實現(xiàn)代碼
這篇文章主要介紹了讓bootstrap的carousel支持滑動滾屏的實現(xiàn)代碼,需要的朋友可以參考下2017-11-11

