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)文章
JS小功能(checkbox實現(xiàn)全選和全取消)實例代碼
這篇文章主要介紹了checkbox實現(xiàn)全選和全取消實例代碼,有需要的朋友可以參考一下2013-11-11JavaScript數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹添加/刪除節(jié)點操作示例
這篇文章主要介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹添加/刪除節(jié)點操作,涉及javascript二叉樹的定義、節(jié)點添加、刪除、遍歷等相關(guān)操作技巧,需要的朋友可以參考下2019-03-03小程序hover-class點擊態(tài)效果實現(xiàn)
這篇文章主要介紹了小程序hover-class點擊態(tài)效果實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-02-02深入理解webpack process.env.NODE_ENV配置
這篇文章主要介紹了深入理解webpack process.env.NODE_ENV配置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02