Hammer.js+轮播原理实现简洁的滑屏功能


Posted in Javascript onFebruary 02, 2016

最近有个任务,做一个非常小的h5的应用,只有2屏,需要做横向的全屏滑动切换和一些简单的动画效果,之前做这种东西用的是fullpage.js和jquery,性能不是很好,于是就想自己动手弄一个简单的东西来实现。最后我用zepto + hammer.js 和轮播的方式解决了这个问题,效果还不错,整个页面不开启Gzip时所有资源请求的数据大小为200KB左右。这篇文章总结下这个方法的实现思路。

效果演示:

Hammer.js+轮播原理实现简洁的滑屏功能

1. 实现要点

1)滑屏借鉴bootstrap的carousel插件,不过完全没有它那个复杂,只需要借鉴它的轮播实现思路即可;

2)滑屏切换的触发,跟PC不一样,PC通常都是通过元素的点击回调来触发,对于滑屏的页面,完全可以利用window的hashchange事件来处理,这样只要通过超链接设置锚点或者通过js改变location.hash就能触发切换;

3)考虑到移动还得支持手势操作,可以使用hammer.js这个手势库,API非常简单易用;

4)动画效果可以用animate.css,不过不用把它所有的代码都弄到代码里,只需要拷贝需要的动画效果相关的代码即可;

5)替代jquery,首选zepto;

6)滑屏效果使用transition动画,为了能够响应动画结束的回调,可以考虑使用transition.js,这个也是Bootstrap提供的工具,不过它默认只能跟jquery使用,要对它稍微改变一下才能跟zepto联合使用。

这些要点说的比较粗糙,后面的内容会一一详细介绍。

2. html结构

空的滑屏页的html结构是这样的:

<div id="container" class="container">
<section id="page-1" class="page page--1">
</section>
<section id="page-2" class="page page--2">
</section>
<section id="page-3" class="page page--3">
</section>
</div>

html,

body {
height: 100%;
-webkit-tap-highlight-color: transparent;
}
.container,
.page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.page {
overflow: hidden;
display: none;
-webkit-transition: -webkit-transform .4s ease;
transition: transform .4s ease;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}

.container与.page初始化的时候采用绝对定位,全屏布局。每一个section.page代表一页,并且默认不显示,所有页的定位都相同,也就是说如果所有页都显示的话,这些页会重叠在一块。

demo页的html结构是:

<div id="container" class="container">
<section id="page-1" class="page page--1">
<div class="page__jump"><a href="#page-2" title="">下一页</a></div>
<p class="page__num animated">1</p>
</section>
<section id="page-2" class="page page--2">
<div class="page__jump"><a href="#page-1" title="">上一页</a><a href="#page-3" title="">下一页</a></div>
<p class="page__num animated">2</p>
</section>
<section id="page-3" class="page page--3">
<div class="page__jump"><a href="#page-2" title="">上一页</a></div>
<p class="page__num animated">3</p>
</section>
</div>

demo相关的css就不展示了。其中animated是应用animate.css需要的,animate.css是一个动画库,github上有。

3. 滑屏切换的实现思路

滑屏切换就是通过js控制2个要滑动的页增加和删除以下定义的这一些css类实现的:

.page.page--active,
.page.page--prev,
.page.page--next {
display: block;
}
.page.page--next,
.page.page--active.page--active-right {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.page.page--prev,
.page.page--active.page--active-left {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
.page.page--next.page--next-left,
.page.page--prev.page--prev-right,
.page.page--active {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}

.page--active表示当前显示的页,页面初始化后,通过以下js调用,给第一页加上.page—active:

var $activePage;
//初始化显示第一页
(function () {
$activePage = $('#page-1');
$activePage.addClass('page--active');
})();

这样页面默认就显示了第一页。以向左滑屏说明这些css的使用原理:

第一步,找到下一页的section,添加page--next类,将它定位当前页的右边,为滑屏做准备;

第二步,找到当前页的section,给它添加page--active-left,由于这个类改变了translate3D属性的值,所以当前页会往左滑动一屏;

在第二步的同时,给下一页的section,添加page--next-left,由于这个类改变了translate3D属性的值,所以下一页会往左滑动一屏;

第三步,在当前页跟下一页滑屏动画结束后,找到原来的当前页,移除掉page--active和page--active-left类;

在第三步的同时,找到下一页,移除掉page--next和page--next-left类,添加page--active。

gif图说明如下:

Hammer.js+轮播原理实现简洁的滑屏功能

向右滑屏原理类似:

第一步,找到上一页的section,添加page--prev类,将它定位当前页的左边,为滑屏做准备;

第二步,找到当前页的section,给它添加page--active-right,由于这个类改变了translate3D属性的值,所以当前页会往右滑动一屏;

在第二步的同时,给上一页的section,添加page--prev-right,由于这个类改变了translate3D属性的值,所以上一页会往右滑动一屏;

第三步,在当前页跟上一页滑屏动画结束后,找到原来的当前页,移除掉page--active和page--active-right类;

在第三步的同时,找到上一页,移除掉page--prev和page--prev-right类,添加page--active。

综合以上实现原理,封装成JS函数如下:

var TRANSITION_DURATION = 400, sliding = false; function getSlideType($targetPage) {
var activePageId = $activePage.attr('id'),
targetPageId = $targetPage.attr('id');
return activePageId < targetPageId ? 'next' : activePageId == targetPageId ? '' : 'prev';
}
function slide(targetPageId) {
var $targetPage = $('#' + targetPageId);
if (!$targetPage.length || sliding) return;
var slideType = getSlideType($targetPage),
direction = slideType == 'next' ? 'left' : 'right';
if (slideType == '') return;
sliding = true;
$targetPage.addClass('page--' + slideType);
$targetPage[0].offsetWidth;
$activePage.addClass('page--active-' + direction);
$targetPage.addClass('page--' + slideType + '-' + direction);
$activePage
.one($.transitionEnd.end, function () {
$targetPage.removeClass(['page--' + slideType, 'page--' + slideType + '-' + direction].join(' ')).addClass('page--active');
$activePage.removeClass(['page--active', 'page--active-' + direction].join(' '));
$activePage = $targetPage;
sliding = false;
})
.emulateTransitionEnd(TRANSITION_DURATION);
}

由于$activePage在页面初始化的时候默认指定为第一页,在每次滑屏结束后都会更新成最新的当前页,所以调用的时候只要把目标页的ID传给slide函数即可。以上代码可能会有疑问的是:

1)$targetPage[0].offsetWidth的作用,这个代码用来触发浏览器的重绘,因为目标页原来是display: none的,如果不触发重绘的话,下一步添加css类后将看不到动画效果;

2)$.transitionEnd.end以及emulateTransitionEnd的作用,这个在下一部分说明。

4. 浏览器css动画结束的回调及模拟

bootstrap提供了一个工具,transition.js,用来判断浏览器是否支持css动画回调事件,以及在浏览器没有在动画结束后自动触发回调的特殊情况下通过模拟的方式来手动触发回调,原先这个工具只能配合jquery使用,为了在zepto中使用,必须稍微改变一下,下面就是改变之后的代码:

(function(){
var transition = $.transitionEnd = {
end: (function () {
var el = document.createElement('transitionEnd'),
transEndEventNames = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return transEndEventNames[name];
}
}
return false;
})()
};
$.fn.emulateTransitionEnd = function (duration) {
var called = false,
_this = this,
callback = function () {
if (!called) $(_this).trigger(transition.end);
};
$(this).one(transition.end, function () {
called = true;
});
setTimeout(callback, duration);
return this;
};
})();

$.transitionEnd.end表示当前浏览器支持的动画结束事件的名称。$.fn.emulateTransitionEnd是一个扩展了Zepto原型的一个方法,传入一个动画的过渡时间,当这个时间段过完之后,如果浏览器没有自动触发回调事件,called就始终是false,setTimeout会导致callback被调用,然后callback内部就会手动触发动画结束的回调。为什么要通过这个方式来模拟动画结束,是因为浏览器即使支持动画结束事件的回调,但是有些时候并不会触发这个事件,或者在动画结束后不能立即触发,影响回调的准确性。传入的duration应该与执行动画的元素,在css上设置的transtion-duration相同,注意以下代码中标黄的部分:

var TRANSITION_DURATION = 400 ;
$activePage
.one($.transitionEnd.end, function () {
$targetPage.removeClass(['page--' + slideType, 'page--' + slideType + '-' + direction].join(' ')).addClass('page--active');
$activePage.removeClass(['page--active', 'page--active-' + direction].join(' '));
$activePage = $targetPage;
sliding = false;
})
.emulateTransitionEnd(TRANSITION_DURATION); 
.page {
overflow: hidden;
display: none;
-webkit-transition: -webkit-transform .4s ease;
transition: transform .4s ease;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}

5. hashchange事件

PC端滑屏都是给元素添加点击事件触发的,移动端可以利用window的hashchange事件:

$(window).on('hashchange', function (e) {
var hash = location.hash;
if (!hash) hash = '#page-1';
slide(hash.substring(1));
});
location.hash = '#page-1';

hashchange事件,在js代码中通过改变loaction.hash或者是点击<a href="#page-2" title="">下一页</a>这样的超链接时,都会触发,所以只要在这个事件的回调去做滑屏切换即可。这样那些上一页和下一页的链接元素都不用加事件了。

6. hammer.js使用简介

hammer.js是一个手势库,支持常用的手势操作,使用简单,引入它的js之后,通过以下的方式来支持手势滑屏:

//初始化手势滑动
var container = document.getElementById('container'),
mc = new Hammer.Manager(container),
Swipe = new Hammer.Swipe();
mc.add(Swipe);
mc.on('swipeleft', function (e) {
swipteTo('next', e);
});
mc.on('swiperight', function (e) {
swipteTo('prev', e);
});
function swipteTo(slideType, e) {
var $targetPage = $activePage[slideType]('.page');
$targetPage.length && (location.hash = '#' + $targetPage.attr('id'));
}

把整个container元素作为滑屏的stage,监听到swipeleft事件,就表示向左滑,页面应该显示下一页;监听到swiperight事件,就表示向右滑,页面应该显示下一页。

7. 结束语

animate.css的使用就不详细介绍了,比较简单,这是它的github地址:https://github.com/daneden/animate.css,是一个非常好用的动画库。本文把最近的一点工作经验记录了下来,技术上的东西,有的时候一些文字不能完全讲的清楚,所以我只能尽自己的能力去把一些问题讲地稍微细致一点,说的不对和有问题的尽管在评论区与我说明,我会认真查看,另外我自己对移动端这一块入门不深,您有更好的见解,欢迎与我们一起分享。谢谢您的阅读,马上就新年,祝您猴年大吉!

Javascript 相关文章推荐
Chosen 基于jquery的选择框插件使用方法
May 30 Javascript
JS的replace方法介绍
Oct 20 Javascript
jquery+css+ul模拟列表菜单具体实现思路
Apr 15 Javascript
Javascript解析URL方法详解
Dec 05 Javascript
jQuery实现简单的间隔向上滚动效果
Mar 09 Javascript
Jquery技巧(必须掌握)
Mar 16 Javascript
ES6中参数的默认值语法介绍
May 03 Javascript
微信小程序商品详情页的底部弹出框效果
Nov 16 Javascript
jQuery实现的页面详情展开收起功能示例
Jun 11 jQuery
详解JS转换数值函数Number()、parseInt()、parseFloat()
Aug 24 Javascript
详解js访问对象的属性和方法
Oct 25 Javascript
vue界面发送表情的实现代码
Sep 11 Javascript
JavaScript实现的MD5算法完整实例
Feb 02 #Javascript
javascript禁止超链接跳转的方法
Feb 02 #Javascript
JS组件Bootstrap Table使用方法详解
Feb 02 #Javascript
Node.js实现JS文件合并小工具
Feb 02 #Javascript
体验jQuery和AngularJS的不同点及AngularJS的迷人之处
Feb 02 #Javascript
JavaScript通过代码调用Flash显示的方法
Feb 02 #Javascript
AngularJS中$interval的用法详解
Feb 02 #Javascript
You might like
DOMXML函数笔记
2006/10/09 PHP
PHP批量检测并去除文件BOM头代码实例
2014/05/08 PHP
php+ajax登录跳转登录实现思路
2016/07/31 PHP
php mysql 封装类实例代码
2016/09/18 PHP
JavaScript的document对象和window对象详解
2010/12/30 Javascript
jquery一般方法介绍 入门参考
2011/06/21 Javascript
使用js实现按钮控制文本框加1减1应用于小时+分钟
2013/12/09 Javascript
浅析node.js中close事件
2014/11/26 Javascript
javascript实现简单加载随机色方块
2015/12/25 Javascript
JS实现队列与堆栈的方法
2016/04/21 Javascript
浅谈toLowerCase和toLocaleLowerCase的区别
2016/08/15 Javascript
jQuery表单对象属性过滤选择器实例详解
2016/09/13 Javascript
AngularJs ng-repeat 嵌套如何获取外层$index
2016/09/21 Javascript
微信小程序 省市区选择器实例详解(附源码下载)
2017/01/05 Javascript
IE浏览器下JS脚本提交表单后,不能自动提示问题解决方法
2019/06/04 Javascript
Vue登录主页动态背景短视频制作
2019/09/21 Javascript
[01:02:03]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS VG
2014/05/26 DOTA
[11:01]2014DOTA2西雅图邀请赛 冷冷带你探秘威斯汀
2014/07/08 DOTA
[57:28]2018DOTA2亚洲邀请赛 4.6 淘汰赛 TNC vs Liquid 第一场
2018/04/10 DOTA
python调用java的Webservice示例
2014/03/10 Python
如何通过python画loss曲线的方法
2019/06/26 Python
Python中turtle库的使用实例
2019/09/09 Python
Python帮你微信头像任意添加装饰别再@微信官方了
2019/09/25 Python
解决python父线程关闭后子线程不关闭问题
2020/04/25 Python
什么是CSS3 HSLA色彩模式?HSLA模拟渐变色条
2016/04/26 HTML / CSS
HTML5 HTMLCollection和NodeList的区别详解
2020/04/29 HTML / CSS
video.js支持m3u8格式直播的实现示例
2020/05/20 HTML / CSS
智能旅行箱:Horizn Studios
2018/04/30 全球购物
迪卡侬比利时官网:Decathlon比利时
2019/12/28 全球购物
什么是典型的软件三层结构?软件设计为什么要分层?软件分层有什么好处?
2012/03/14 面试题
音乐专业应届生教师求职信
2013/11/04 职场文书
员工培训邀请函
2014/01/11 职场文书
年度考核评语
2014/01/19 职场文书
优秀部门获奖感言
2014/02/14 职场文书
大学生第一学年自我鉴定
2014/09/12 职场文书
小学教师教学随笔
2015/08/14 职场文书