最近在做行動端方面運用到了餓了麼的vue前端元件庫,因為不想單純用元件而使用它,故想深入了解實現原理。本文主要為大家詳細介紹了行動端效果之Swiper的相關資料,具有一定的參考價值,有興趣的小夥伴們可以參考一下,希望能幫助大家。
程式碼在這裡:戳我
1.說明
父容器 overflow:hidden;,子頁transform:translateX(-100%);width:100%;
2.核心解析
2.1 頁面初始化
由於所有頁面都在手機螢幕左側一個螢幕寬度的位置,因此最開始的情況是頁面中看不到任何一個子頁面,所以第一步應該設定應該顯示的子頁面,預設情況下defaultIndex:0
function reInitPages() { // 得出页面是否能够被滑动 // 1. 子页面只有一个 // 2. 用户手动设置不能滑动 noDragWhenSingle = true noDrag = children.length === 1 && noDragWhenSingle; var aPages = []; var intDefaultIndex = Math.floor(defaultIndex); var defaultIndex = (intDefaultIndex >= 0 && intDefaultIndex < children.length) ? intDefaultIndex : 0; // 得到当前被激活的子页面索引 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 容器滑動開始(onTouchStart)
在低版本的android手機上,設定event.preventDefault()會起到一定的性能提升作用,使得滑動起來不是那麼卡。
前置工作:
如果使用者設定了prevent:true, 滑動時阻止預設行為
如果使用者設定了stopPropagation:true, 滑動時阻止事件向上傳播
如果動畫尚未結束,阻止滑動
#設定dragging:true,滑動開始
設定使用者捲動為false
#滑動開始:
使用一個全域物件記錄訊息,這些資訊包括:
dragState = { startTime // 开始时间 startLeft // 开始的X坐标 startTop // 开始的Y坐标(相对于整个页面viewport pageY) startTopAbsolute // 绝对Y坐标(相对于文档顶部 clientY) pageWidth // 一个页面宽度 pageHeight // 一个页面的高度 prevPage // 上一个页面 dragPage // 当前页面 nextPage // 下一个页面 };
2.3 容器滑動(onTouchMove)
套用全域dragState,記錄新的資訊
dragState = { currentLeft // 开始的X坐标 currentTop // 开始的Y坐标(相对于整个页面viewport pageY) currentTopAbsolute // 绝对Y坐标(相对于文档顶部 clientY) };
那麼我們就可以透過開始和滑動中的信息來計算出一些東西:
滑動的水平位移(offsetLeft = currentLeft - startLeft)
滑動的垂直位移(offsetTop = currentTopAbsolute - startTopAbsolute)
是否是用戶的自然滾動,這裡的自然滾動說的是用戶並不是想滑動swiper,而是想滑動頁面
// 条件 // distanceX = Math.abs(offsetLeft); // distanceY = Math.abs(offsetTop); distanceX < 5 || ( distanceY >= 5 && distanceY >= 1.73 * distanceX )
判斷是左移還是右移(offsetLeft < 0 左移,反之,右移)
重設位移
// 如果存在上一个页面并且是左移 if (dragState.prevPage && towards === 'prev') { // 重置上一个页面的水平位移为 offsetLeft - dragState.pageWidth // 由于 offsetLeft 一直在变化,并且 >0 // 那么也就是说 offsetLeft - dragState.pageWidth 的值一直在变大,但是仍未负数 // 这就是为什么当连续属性存在的时候左滑会看到上一个页面会跟着滑动的原因 // 这里的 translate 方法其实很简单,在滑动的时候去除了动画效果`transition`,单纯改变位移 // 而在滑动结束的时候,加上`transition`,使得滑动到最后释放的过渡更加自然 translate(dragState.prevPage, offsetLeft - dragState.pageWidth); } // 当前页面跟着滑动 translate(dragState.dragPage, offsetLeft); // 后一个页面同理 if (dragState.nextPage && towards === 'next') { translate(dragState.nextPage, offsetLeft + dragState.pageWidth); }
2.4 滑動結束(onTouchEnd)
前置工作:
在滑動中,我們是可以實時地來判斷到底是不是用戶的自然滾動userScrolling,如果是用戶自然滾動,那麼swiper的滑動訊息就不算數,因此要做一些清除操作:
dragging = false; dragState = {};
當然如果userScrolling:false,那麼就是滑動子頁面,執行doOnTouchEnd方法
判斷是否為tap事件
// 时间小于300ms,click事件延迟300ms触发 // 水平位移和垂直位移栋小于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 或者 滑动位移超过屏幕宽度 1/2, 根据位移判断方向 if (dragDuration < 300 || Math.abs(offsetLeft) > pageWidth / 2) { towards = offsetLeft < 0 ? 'next' : 'prev'; } // 如果非连续,当处于第一页,不会出现上一页,当处于最后一页,不会出现下一页 if (!continuous) { if ((index === 0 && towards === 'prev') || (index === pageCount - 1 && towards === 'next')) { towards = null; } } // 子页面数量小于2时,不执行滑动动画 if (children.length < 2) { towards = null; }
#執行動畫
##
// 当没有options的时候,为自然滑动,也就是定时器滑动 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; // 定时器滑动 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]; } } // 计算上一页与下一页之后 // 重置位移 // 参看doOnTouchMove // 其实这里的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]; // 得到滑动之后的新的索引 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; } } // 动画完成之后的回调 var callback = function() { // 得到滑动之后的激活页面,添加激活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() { // 向后滑动 if (towards === 'next') { isDone = true; before(currentPage); // 当前页执行动画,完成后执行callback translate(currentPage, -pageWidth, speed, callback); if (nextPage) { // 下一面移动视野中 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; // 当前页面依旧处于视野中 // 上一页和下一页滑出 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); }
後置工作:
清除一次滑動週期中儲存的狀態資訊dragging = false; dragState = {};
總結
transition的效果置為空,是為了防止在滑動中上一頁與下一頁因為過渡存在而位移得不自然,在滑動結束後再給他們加上動畫效果。
相關推薦:以上是Swiper在行動端的用法的詳細內容。更多資訊請關注PHP中文網其他相關文章!