JavaScript實(shí)現(xiàn)手寫循環(huán)滑動(dòng)效果
最近一直在做業(yè)務(wù),遇到一個(gè)需求是: 頁面頂部需要展示圖片,可以拖動(dòng),拖動(dòng)到最后一張的時(shí)候需要無縫切換到第一張,可以實(shí)現(xiàn)循環(huán)滑動(dòng)。
這種需求場(chǎng)景挺常見的,比如淘寶,網(wǎng)易云,頁面頂部都是這個(gè)模式:
如果不要求循環(huán)滑動(dòng),css
就能實(shí)現(xiàn):
父元素定寬,同時(shí)設(shè)置overflow-x:auto
。子元素有寬度,橫向排列即可。
一旦要求循環(huán)滑動(dòng),或者不能用overflow
,就會(huì)有點(diǎn)麻煩:
需要手動(dòng)實(shí)現(xiàn)滑動(dòng)的效果:
1.監(jiān)聽 touch
事件,監(jiān)聽touchStart
,touchMove
,touchEnd
,計(jì)算位移
let start = 0, move = 0, end = 0 ??????? const touchStart = (e) => { // 計(jì)算滑動(dòng)的起點(diǎn),同時(shí)需要保存上一次的停留的位置 start = e.touches[0].clientX - end } const touchMove = (e) => { // 記錄滑動(dòng)的距離 move = e.touches[0].clientX - start } const touchEnd = () => { // 記錄結(jié)束滑動(dòng)的位置 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)滑動(dòng)到最后一張的時(shí)候,跳回第一張,實(shí)現(xiàn)首尾銜接:
1.將列表的第一張復(fù)制一份,放到最后一張后面。把最后一張復(fù)制一份,放到第一張前面
if (length > 1) { const startItem = list[0] const endItem = list[length - 1] list.push(startItem) list.unshift(endItem) }
2.滑動(dòng)的時(shí)候設(shè)置彈性回彈,比如卡片滑動(dòng)了一半,需要就近回彈到最近的卡片計(jì)算下卡片的滑動(dòng)節(jié)點(diǎn):比如[-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) }
判斷下滑動(dòng)時(shí),距離左右卡片哪個(gè)最近:
for (let i = 1; i < boundary.length; i++) { // 最后卡片結(jié)束的位置在某個(gè)區(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 } }
回彈卡片的時(shí)候需要過渡效果,但滑動(dòng)卡片的時(shí)候不需要過渡效果,所以在touchMove階段移除transition,在touchEnd階段開啟過渡效果。
滑動(dòng)結(jié)束后,需要判斷邊界情況:是否是最后一張,需要跳轉(zhuǎn)到第一張。需要注意,這個(gè)時(shí)候是不需要過渡效果的:
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òng)的圖片都在中間,而不是在列表的第一張和最后一張 if (end > boundary[boundary.length - 2]) { end = boundary[1] setTransform(end) } // 300 是因?yàn)樾枰却貜梽?dòng)畫結(jié)束 }, 300);
完整實(shí)現(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 } // 計(jì)算位移 const touchMove = (e) => { move = e.touches[0].clientX - start setTransition(false) setTransform(move) } // 結(jié)束后計(jì)算回彈,和邊界情況 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) => { // 保存真實(shí)的位置,因?yàn)楹竺鏁?huì)補(bǔ)充前后兩張卡片 item.activeKey = index // 保存真實(shí)的列表長(zhǎng)度,不直接減2是因?yàn)橛?張卡片的情況 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實(shí)現(xiàn)手寫循環(huán)滑動(dòng)效果的文章就介紹到這了,更多相關(guān)JavaScript循環(huán)滑動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
原生javascript實(shí)現(xiàn)勻速運(yùn)動(dòng)動(dòng)畫效果
這篇文章主要為大家詳細(xì)介紹了原生javascript實(shí)現(xiàn)勻速運(yùn)動(dòng)動(dòng)畫效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02JS小功能(checkbox實(shí)現(xiàn)全選和全取消)實(shí)例代碼
這篇文章主要介紹了checkbox實(shí)現(xiàn)全選和全取消實(shí)例代碼,有需要的朋友可以參考一下2013-11-11JavaScript數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹添加/刪除節(jié)點(diǎn)操作示例
這篇文章主要介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹添加/刪除節(jié)點(diǎn)操作,涉及javascript二叉樹的定義、節(jié)點(diǎn)添加、刪除、遍歷等相關(guān)操作技巧,需要的朋友可以參考下2019-03-03小程序hover-class點(diǎn)擊態(tài)效果實(shí)現(xiàn)
這篇文章主要介紹了小程序hover-class點(diǎn)擊態(tài)效果實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02JS中自定義定時(shí)器讓它在某一時(shí)刻執(zhí)行
寫一個(gè)方法,讓它在某一時(shí)刻執(zhí)行,即需要在JS中寫一個(gè)定時(shí)器,當(dāng)時(shí)間達(dá)到要求時(shí)間時(shí),需要執(zhí)行的方法自動(dòng)執(zhí)行,下面的示例大家可以參考下2014-09-09echarts折線圖每段顯示不同的顏色的實(shí)現(xiàn)
本文主要介紹了echarts折線圖每段顯示不同的顏色的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09深入理解webpack process.env.NODE_ENV配置
這篇文章主要介紹了深入理解webpack process.env.NODE_ENV配置,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02用js判斷是否為360瀏覽器的實(shí)現(xiàn)代碼
這篇文章主要介紹了用js判斷是否為360瀏覽器的實(shí)現(xiàn)代碼,有時(shí)候我們需要判斷是否為360瀏覽器,包括百度聯(lián)盟后臺(tái)就有這樣的提示需要的朋友可以參考下2015-01-01