写在前面

最近在做移动端方面运用到了饿了么的vue前端组件库,因为不想单纯用组件而使用它,故想深入了解一下实现原理。后续将会继续研究一下其他的组件实现原理,有兴趣的可以关注下。

代码在这里:戳我 or github

移动端效果之Picker

移动端效果之CellSwiper

移动端效果之IndexList

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,而是想滑动页面

```javascript
// 条件
// distanceX = Math.abs(offsetLeft);
// distanceY = Math.abs(offsetTop);
distanceX < 5 || ( distanceY >= 5 && distanceY >= 1.73 * distanceX )
```

四、判断是左移还是右移(offsetLeft < 0 左移,反之,右移)

五、重置位移

```javascript
// 如果存在上一个页面并且是左移
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事件

```javascript
// 时间小于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');
}
}
```

二、判断方向

```javascript
// 如果事件间隔小于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;
}
```

三、执行动画

```javascript
// 当没有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的更多相关文章

  1. 移动端效果之Picker

    写在前面 接着前面的移动端效果的研究,这次来看看picker选择器的实现原理 移动端效果之Swiper 代码看这里:github 1. 核心解析 1.1 基本HTML结构 <!-- 说明: 1. ...

  2. 移动端效果之CellSwiper

    写在前面 接着之前的移动端效果讲解,刚好项目中需要使用到这一效果,去饿了么的组件库看了一下效果,发现效果和微信端的cellSwiper还是有点差别的,由于项目中又是使用的React,之前使用的Reac ...

  3. 移动端效果之IndexList

    写在前面 接着前面的移动端效果讲,这次讲解的的是IndexList的实现原理.效果如下: 代码请看这里:github 移动端效果之swiper 移动端效果之picker 移动端效果之cellSwipe ...

  4. 移动端效果之LoadMore

    写在前面 列表一直是展示数据的一个重要方式,在手机端的列表展示又和PC端展示不同,毕竟手机端主要靠滑.之前手机端之前一直使用的IScroll,但是IScroll本身其实有很多兼容性BUG,想改动一下需 ...

  5. 移动端效果之ScrollList

    写在前面 列表一直是展示数据的一个重要方式,在手机端的列表展示又和PC端展示不同,毕竟手机端主要靠滑.之前手机端之前一直使用的IScroll,但是IScroll本身其实有很多兼容性BUG,想改动一下需 ...

  6. 移动端如何用swiper实现导航栏效果

    在移动端如何用swiper实现导航栏效果 我们在写移动端的时候会有滑动和点击导航后滑动到目标页面功能:而这个功能如果自己写的话会很麻烦,所以我在这推荐一下swiper这个插件. 其实swiper中的官 ...

  7. 在移动端如何用swiper实现导航栏效果

    我们在写移动端的时候会有滑动和点击导航后滑动到目标页面功能:而这个功能如果自己写的话会很麻烦,所以我在这推荐一下swiper这个插件. 其实swiper中的官网中也有这种功能的实现,但是我认为是有点麻 ...

  8. flow-vue.js移动端效果

    得益于vue.js和element,以及vue-element-extends在线表格编辑.前后端分离的后端用golang+beego框架,服务器采用腾讯云. vue的自适应做的很好,只要将侧栏加一行 ...

  9. 移动端网站的内容触摸滑动-Swiper插件

    手机平板等大多移动端站点都会有触摸滑动内容的功能,公司移动端站点(m.muzhiwan.com)的标题广告滑动以及轮播效果就是用的Swiper插件. Swiper就是常用于移动端网站的内容触摸滑动的一 ...

随机推荐

  1. epoll全面讲解:从实现到应用

    多路复用的适用场合 •     当客户处理多个描述符时(例如同时处理交互式输入和网络套接口),必须使用I/O复用. •     如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用 ...

  2. Nginx keepalived实现高可用负载均衡详细配置步骤

    Keepalived是一个免费开源的,用C编写的类似于layer3, 4 & 7交换机制软件,具备我们平时说的第3层.第4层和第7层交换机的功能.主要提供loadbalancing(负载均衡) ...

  3. IOS学习【xcode 7新特性url链接】

    由于xcode7的更新,在访问http链接的时候会输出错误信息 The resource could not be loaded because the App Transport Security ...

  4. [转载]PS各个工具的字母快捷键和英文全名

    原文地址:PS各个工具的字母快捷键和英文全名作者:Tycho     选框-Marquee(M)     移动-move(V)     套索-Lasso(L)     魔棒-Wand(W) 喷枪-in ...

  5. css3 如何实现圆边框的渐变

    使用 css 实现下面效果: 把效果分解. 代码一: <style> .helper1 { height: 40px; padding: 15px; background: -webkit ...

  6. 201521123084 《Java程序设计》第3周学习总结

    1. 本周学习总结 初学面向对象,会学习到很多碎片化的概念与知识.尝试学会使用思维导图将这些碎片化的概念.知识组织起来.请使用纸笔或者下面的工具画出本周学习到的知识点.截图或者拍照上传. 本周学习总结 ...

  7. 微信小程序wx.navigateTo层叠5次限制,特殊情况的建议

    小程序页面的实例使用栈的数据结构存储,栈内元素最多5个(换一种方式说,就是用户最多能点击5次返回),微信小程序能在栈中相对高层某个页面调用其他相对低层的页面实例的方法. 小程序三种页面跳转API 的区 ...

  8. 201521123059 《Java程序设计》第七周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 参考资料: XMind 2. 书面作业 Q1: ArrayList代码分析 1.1 解释ArrayList的contains ...

  9. 201521123054 《Java程序设计》 第2周学习总结

    本周学习总结 能够利用jdk文档处理实验问题: 动态数组的学习: 书面作业 1.使用Eclipse关联jdk源代码,并查看String对象的源代码(截图)?分析String使用什么来存储字符串的?分析 ...

  10. 201521123060 《Java程序设计》第13周学习总结

    1.本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2.书面作业 1.网络基础 1.1比较ping www.baidu.com与ping cec.jmu.edu ...