最近在做移动端方面运用到了饿了么的vue
前端组件库,因为不想单纯用组件而使用它,故想深入了解一下实现原理。后续将会继续研究一下其他的组件实现原理,有兴趣的可以关注下。
移动端效果之Picker
移动端效果之CellSwiper
移动端效果之IndexList
移动端效果之scrollList
父容器overflow:hidden;
,子页面transform:translateX(-100%);width:100%;
由于所有页面都在手机屏幕左侧一个屏幕宽度的位置,因此最开始的情况是页面中看不到任何一个子页面,所以第一步应该设置应该显示的子页面,默认情况下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;
}
在低版本的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 // 下一个页面
};
套用全局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
左移,反之,右移)
前置工作:
在滑动中,我们是可以实时地来判断到底是不是用户的自然滚动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'); } }
后置工作:
清除一次滑动周期中保存的状态信息
dragging = false;
dragState = {};
整体来说实现原理还是比较简单的,滑动开始记录初始位置,计算上一页与下一页的应该展示的页面;滑动中计算位移,计算上一页下一页的位移;滑动结束根据位移结果执行相应的动画。
有一个细节就是,在滑动中transition
的效果置为空,是为了防止在滑动中上一页与下一页因为过渡存在而位移得不自然,在滑动结束后再给他们加上动画效果。