最近在做移动端方面运用到了饿了么的vue前端组件库,因为不想单纯用组件而使用它,故想深入了解一下实现原理。后续将会继续研究一下其他的组件实现原理,有兴趣的可以关注下。
代码在这里:
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'); } }
判断方向