寫在前面
成都創(chuàng)新互聯(lián)自2013年創(chuàng)立以來,先為汾西等服務(wù)建站,汾西等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為汾西企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
最近在做移動(dòng)端方面運(yùn)用到了餓了么的vue前端組件庫,因?yàn)椴幌雴渭冇媒M件而使用它,故想深入了解一下實(shí)現(xiàn)原理。后續(xù)將會(huì)繼續(xù)研究一下其他的組件實(shí)現(xiàn)原理,有興趣的可以關(guān)注下。
代碼在這里:戳我
1. 說明
父容器overflow:hidden;,子頁面transform:translateX(-100%);width:100%;
2. 核心解析
2.1 頁面初始化
由于所有頁面都在手機(jī)屏幕左側(cè)一個(gè)屏幕寬度的位置,因此最開始的情況是頁面中看不到任何一個(gè)子頁面,所以第一步應(yīng)該設(shè)置應(yīng)該顯示的子頁面,默認(rèn)情況下defaultIndex:0
function reInitPages() { // 得出頁面是否能夠被滑動(dòng) // 1. 子頁面只有一個(gè) // 2. 用戶手動(dòng)設(shè)置不能滑動(dòng) noDragWhenSingle = true noDrag = children.length === 1 && noDragWhenSingle; var aPages = []; var intDefaultIndex = Math.floor(defaultIndex); var defaultIndex = (intDefaultIndex >= 0 && intDefaultIndex < children.length) ? intDefaultIndex : 0; // 得到當(dāng)前被激活的子頁面索引 index = defaultIndex; children.forEach(function(child, index) { aPages.push(child); // 所有頁面移除激活class child.classList.remove('is-active'); if (index === defaultIndex) { // 給激活的子頁面加上激活class child.classList.add('is-active'); } }); pages = aPages; }
2.2 容器滑動(dòng)開始(onTouchStart)
在低版本的android手機(jī)上,設(shè)置event.preventDefault()會(huì)起到一定的性能提升作用,使得滑動(dòng)起來不是那么卡。
前置工作:
滑動(dòng)開始:
使用一個(gè)全局對(duì)象記錄信息,這些信息包括:
dragState = { startTime // 開始時(shí)間 startLeft // 開始的X坐標(biāo) startTop // 開始的Y坐標(biāo)(相對(duì)于整個(gè)頁面viewport pageY) startTopAbsolute // 絕對(duì)Y坐標(biāo)(相對(duì)于文檔頂部 clientY) pageWidth // 一個(gè)頁面寬度 pageHeight // 一個(gè)頁面的高度 prevPage // 上一個(gè)頁面 dragPage // 當(dāng)前頁面 nextPage // 下一個(gè)頁面 };
2.3 容器滑動(dòng)(onTouchMove)
套用全局dragState,記錄新的信息
dragState = { currentLeft // 開始的X坐標(biāo) currentTop // 開始的Y坐標(biāo)(相對(duì)于整個(gè)頁面viewport pageY) currentTopAbsolute // 絕對(duì)Y坐標(biāo)(相對(duì)于文檔頂部 clientY) };
那么我們就可以通過開始和滑動(dòng)中的信息來計(jì)算出一些東西:
滑動(dòng)的水平位移(offsetLeft = currentLeft - startLeft)
滑動(dòng)的垂直位移(offsetTop = currentTopAbsolute - startTopAbsolute)
是否是用戶的自然滾動(dòng),這里的自然滾動(dòng)說的是用戶并不是想滑動(dòng)swiper,而是想滑動(dòng)頁面
// 條件 // distanceX = Math.abs(offsetLeft); // distanceY = Math.abs(offsetTop); distanceX < 5 || ( distanceY >= 5 && distanceY >= 1.73 * distanceX )
判斷是左移還是右移(offsetLeft < 0 左移,反之,右移)
重置位移
// 如果存在上一個(gè)頁面并且是左移 if (dragState.prevPage && towards === 'prev') { // 重置上一個(gè)頁面的水平位移為 offsetLeft - dragState.pageWidth // 由于 offsetLeft 一直在變化,并且 >0 // 那么也就是說 offsetLeft - dragState.pageWidth 的值一直在變大,但是仍未負(fù)數(shù) // 這就是為什么當(dāng)連續(xù)屬性存在的時(shí)候左滑會(huì)看到上一個(gè)頁面會(huì)跟著滑動(dòng)的原因 // 這里的 translate 方法其實(shí)很簡單,在滑動(dòng)的時(shí)候去除了動(dòng)畫效果`transition`,單純改變位移 // 而在滑動(dòng)結(jié)束的時(shí)候,加上`transition`,使得滑動(dòng)到最后釋放的過渡更加自然 translate(dragState.prevPage, offsetLeft - dragState.pageWidth); } // 當(dāng)前頁面跟著滑動(dòng) translate(dragState.dragPage, offsetLeft); // 后一個(gè)頁面同理 if (dragState.nextPage && towards === 'next') { translate(dragState.nextPage, offsetLeft + dragState.pageWidth); }
2.4 滑動(dòng)結(jié)束(onTouchEnd)
前置工作:
在滑動(dòng)中,我們是可以實(shí)時(shí)地來判斷到底是不是用戶的自然滾動(dòng)userScrolling,如果是用戶自然滾動(dòng),那么swiper的滑動(dòng)信息就不算數(shù),因此要做一些清除操作:
dragging = false; dragState = {};
當(dāng)然如果userScrolling:false,那么就是滑動(dòng)子頁面,執(zhí)行doOnTouchEnd方法
判斷是否是tap事件
// 時(shí)間小于300ms,click事件延遲300ms觸發(fā) // 水平位移和垂直位移棟小于5像素 if (dragDuration < 300) { var fireTap = Math.abs(offsetLeft) < 5 && Math.abs(offsetTop < 5); if (isNaN(offsetLeft) || isNaN(offsetTop)) { fireTap = true; } if (fireTap) { console.log('tap'); } }
判斷方向
// 如果事件間隔小于300ms但是滑出屏幕,直接返回 if (dragDuration < 300 && dragState.currentLeft === undefined) return; // 如果事件間隔小于300ms 或者 滑動(dòng)位移超過屏幕寬度 1/2, 根據(jù)位移判斷方向 if (dragDuration < 300 || Math.abs(offsetLeft) > pageWidth / 2) { towards = offsetLeft < 0 ? 'next' : 'prev'; } // 如果非連續(xù),當(dāng)處于第一頁,不會(huì)出現(xiàn)上一頁,當(dāng)處于最后一頁,不會(huì)出現(xiàn)下一頁 if (!continuous) { if ((index === 0 && towards === 'prev') || (index === pageCount - 1 && towards === 'next')) { towards = null; } } // 子頁面數(shù)量小于2時(shí),不執(zhí)行滑動(dòng)動(dòng)畫 if (children.length < 2) { towards = null; }
執(zhí)行動(dòng)畫
// 當(dāng)沒有options的時(shí)候,為自然滑動(dòng),也就是定時(shí)器滑動(dòng) function doAnimate(towards, options) { if (children.length === 0) return; if (!options && children.length < 2) return; var prevPage, nextPage, currentPage, pageWidth, offsetLeft; var pageCount = pages.length; // 定時(shí)器滑動(dòng) if (!options) { pageWidth = element.clientWidth; currentPage = pages[index]; prevPage = pages[index - 1]; nextPage = pages[index + 1]; if (continuous && pages.length > 1) { if (!prevPage) { prevPage = pages[pages.length - 1]; } if (!nextPage) { nextPage = pages[0]; } } // 計(jì)算上一頁與下一頁之后 // 重置位移 // 參看doOnTouchMove // 其實(shí)這里的options 傳與不傳也就是獲取上一頁信息與下一頁信息 if (prevPage) { prevPage.style.display = 'block'; translate(prevPage, -pageWidth); } if (nextPage) { nextPage.style.display = 'block'; translate(nextPage, pageWidth); } } else { prevPage = options.prevPage; currentPage = options.currentPage; nextPage = options.nextPage; pageWidth = options.pageWidth; offsetLeft = options.offsetLeft; } var newIndex; var oldPage = children[index]; // 得到滑動(dòng)之后的新的索引 if (towards === 'prev') { if (index > 0) { newIndex = index - 1; } if (continuous && index === 0) { newIndex = pageCount - 1; } } else if (towards === 'next') { if (index < pageCount - 1) { newIndex = index + 1; } if (continuous && index === pageCount - 1) { newIndex = 0; } } // 動(dòng)畫完成之后的回調(diào) var callback = function() { // 得到滑動(dòng)之后的激活頁面,添加激活class // 重新賦值索引 if (newIndex !== undefined) { var newPage = children[newIndex]; oldPage.classList.remove('is-active'); newPage.classList.add('is-active'); index = newIndex } if (isDone) { end(); } if (prevPage) { prevPage.style.display = ''; } if (nextPage) { nextPage.style.display = ''; } } setTimeout(function() { // 向后滑動(dòng) if (towards === 'next') { isDone = true; before(currentPage); // 當(dāng)前頁執(zhí)行動(dòng)畫,完成后執(zhí)行callback translate(currentPage, -pageWidth, speed, callback); if (nextPage) { // 下一面移動(dòng)視野中 translate(nextPage, 0, speed) } } else if (towards === 'prev') { isDone = true; before(currentPage); translate(currentPage, pageWidth, speed, callback); if (prevPage) { translate(prevPage, 0, speed); } } else { // 如果既不是左滑也不是右滑 isDone = true; // 當(dāng)前頁面依舊處于視野中 // 上一頁和下一頁滑出 translate(currentPage, 0, speed, callback); if (typeof offsetLeft !== 'undefined') { if (prevPage && offsetLeft > 0) { translate(prevPage, pageWidth * -1, speed); } if (nextPage && offsetLeft < 0) { translate(nextPage, pageWidth, speed); } } else { if (prevPage) { translate(prevPage, pageWidth * -1, speed); } if (nextPage) { translate(nextPage, pageWidth, speed); } } } }, 10); }
后置工作:
清除一次滑動(dòng)周期中保存的狀態(tài)信息
dragging = false; dragState = {};
總結(jié)
整體來說實(shí)現(xiàn)原理還是比較簡單的,滑動(dòng)開始記錄初始位置,計(jì)算上一頁與下一頁的應(yīng)該展示的頁面;滑動(dòng)中計(jì)算位移,計(jì)算上一頁下一頁的位移;滑動(dòng)結(jié)束根據(jù)位移結(jié)果執(zhí)行相應(yīng)的動(dòng)畫。
有一個(gè)細(xì)節(jié)就是,在滑動(dòng)中transition的效果置為空,是為了防止在滑動(dòng)中上一頁與下一頁因?yàn)檫^渡存在而位移得不自然,在滑動(dòng)結(jié)束后再給他們加上動(dòng)畫效果。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
文章標(biāo)題:移動(dòng)端效果之Swiper詳解
網(wǎng)頁地址:http://aaarwkj.com/article38/isgisp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、云服務(wù)器、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、網(wǎng)站維護(hù)、域名注冊
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)