<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" /> | |
<title>手势实现图像预览</title> | |
<link rel="stylesheet" href="style_c0d0732.css"> | |
</head> | |
<body> | |
<div class="card"> | |
<div class="head"> | |
<div class="avatar"> | |
<img src="https://dn-lay.qbox.me/build/single-page/images/my-avatar_0b91c8c.jpg"> | |
</div> | |
<div class="username">剧中人</div> | |
<div class="time">今天15:34</div> | |
</div> | |
<div class="content"> | |
字不重要看图,对的,看图。 | |
</div> | |
<div class="pics"> | |
<div class="item"> | |
<img src="imgs/me_thumb_206a0fd.jpg"> | |
</div> | |
<div class="item"> | |
<img src="imgs/psb_thumb_e125261.jpg"> | |
</div> | |
<div class="item"> | |
<img src="imgs/window_thumb_c99152e.jpg"> | |
</div> | |
</div> | |
<div class="comment"> | |
我也说一句 | |
</div> | |
</div> | |
<script type="text/tpl" id=tpl> | |
<div class="swiper zoomIn"> | |
<div class="swiper-list"> | |
<div class="swiper-list-body"> | |
<div class="swiper-item"></div> | |
<div class="swiper-item"></div> | |
<div class="swiper-item"></div> | |
</div> | |
</div> | |
<div class="swiper-num"> | |
<span class="current-index">1</span class="total-count">/<span>3</span> | |
</div> | |
<div class="swiper-menu"> | |
<div class="menu-pop"> | |
<div class="main-list"> | |
<a class="menu-item" href="javascript:void(0)">保存到手机</a> | |
<a class="menu-item" href="javascript:void(0)">发送给 QQ 好友</a> | |
<a class="menu-item" href="javascript:void(0)">发送给微信好友</a> | |
<a class="menu-item" href="javascript:void(0)">收藏</a> | |
</div> | |
<div class="cancel"> | |
<a href="javascript:void(0)">取消</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
</script> | |
<script src="js/toucher_2cb1d8f.js"></script> | |
<script src="js/swipe_8158bb6.js"></script> | |
<script type="text/javascript"> | |
(function(){ | |
var node_pics = document.getElementsByClassName('item'), | |
big_pic_urls = [ | |
'imgs/me_ba1d3e6.jpg', | |
'imgs/psb_da08ff5.jpeg', | |
'imgs/window_e02f4ca.jpg' | |
]; | |
Array.prototype.forEach.call(node_pics,function(node,index){ | |
//监听点击事件 | |
new util.toucher(node).on('singleTap',function(){ | |
new swiper(big_pic_urls,index); | |
}); | |
//偷偷加载图片 | |
new Image().src=big_pic_urls[index]; | |
}); | |
})(); | |
</script> | |
</body> | |
</html> | |
(function(global,doc,factoryFn){ //初始化toucher主方法 var factory = factoryFn(global,doc); //提供window.util.toucher()接口 global.util = global.util || {}; global.util.toucher = global.util.toucher || factory; //提供CommonJS规范的接口 global.define && define(function(require,exports,module){ return factory; }); })(this,document,function(window,document){ /** * 判断是否拥有某个class */ function hasClass(dom,classSingle){ return dom.className.match(new RegExp('(\\s|^)' + classSingle +'(\\s|$)')); } /** * @method 事件触发器 * @description 根据事件最原始被触发的target,逐级向上追溯事件绑定 * * @param string 事件名 * @param object 原生事件对象 */ function EMIT(eventName,e){ this._events = this._events || {}; //事件堆无该事件,结束触发 if(!this._events[eventName]){ return; } //记录尚未被执行掉的事件绑定 var rest_events = this._events[eventName]; //从事件源:target开始向上冒泡 var target = e.target; while (1) { //若没有需要执行的事件,结束冒泡 if(rest_events.length ==0){ return; } //若已经冒泡至顶,检测顶级绑定,结束冒泡 if(target == this.dom || !target){ //遍历剩余所有事件绑定 for(var i=0,total=rest_events.length;i<total;i++){ var classStr = rest_events[i]['className']; var callback = rest_events[i]['fn']; //未指定事件委托,直接执行 if(classStr == null){ event_callback(eventName,callback,target,e); } } return; } //当前需要校验的事件集 var eventsList = rest_events; //置空尚未执行掉的事件集 rest_events = []; //遍历事件所有绑定 for(var i=0,total=eventsList.length;i<total;i++){ var classStr = eventsList[i]['className']; var callback = eventsList[i]['fn']; //符合事件委托,执行 if(hasClass(target,classStr)){ //返回false停止事件冒泡及后续事件,其余继续执行 if(event_callback(eventName,callback,target,e) == false){ return; } }else{ //不符合执行条件,压回到尚未执行掉的列表中 rest_events.push(eventsList[i]); } } //向上冒泡 target = target.parentNode; } } /** * 执行绑定的回调函数,并创建一个事件对象 * @param[string]事件名 * @param[function]被执行掉的函数 * @param[object]指向的dom * @param[object]原生event对象 */ function event_callback(name,fn,dom,e){ //优先使用自定义的touches(目前是为了解决touchEnd无touches的问题) var touches = e.plugTouches || e.touches, touch = touches.length ? touches[0] : {}, newE = { type : name, target : e.target, pageX : touch.pageX, pageY : touch.pageY }; //为swipe事件增加交互初始位置及移动距离 if(name.match(/^swipe/) && e.plugStartPosition){ newE.startX = e.plugStartPosition.pageX; newE.startY = e.plugStartPosition.pageY; newE.moveX = newE.pageX - newE.startX; newE.moveY = newE.pageY - newE.startY; } //执行绑定事件的回调,并记录返回值 var call_result = fn.call(dom,newE); //若返回false,阻止浏览器默认事件 if(call_result == false){ e.preventDefault(); e.stopPropagation(); } return call_result; } /** * 判断swipe方向 */ function swipeDirection(x1, x2, y1, y2) { return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down'); } /** * 监听原生的事件,主动触发模拟事件 * */ function eventListener(DOM){ var this_touch = this; //轻击开始时间 var touchStartTime = 0; //记录上一次点击时间 var lastTouchTime = 0; //记录初始轻击的位置 var x1,y1,x2,y2; //轻击事件的延时器 var touchDelay; //测试长按事件的延时器 var longTap; //记录当前事件是否已为等待结束的状态 var isActive = false; //记录有坐标信息的事件 var eventMark = null; //单次用户操作结束 function actionOver(e){ isActive = false; clearTimeout(longTap); clearTimeout(touchDelay); } //断定此次事件为轻击事件 function isSingleTap(){ actionOver(); EMIT.call(this_touch,'singleTap',eventMark); } //触屏开始 function touchStart(e){ //缓存事件 eventMark = e; x1 = e.touches[0].pageX; y1 = e.touches[0].pageY; x2 = 0; y2 = 0; isActive = true; touchStartTime = new Date(); EMIT.call(this_touch,'swipeStart',e); //检测是否为长按 clearTimeout(longTap); longTap = setTimeout(function(){ actionOver(e); //断定此次事件为长按事件 EMIT.call(this_touch,'longTap',e); },500); } //触屏结束 function touchend(e){ //touchend中,拿不到坐标位置信息,故使用全局保存下数据 e.plugStartPosition = eventMark.plugStartPosition; e.plugTouches = eventMark.touches; EMIT.call(this_touch,'swipeEnd',e); if(!isActive){ return; } var now = new Date(); //若未监听doubleTap,直接响应 if(!this_touch._events.doubleTap || this_touch._events.doubleTap.length == 0){ isSingleTap(); }else if(now - lastTouchTime > 200){ //延迟响应 touchDelay = setTimeout(isSingleTap,190); }else{ clearTimeout(touchDelay); actionOver(e); //断定此次事件为连续两次轻击事件 EMIT.call(this_touch,'doubleTap',eventMark); } lastTouchTime = now; } //手指移动 function touchmove(e){ //缓存事件 eventMark = e; //在原生事件基础上记录初始位置(为swipe事件增加参数传递) e.plugStartPosition = { pageX : x1, pageY : y1 }; //断定此次事件为移动事件 EMIT.call(this_touch,'swipe',e); if(!isActive){ return; } x2 = e.touches[0].pageX; y2 = e.touches[0].pageY; if(Math.abs(x1-x2)>2 || Math.abs(y1-y2)>2){ //断定此次事件为移动手势 var direction = swipeDirection(x1, x2, y1, y2); EMIT.call(this_touch,'swipe' + direction,e); }else{ //断定此次事件为轻击事件 isSingleTap(); } actionOver(e); } /** * 对开始手势的监听 */ DOM.addEventListener('touchstart',touchStart); DOM.addEventListener('MSPointerDown',touchStart); DOM.addEventListener('pointerdown',touchStart); /** * 对手势结束的监听(轻击) */ DOM.addEventListener('touchend',touchend); DOM.addEventListener('MSPointerUp',touchend); DOM.addEventListener('pointerup',touchend); /** * 对移动手势的监听 */ DOM.addEventListener('touchmove',touchmove); DOM.addEventListener('MSPointerMove',touchmove); DOM.addEventListener('pointermove',touchmove); /** * 对移动结束的监听 */ DOM.addEventListener('touchcancel',actionOver); DOM.addEventListener('MSPointerCancel',actionOver); DOM.addEventListener('pointercancel',actionOver); } /** * touch类 * */ function Touch(DOM,param){ var param = param || {}; this.dom = DOM; //存储监听事件的回调 this._events = {}; //监听DOM原生事件 eventListener.call(this,this.dom); } /** * @method 增加事件监听 * @description 支持链式调用 * * @param string 事件名 * @param [string] 事件委托至某个class(可选) * @param function 符合条件的事件被触发时需要执行的回调函数 * **/ Touch.prototype.on = function ON(eventStr,a,b){ var className,fn; if(typeof(a) == 'string'){ className = a.replace(/^\./,''); fn = b; }else{ className = null; fn = a; } //检测callback是否合法,事件名参数是否存在· if(typeof(fn) == 'function' && eventStr && eventStr.length){ var eventNames = eventStr.split(/\s+/); for(var i=0,total=eventNames.length;i<total;i++){ var eventName = eventNames[i]; //事件堆无该事件,创建一个事件堆 if(!this._events[eventName]){ this._events[eventName] = []; } this._events[eventName].push({ className : className, fn : fn }); } } //提供链式调用的支持 return this; }; //对外提供接口 return function (dom){ return new Touch(dom); }; });js2:
(function(){ /** * 判断dom是否拥有某个class **/ function hasClass(dom,classSingle){ return dom.className && dom.className.match(new RegExp('(\\s|^)' + classSingle + '(\\s|$)')) || false; } function addClass(dom, cls) { if (!hasClass(dom, cls)) dom.className += " " + cls; } function removeClass(dom, cls) { if (hasClass(dom, cls)) { var reg = new RegExp('(\\s+|^)' + cls + '(\\s+|$)'); dom.className = dom.className.replace(reg, ' '); } } function setCss(node,cssObj){ for(var key in cssObj){ node.style[key] = cssObj[key]; if(key == 'transform' || key == 'transition'){ node.style['-webkit-' + key] = cssObj[key]; } } } function createDom(html){ var a = document.createElement('div'); a.innerHTML = html.replace(/^\s+|\s+$/g,''); return a.childNodes[0]; } function removeNode(elem){ if(elem && elem.parentNode && elem.tagName != 'BODY'){ elem.parentNode.removeChild(elem); } } var tpl = document.getElementById('tpl').innerHTML, body = document.getElementsByTagName('body')[0]; function swiper(pics,index){ var me = this, touch; me.node = createDom(tpl); me.swiper_list = me.node.getElementsByClassName('swiper-list')[0]; me.swiper_list_body = me.node.getElementsByClassName('swiper-list-body')[0]; me.swiper_items = me.swiper_list.getElementsByClassName('swiper-item'); me.current_index = me.node.getElementsByClassName('current-index')[0]; body.appendChild(me.node); me.index = index || 0; me.width = me.getWidth(); me.total = me.swiper_items.length; me.refresh(); me.scrollToItem(me.index,false); Array.prototype.forEach.call(me.swiper_items,function(node,index){ node.style['background-image'] = 'url(' + pics[index] + ')'; }); new util.toucher(me.node) .on('swipe',function(){ return false; }).on('swipeLeft',function(){ if(me.index + 1 >= me.total){ me.shock('toLeft'); return; } me.index++; me.scrollToItem(me.index,true); }).on('swipeRight',function(){ if(me.index <= 0){ me.shock('toRight'); return } me.index--; me.scrollToItem(me.index,true); }).on('longTap',function(){ me.showMenu(); return false; }).on('singleTap',function(){ me.destroy(); }) //重置 pop 手势 .on('singleTap','.swiper-menu',function(){ me.closeMenu(); return false; }).on('swipeLeft','.swiper-menu',function(){ return false; }).on('swipeRight','.swiper-menu',function(){ return false; }).on('longTap','.swiper-menu',function(){ return false; }).on('singleTap','.menu-item',function(){ return false; }); this.node.οncοntextmenu=function(){ return false; } } swiper.prototype.destroy = function(){ var me = this; removeClass(me.node,'zoomIn'); addClass(me.node,'zoomOut'); setTimeout(function(){ removeNode(me.node); },400); } swiper.prototype.getWidth = function(){ return this.node.clientWidth; } swiper.prototype.showMenu = function(){ addClass(this.node,'showMenu'); } swiper.prototype.closeMenu = function(){ removeClass(this.node,'showMenu'); } swiper.prototype.refresh = function(){ var width = this.width; this.swiper_list_body.style.width = this.total * this.width + 'px'; Array.prototype.forEach.call(this.swiper_items,function(node,index){ node.style.width = width + 'px' }); }; swiper.prototype.shock = function(direction){ var me = this, left = -me.index * me.width, a = direction=='toLeft' ? -40 : 40; me.scrollTo(left + a,true); setTimeout(function(){ me.scrollTo(left,true); },200); }; swiper.prototype.scrollToItem = function(index,useAnimation){ this.current_index.innerHTML = index + 1; this.scrollTo(-index * this.width,useAnimation); }; swiper.prototype.scrollTo = function(left,useAnimation){ var useAnimation = typeof(useAnimation) == 'boolean' ? useAnimation : false, cssObj = { left: left + 'px' }; cssObj.transition = useAnimation ? '.3s ease-in-out' : '0'; setCss(this.swiper_list_body,cssObj); }; window.swiper = swiper; })();
css:
* { margin: 0; padding: 0; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } body { background: #fff; overflow: hidden; } .card { margin-top: 30px; background: #fff; border: 1px solid #ddd; border-width: 24px 0; } .card .head { height: 50px; padding: 15px; } .card .head .avatar { float: left; width: 50px; height: 50px; margin-right: 15px; border-radius: 50%; overflow: hidden; background: #eee; } .card .head .avatar img { display: block; width: 100%; height: 100%; } .card .head .username { line-height: 30px; font-size: 18px; color: #000; } .card .head .time { line-height: 15px; font-size: 13px; color: #aaa; } .card .content { padding: 10px 15px 20px; color: #000; font-size: 16px; } .card .pics { margin: 0 15px 20px; overflow: hidden; font-size: 0; } .card .pics .item { display: inline-block; width: 33.333333%; overflow: hidden; background: #333; } .card .pics .item::before { content: ''; display: block; width: 100%; padding-top: 100%; } .card .pics .item img { display: block; width: 100%; margin-top: -100%; } .card .comment { height: 25px; border: 1px solid #eee; margin: 0 15px 20px; text-indent: 10px; line-height: 25px; font-size: 12px; color: #ccc; } .swiper { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: #000; overflow: hidden; } .swiper-list { position: absolute; width: 100%; height: 100%; overflow: hidden; } .swiper-list-body { position: absolute; width: 300%; height: 100%; top: 0; left: 0; } .swiper-item { position: relative; float: left; width: 33.33333%; height: 100%; background-size: contain; background-repeat: no-repeat; background-position: center; } .swiper-num { position: absolute; bottom: 50px; left: 50%; width: 100px; margin-left: -50px; text-align: center; font-size: 14px; color: #fff; } .swiper-menu { position: absolute; width: 100%; height: 100%; top: 100%; left: 0; transition: 0.3s ease-in-out; -webkit-transition: 0.3s ease-in-out; } .showMenu .swiper-menu { top: 0; } .menu-pop { position: absolute; left: 0; bottom: 0; width: 100%; } .menu-pop .main-list, .menu-pop .cancel { margin: 0 5px 5px; border-radius: 4px; background: rgba(255, 255, 255, 0.9); box-shadow: 0 0 1px rgba(0, 0, 0, 0.5); } .menu-pop a { display: block; line-height: 40px; text-align: center; text-decoration: none; font-size: 14px; } .menu-pop a + a { border-top: 1px solid #ccc; } @-webkit-keyframes zoomIn { from { opacity: 0; -webkit-transform: scale3d(0.6, 0.6, 0.6); transform: scale3d(0.6, 0.6, 0.6); } 50% { opacity: 1; } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes zoomIn { from { opacity: 0; -webkit-transform: scale3d(0.6, 0.6, 0.6); transform: scale3d(0.6, 0.6, 0.6); } 50% { opacity: 1; } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @-webkit-keyframes zoomOut { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 20% { opacity: 1; } to { opacity: 0; -webkit-transform: scale3d(0.6, 0.6, 0.6); transform: scale3d(0.6, 0.6, 0.6); } } @keyframes zoomOut { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 20% { opacity: 1; } to { opacity: 0; -webkit-transform: scale3d(0.6, 0.6, 0.6); transform: scale3d(0.6, 0.6, 0.6); } } .zoomIn { -webkit-animation: zoomIn 0.3s ease-in-out forwards; animation: zoomIn 0.3s ease-in-out forwards; } .zoomOut { -webkit-animation: zoomOut 0.2s ease-in-out forwards; animation: zoomOut 0.2s ease-in-out forwards; }
原文:http://bh-lay.github.io/toucher/swiper/