diff --git a/src/js/anot.shim.js b/src/js/anot.shim.js index d4d0c72..3e1334e 100644 --- a/src/js/anot.shim.js +++ b/src/js/anot.shim.js @@ -5725,7 +5725,9 @@ time: function(str) { str = str >> 0 var s = str % 60 - ;(m = Math.floor(str / 60)), (h = Math.floor(m / 60)), (m = m % 60) + var m = Math.floor(str / 60) + var h = Math.floor(m / 60) + m = m % 60 m = m < 10 ? '0' + m : m s = s < 10 ? '0' + s : s diff --git a/src/js/layer/base.js b/src/js/layer/base.js index 824fb1f..a79c1b7 100644 --- a/src/js/layer/base.js +++ b/src/js/layer/base.js @@ -61,7 +61,7 @@ const close = function(id) { } } -const reapeat = function(str, num) { +const repeat = function(str, num) { var idx = 0, result = '' while (idx < num) { @@ -77,15 +77,15 @@ const fixOffset = function(val) { return val } } -const __layer__ = function(conf) { - if (conf) { - let { yes, no, success } = conf - delete conf.yes - delete conf.no - delete conf.success +const __layer__ = function(opt) { + if (opt) { + let { yes, no, success } = opt + delete opt.yes + delete opt.no + delete opt.success this.construct({ - state: { ...conf }, + state: { ...opt }, props: { yes, no, success } }) .append() @@ -130,27 +130,35 @@ const _layer = { } return _layer.open(opt) }, - msg: function(msg, conf) { - if (typeof conf !== 'object') { - var tmp = conf - conf = { timeout: 2500 } - if (typeof tmp === 'number') { - conf.icon = tmp - } + toast: function(content, type, timeout, cb) { + // if (typeof opt !== 'object') { + // var tmp = opt + // opt = { timeout: 2500 } + // if (typeof tmp === 'number') { + // opt.icon = tmp + // } + // } + + if (typeof type === 'number') { + timeout = type + type = 'info' } - if (!conf.hasOwnProperty('timeout')) { - conf.timeout = 2500 + // if (!opt.hasOwnProperty('timeout')) { + // opt.timeout = 2500 + // } + + let opt = { + content: `${content}`, + menubar: false, + mask: false, + type: 7, + fixed: true } - conf.specialMode = true //特殊模式 - conf.content = '

' + msg + '

' - conf.type = 7 - conf.fixed = true - conf.shade = false - conf.menubar = false - conf.radius = '5px' - return _layer.open(conf) + opt.toast = true // toast模式 + + return _layer.open(opt) }, loading: function(style, time, cb) { style = style >>> 0 @@ -174,34 +182,34 @@ const _layer = { fixed: true }) }, - tips: function(content, container, conf) { + tips: function(content, container, opt) { if (!(container instanceof HTMLElement)) { return Anot.error('tips类型必须指定一个目标容器') } - if (typeof conf !== 'object') { - var tmp = conf - conf = { timeout: 2500 } + if (typeof opt !== 'object') { + var tmp = opt + opt = { timeout: 2500 } if (typeof tmp === 'number') { - conf.icon = tmp + opt.icon = tmp } } - if (!conf.hasOwnProperty('timeout')) { - conf.timeout = 2500 + if (!opt.hasOwnProperty('timeout')) { + opt.timeout = 2500 } - if (!conf.background) { - conf.background = 'rgba(0,0,0,.5)' + if (!opt.background) { + opt.background = 'rgba(0,0,0,.5)' } - if (!conf.color) { - conf.color = '#fff' + if (!opt.color) { + opt.color = '#fff' } - conf.container = container - conf.content = content - conf.type = 5 - conf.icon = 0 - conf.fixed = true - conf.shade = false - conf.menubar = false - return _layer.open(conf) + opt.container = container + opt.content = content + opt.type = 5 + opt.icon = 0 + opt.fixed = true + opt.shade = false + opt.menubar = false + return _layer.open(opt) }, prompt: function(title, yescb) { if (typeof yescb !== 'function') { @@ -226,33 +234,33 @@ const _layer = { require(['css!./skin/' + skin], callback) }, close: close, - open: function(conf) { - if (typeof conf === 'string') { - /*conf = '$wrap-' + conf - if (!layerObj[conf]) { + open: function(opt) { + if (typeof opt === 'string') { + /*opt = '$wrap-' + opt + if (!layerObj[opt]) { throw new Error('layer实例不存在') } else { //只能显示一个实例 - if (layerObj[conf].show) { - return conf + if (layerObj[opt].show) { + return opt } - layerObj[conf].show = true + layerObj[opt].show = true - if (!Anot.vmodels[conf]) { - Anot(layerObj[conf].obj.init) + if (!Anot.vmodels[opt]) { + Anot(layerObj[opt].obj.init) } - layerObj[conf].parentElem.appendChild(layerDom[conf][1]) - layerDom[conf][1] + layerObj[opt].parentElem.appendChild(layerDom[opt][1]) + layerDom[opt][1] .querySelector('.detail') - .appendChild(layerObj[conf].wrap) - layerObj[conf].wrap.style.display = '' - // Anot.scan(layerDom[conf][1]) - layerObj[conf].obj.show() - return conf + .appendChild(layerObj[opt].wrap) + layerObj[opt].wrap.style.display = '' + // Anot.scan(layerDom[opt][1]) + layerObj[opt].obj.show() + return opt }*/ } else { - return new __layer__(conf).init.$id + return new __layer__(opt).init.$id } }, version: Anot.ui.layer @@ -277,16 +285,24 @@ __layer__.prototype = { 5: 9 }, timeout: null, - construct: function(conf) { - let _id = conf.$id || uuid() + construct: function(opt) { + let _id = opt.$id || uuid() this.init = { $id: _id, state: { ...defconf, - ...conf.state + ...opt.state }, - props: conf.props, - skip: ['area', 'shift', 'skin', 'mask', 'maskClose', 'container'], + props: opt.props, + skip: [ + 'area', + 'shift', + 'skin', + 'mask', + 'maskClose', + 'container', + 'follow' + ], methods: { onMaskClick: function() { if (this.type < 4 && !this.maskClose) { @@ -358,8 +374,8 @@ __layer__.prototype = { if (state.type === 5) { layBox.classList.add('active') } - if (state.specialMode && state.type === 7) { - layBox.classList.add('type-unspecial') + if (state.toast) { + layBox.classList.add('type-toast') } else { layBox.classList.add('type-' + state.type) } @@ -396,9 +412,10 @@ __layer__.prototype = { ${this.getMenubar()}
+ style="${boxcss}" + ${!state.wrap && state.type !== 6 ? ':html="content"' : ''}> - ${this.getCont()} + ${state.type === 6 ? this.getLoading(state.load) : ''}
${this.getCtrl()} ${arrow} @@ -407,17 +424,14 @@ __layer__.prototype = { outerBox.appendChild(layBox) return [outerBox, layBox] }, - getCont: function() { - let { state, $id } = this.init - if (state.type === 6) { - return this.getLoading(state.load) - } else { - return ` - ${this.getIcon()} -
- ` - } - }, + // getCont: function() { + // let { state, $id } = this.init + // if (state.type === 6) { + // return this.getLoading(state.load) + // } else { + // return !state.wrap ? '{{content | html}}' : '' + // } + // }, getLoading: function(style) { return `
@@ -481,7 +495,7 @@ __layer__.prototype = { } }, append: function() { - let { state, $id } = this.init + let { state, $id, container } = this.init //如果有已经打开的弹窗,则关闭 if (unique) { _layer.close(unique) @@ -491,14 +505,17 @@ __layer__.prototype = { } layerDom[$id] = this.create() - delete state.specialMode + delete state.toast + if (!container) { + container = document.body + } - document.body.appendChild(layerDom[$id][0]) + container.appendChild(layerDom[$id][0]) this.vm = Anot(this.init) return this }, show: function() { - let { state, $id, container } = this.init + let { state, $id, follow } = this.init setTimeout(function() { var style = { visibility: '', background: state.background } @@ -509,10 +526,10 @@ __layer__.prototype = { // only type[tips] can define `color` style.color = state.color - let $container = Anot(container) - let ew = $container.innerWidth() - let ol = $container.offset().left - document.body.scrollLeft - let ot = $container.offset().top - document.body.scrollTop + let $follow = Anot(follow) + let ew = $follow.innerWidth() + let ol = $follow.offset().left - document.body.scrollLeft + let ot = $follow.offset().top - document.body.scrollTop style.left = ol + ew * 0.7 style.top = ot - parseInt(css.height) - 8 @@ -557,7 +574,7 @@ __layer__.prototype = { _this.vm.yes($id) } }, state.timeout) - } else if (statetype === 6) { + } else if (state.type === 6) { // loading类型, 非自动关闭时, 主动触发回调 this.vm.yes($id) } diff --git a/src/js/layer/skin/def.scss b/src/js/layer/skin/def.scss index fa38230..a05028e 100644 --- a/src/js/layer/skin/def.scss +++ b/src/js/layer/skin/def.scss @@ -13,15 +13,9 @@ .layer-box {position:absolute;} - - &.mask {position:fixed;z-index:65534;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,.3); - - .layer-box {position:absolute;} - } - - a {text-decoration:none;} + .layer-box {left:50%;top:50%;z-index:65535; &.shift {transition: all .3s ease-in-out;} @@ -47,20 +41,19 @@ .layer-title {width:100%;height:43px;padding:0 10px;line-height:43px;font-size:16px;color:nth($cgr, 1);} /* 弹层主体内容 */ - .layer-content {position:relative;width:100%;height:auto;min-height:50px;padding:10px; + .layer-content {position:relative;width:100%;height:auto;min-height:50px;padding:10px;word-break:break-all;word-wrap: break-word; - .msg-icon {position:absolute;left:10px;top:10px;width:50px;height:auto;line-height:40px;font-size:35px;text-align:center;} - .detail {width:auto;height:100%;margin:auto auto auto 50px;padding:5px 15px;word-break:break-all;word-wrap: break-word; + // .msg-icon {position:absolute;left:10px;top:10px;width:50px;height:auto;line-height:40px;font-size:35px;text-align:center;} + + .prompt-value {width: 100%;height: 30px;padding: 0 8px;border: 1px solid #ddd;border-radius: 3px; - .prompt-value {width: 230px;height: 30px;padding: 0 8px;border: 1px solid #ddd;border-radius: 3px; + &.alert {border-color:nth($cr, 1)} + &:focus {border-color:nth($ct, 1)} + } + .msg-box {line-height:30px;} + - &.alert {border-color:nth($cr, 1)} - &:focus {border-color:nth($ct, 1)} - } - .msg-box {line-height:30px;} - } - - &.none-icon .detail {margin:0 auto;} + &.none-icon .detail {margin:0 auto;} } @@ -92,7 +85,7 @@ &.active {visibility:visible;opacity:1;} i.arrow {position:absolute;left:5px;bottom:-14px;width:0;height:0;border:6px solid transparent;border-top:8px solid rgba(0,0,0,.5);content: ""} - .layer-content .detail {margin:0;padding:0} + .layer-content {margin:0;padding:0} } @@ -100,11 +93,15 @@ &.type-6 {box-shadow:none;background:transparent;} - /* 特殊类弹层(msg弹层) */ - &.type-unspecial {min-width:10px;background:transparent; + /* 特殊类弹层(toast弹层) */ + &.type-toast {min-width:10px;padding:0;background:transparent; - .layer-content {min-height:60px;padding:0} - .layer-content .detail {margin:0;padding:0} + .layer-content {min-height:40px;height:40px;padding:0} + + .toast-box {display:inline-block;position:relative;min-height:40px;padding:5px 8px 5px 50px;line-height:28px;border:1px solid nth($co, 1);border-radius:3px;background:#fffbed;color:nth($co, 3);word-break: break-all; + + &::before {position:absolute;left:15px;top:5px;font:20px/28px "ui font";color:nth($cr, 1);content:"\e6f6";} + } } @@ -182,6 +179,11 @@ } } + + &.mask {position:fixed;z-index:65534;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,.3); + + .layer-box {position:absolute;} + } &:active {z-index:65536;} } diff --git a/src/js/router/main.js b/src/js/router/main.js index a85ebf5..15626a8 100644 --- a/src/js/router/main.js +++ b/src/js/router/main.js @@ -1,172 +1,180 @@ -define(['yua'], function(){ +/** + * + * @authors yutent (yutent@doui.cc) + * @date 2017-04-14 21:04:50 + * + */ - function Router(){ - this.table = {get: []}; - this.errorFn = null; - this.history = null; - this.hash = ''; - this.started = false; - this.init = {}; - } +'use strict' +//储存版本信息 +Anot.ui.router = '0.0.1' - var defaultOptions = { - prefix: /^(#!|#)[\/]?/, //hash前缀正则 - historyOpen: true, //是否开启hash历史 - allowReload: true //连续点击同一个链接是否重新加载 - }; - var isMouseUp = true; +function Router() { + this.table = { get: [] } + this.errorFn = null + this.history = null + this.hash = '' + this.started = false + this.init = {} +} - var ruleRegExp = /(:id)|(\{id\})|(\{id:([A-z\d\,\[\]\{\}\-\+\*\?\!:\^\$]*)\})/g; +var defaultOptions = { + prefix: /^(#!|#)[\/]?/, //hash前缀正则 + historyOpen: true, //是否开启hash历史 + allowReload: true //连续点击同一个链接是否重新加载 +} +var isMouseUp = true - Router.prototype = { - error: function(callback){ - this.errorFn = callback; - }, - config: function(opts){ - if(this.started) - return console.error('Router config has been set'); +var ruleRegExp = /(:id)|(\{id\})|(\{id:([A-z\d\,\[\]\{\}\-\+\*\?\!:\^\$]*)\})/g - this.started = true; - if(!opts.allowReload) - opts.historyOpen = true; - this.init = yua.mix({}, defaultOptions, opts); - }, - _getRegExp: function(rule, opts){ - var re = rule.replace(ruleRegExp, function(m, p1, p2, p3, p4){ - var w = '([\\w.-]'; - if(p1 || p2){ - return w + '+)'; - }else{ - if(!/^\{[\d\,]+\}$/.test(p4)){ - w = '('; - } - return w + p4 + ')'; - } - }) - re = re.replace(/(([^\\])([\/]+))/g, '$2\\/').replace(/(([^\\])([\.]+))/g, '$2\\.').replace(/(([^\\])([\-]+))/g, '$2\\-').replace(/(\(.*)(\\[\-]+)(.*\))/g, '$1-$3'); - re = '^' + re + '$'; - opts.regexp = new RegExp(re) - return opts; - }, - _add: function(method, rule, callback){ - if(!this.started) - this.config({}); +Router.prototype = { + error: function(callback) { + this.errorFn = callback + }, + config: function(opts) { + if (this.started) return console.error('Router config has been set') - var table = this.table[method.toLowerCase()]; - if (rule.charAt(0) !== "/") { - console.error('char "/" must be in front of router rule'); - return; - } - rule = rule.replace(/^[\/]+|[\/]+$|\s+/g, ''); - var opts = {}; - opts.rule = rule; - opts.callback = callback; - yua.Array.ensure(table, this._getRegExp(rule, opts)); - }, - _route: function(method, hash){ - var hash = hash.trim(); - var table = this.table[method]; - var init = this.init; - - if(!init.allowReload && hash === this.history) - return; - - if(init.historyOpen){ - this.history = hash; - if(yua.ls) - yua.ls('lastHash', hash); - } - - for(var i = 0, obj; obj = table[i++];){ - var args = hash.match(obj.regexp); - if(args){ - args.shift(); - return obj.callback.apply(obj, args) - } - } - this.errorFn && this.errorFn(hash); - }, - on: function(rule, callback){ - var _this = this - if(Array.isArray(rule)){ - rule.forEach(function(it){ - _this._add('get', it, callback); - }) - }else{ - this._add('get', rule, callback); - } - } - } - - yua.bind(window, 'load', function(){ - if(!yua.router.started) - return; - var prefix = yua.router.init.prefix; - var hash = location.hash; - hash = hash.replace(prefix, "").trim(); - yua.router._route('get', hash); - }); - - - if('onhashchange' in window){ - window.addEventListener('hashchange', function(event){ - if(!isMouseUp) - return; - var prefix = yua.router.init.prefix; - var hash = location.hash.replace(prefix, "").trim(); - yua.router._route('get', hash) - }) - } - - //劫持页面上所有点击事件,如果事件源来自链接或其内部, - //并且它不会跳出本页,并且以"#/"或"#!/"开头,那么触发updateLocation方法 - yua.bind(document, "mousedown", function(event){ - var defaultPrevented = "defaultPrevented" in event ? event['defaultPrevented'] : event.returnValue === false - if (defaultPrevented || event.ctrlKey || event.metaKey || event.which === 2) - return - var target = event.target - while (target.nodeName !== "A") { - target = target.parentNode - if (!target || target.tagName === "BODY") { - return - } - } - - if (targetIsThisWindow(target.target)){ - if(!yua.router.started) - return; - var href = target.getAttribute("href") || target.getAttribute("xlink:href"), - prefix = yua.router.init.prefix; - - if (href === null || !prefix.test(href)) - return - - yua.router.hash = href.replace(prefix, "").trim(); - event.preventDefault(); - location.hash = href; - isMouseUp = false; + this.started = true + if (!opts.allowReload) opts.historyOpen = true + this.init = Anot.mix({}, defaultOptions, opts) + }, + _getRegExp: function(rule, opts) { + var re = rule.replace(ruleRegExp, function(m, p1, p2, p3, p4) { + var w = '([\\w.-]' + if (p1 || p2) { + return w + '+)' + } else { + if (!/^\{[\d\,]+\}$/.test(p4)) { + w = '(' } + return w + p4 + ')' + } }) + re = re + .replace(/(([^\\])([\/]+))/g, '$2\\/') + .replace(/(([^\\])([\.]+))/g, '$2\\.') + .replace(/(([^\\])([\-]+))/g, '$2\\-') + .replace(/(\(.*)(\\[\-]+)(.*\))/g, '$1-$3') + re = '^' + re + '$' + opts.regexp = new RegExp(re) + return opts + }, + _add: function(method, rule, callback) { + if (!this.started) this.config({}) - yua.bind(document, "mouseup", function(){ - if(!isMouseUp){ - yua.router._route('get', yua.router.hash); - isMouseUp = true; - } - - }) - + var table = this.table[method.toLowerCase()] + if (rule.charAt(0) !== '/') { + console.error('char "/" must be in front of router rule') + return + } + rule = rule.replace(/^[\/]+|[\/]+$|\s+/g, '') + var opts = {} + opts.rule = rule + opts.callback = callback + Anot.Array.ensure(table, this._getRegExp(rule, opts)) + }, + _route: function(method, hash) { + var hash = hash.trim() + var table = this.table[method] + var init = this.init - //判定A标签的target属性是否指向自身 - //thanks https://github.com/quirkey/sammy/blob/master/lib/sammy.js#L219 - function targetIsThisWindow(targetWindow) { - if (!targetWindow || targetWindow === window.name || targetWindow === '_self' || (targetWindow === 'top' && window == window.top)) { - return true - } - return false + if (!init.allowReload && hash === this.history) return + + if (init.historyOpen) { + this.history = hash + if (Anot.ls) { + Anot.ls('lastHash', hash) + } } - yua.ui.router = '0.0.1' + for (var i = 0, obj; (obj = table[i++]); ) { + var args = hash.match(obj.regexp) + if (args) { + args.shift() + return obj.callback.apply(obj, args) + } + } + this.errorFn && this.errorFn(hash) + }, + on: function(rule, callback) { + var _this = this + if (Array.isArray(rule)) { + rule.forEach(function(it) { + _this._add('get', it, callback) + }) + } else { + this._add('get', rule, callback) + } + } +} - return yua.router = new Router; -}) \ No newline at end of file +Anot.bind(window, 'load', function() { + if (!Anot.router.started) return + var prefix = Anot.router.init.prefix + var hash = location.hash + hash = hash.replace(prefix, '').trim() + Anot.router._route('get', hash) +}) + +if ('onhashchange' in window) { + window.addEventListener('hashchange', function(event) { + if (!isMouseUp) return + var prefix = Anot.router.init.prefix + var hash = location.hash.replace(prefix, '').trim() + Anot.router._route('get', hash) + }) +} + +//劫持页面上所有点击事件,如果事件源来自链接或其内部, +//并且它不会跳出本页,并且以"#/"或"#!/"开头,那么触发updateLocation方法 +Anot.bind(document, 'mousedown', function(event) { + var defaultPrevented = + 'defaultPrevented' in event + ? event['defaultPrevented'] + : event.returnValue === false + if (defaultPrevented || event.ctrlKey || event.metaKey || event.which === 2) + return + var target = event.target + while (target.nodeName !== 'A') { + target = target.parentNode + if (!target || target.tagName === 'BODY') { + return + } + } + + if (targetIsThisWindow(target.target)) { + if (!Anot.router.started) return + var href = target.getAttribute('href') || target.getAttribute('xlink:href'), + prefix = Anot.router.init.prefix + + if (href === null || !prefix.test(href)) return + + Anot.router.hash = href.replace(prefix, '').trim() + event.preventDefault() + location.hash = href + isMouseUp = false + } +}) + +Anot.bind(document, 'mouseup', function() { + if (!isMouseUp) { + Anot.router._route('get', Anot.router.hash) + isMouseUp = true + } +}) + +//判定A标签的target属性是否指向自身 +//thanks https://github.com/quirkey/sammy/blob/master/lib/sammy.js#L219 +function targetIsThisWindow(targetWindow) { + if ( + !targetWindow || + targetWindow === window.name || + targetWindow === '_self' || + (targetWindow === 'top' && window == window.top) + ) { + return true + } + return false +} + +export default (Anot.router = new Router()) diff --git a/src/js/touch.patch.js b/src/js/touch.patch.js index 443fd92..a444471 100644 --- a/src/js/touch.patch.js +++ b/src/js/touch.patch.js @@ -1,594 +1,583 @@ var ua = navigator.userAgent.toLowerCase() //http://stackoverflow.com/questions/9038625/detect-if-device-is-ios function iOSversion() { - //https://developer.apple.com/library/prerelease/mac/releasenotes/General/WhatsNewInSafari/Articles/Safari_9.html - //http://mp.weixin.qq.com/s?__biz=MzA3MDQ4MzQzMg==&mid=256900619&idx=1&sn=b29f84cff0b8d7b9742e5d8b3cd8f218&scene=1&srcid=1009F9l4gh9nZ7rcQJEhmf7Q#rd - if (/iPad|iPhone|iPod/i.test(ua) && !window.MSStream) { - if ('backdropFilter' in document.documentElement.style) { - return 9 - } - if (!!window.indexedDB) { - return 8 - } - if (!!window.SpeechSynthesisUtterance) { - return 7 - } - if (!!window.webkitAudioContext) { - return 6 - } - if (!!window.matchMedia) { - return 5 - } - if (!!window.history && 'pushState' in window.history) { - return 4 - } - return 3 + //https://developer.apple.com/library/prerelease/mac/releasenotes/General/WhatsNewInSafari/Articles/Safari_9.html + //http://mp.weixin.qq.com/s?__biz=MzA3MDQ4MzQzMg==&mid=256900619&idx=1&sn=b29f84cff0b8d7b9742e5d8b3cd8f218&scene=1&srcid=1009F9l4gh9nZ7rcQJEhmf7Q#rd + if (/ipad|iphone|ipod/.test(ua) && !window.MSStream) { + if ('backdropFilter' in document.documentElement.style) { + return 9 } - return NaN + if (!!window.indexedDB) { + return 8 + } + if (!!window.SpeechSynthesisUtterance) { + return 7 + } + if (!!window.webkitAudioContext) { + return 6 + } + if (!!window.matchMedia) { + return 5 + } + if (!!window.history && 'pushState' in window.history) { + return 4 + } + return 3 + } + return NaN } var deviceIsAndroid = ua.indexOf('android') > 0 var deviceIsIOS = iOSversion() -var Recognizer = (yua.gestureHooks = { - pointers: {}, - //以AOP切入touchstart, touchmove, touchend, touchcancel回调 - start: function(event, callback) { - //touches是当前屏幕上所有触摸点的列表; - //targetTouches是当前对象上所有触摸点的列表; - //changedTouches是涉及当前事件的触摸点的列表。 - for (var i = 0; i < event.changedTouches.length; i++) { - var touch = event.changedTouches[i], - id = touch.identifier, - pointer = { - startTouch: mixLocations({}, touch), - startTime: Date.now(), - status: 'tapping', - element: event.target, - pressingHandler: - Recognizer.pointers[id] && - Recognizer.pointers[id].pressingHandler - } - Recognizer.pointers[id] = pointer - callback(pointer, touch) +var Recognizer = (Anot.gestureHooks = { + pointers: {}, + //以AOP切入touchstart, touchmove, touchend, touchcancel回调 + start: function(event, callback) { + //touches是当前屏幕上所有触摸点的列表; + //targetTouches是当前对象上所有触摸点的列表; + //changedTouches是涉及当前事件的触摸点的列表。 + for (var i = 0; i < event.changedTouches.length; i++) { + var touch = event.changedTouches[i], + id = touch.identifier, + pointer = { + startTouch: mixLocations({}, touch), + startTime: Date.now(), + status: 'tapping', + element: event.target, + pressingHandler: + Recognizer.pointers[id] && Recognizer.pointers[id].pressingHandler } - }, - move: function(event, callback) { - for (var i = 0; i < event.changedTouches.length; i++) { - var touch = event.changedTouches[i] - var pointer = Recognizer.pointers[touch.identifier] - if (!pointer) { - return - } - - if (!('lastTouch' in pointer)) { - pointer.lastTouch = pointer.startTouch - pointer.lastTime = pointer.startTime - pointer.deltaX = pointer.deltaY = pointer.duration = pointer.distance = 0 - } - - var time = Date.now() - pointer.lastTime - - if (time > 0) { - var RECORD_DURATION = 70 - if (time > RECORD_DURATION) { - time = RECORD_DURATION - } - if (pointer.duration + time > RECORD_DURATION) { - pointer.duration = RECORD_DURATION - time - } - - pointer.duration += time - pointer.lastTouch = mixLocations({}, touch) - - pointer.lastTime = Date.now() - - pointer.deltaX = touch.clientX - pointer.startTouch.clientX - pointer.deltaY = touch.clientY - pointer.startTouch.clientY - var x = pointer.deltaX * pointer.deltaX - var y = pointer.deltaY * pointer.deltaY - pointer.distance = Math.sqrt(x + y) - pointer.isVertical = x < y - - callback(pointer, touch) - } - } - }, - end: function(event, callback) { - for (var i = 0; i < event.changedTouches.length; i++) { - var touch = event.changedTouches[i], - id = touch.identifier, - pointer = Recognizer.pointers[id] - - if (!pointer) continue - - callback(pointer, touch) - - delete Recognizer.pointers[id] - } - }, - //人工触发合成事件 - fire: function(elem, type, props) { - if (elem) { - var event = document.createEvent('Events') - event.initEvent(type, true, true) - yua.mix(event, props) - elem.dispatchEvent(event) - } - }, - //添加各种识别器 - add: function(name, recognizer) { - function move(event) { - recognizer.touchmove(event) - } - - function end(event) { - recognizer.touchend(event) - - document.removeEventListener('touchmove', move) - - document.removeEventListener('touchend', end) - - document.removeEventListener('touchcancel', cancel) - } - - function cancel(event) { - recognizer.touchcancel(event) - - document.removeEventListener('touchmove', move) - - document.removeEventListener('touchend', end) - - document.removeEventListener('touchcancel', cancel) - } - - recognizer.events.forEach(function(eventName) { - yua.eventHooks[eventName] = { - fix: function(el, fn) { - if (!el['touch-' + name]) { - el['touch-' + name] = '1' - el.addEventListener('touchstart', function(event) { - recognizer.touchstart(event) - - document.addEventListener('touchmove', move) - - document.addEventListener('touchend', end) - - document.addEventListener('touchcancel', cancel) - }) - } - return fn - } - } - }) + Recognizer.pointers[id] = pointer + callback(pointer, touch) } + }, + move: function(event, callback) { + for (var i = 0; i < event.changedTouches.length; i++) { + var touch = event.changedTouches[i] + var pointer = Recognizer.pointers[touch.identifier] + if (!pointer) { + return + } + + if (!('lastTouch' in pointer)) { + pointer.lastTouch = pointer.startTouch + pointer.lastTime = pointer.startTime + pointer.deltaX = pointer.deltaY = pointer.duration = pointer.distance = 0 + } + + var time = Date.now() - pointer.lastTime + + if (time > 0) { + var RECORD_DURATION = 70 + if (time > RECORD_DURATION) { + time = RECORD_DURATION + } + if (pointer.duration + time > RECORD_DURATION) { + pointer.duration = RECORD_DURATION - time + } + + pointer.duration += time + pointer.lastTouch = mixLocations({}, touch) + + pointer.lastTime = Date.now() + + pointer.deltaX = touch.clientX - pointer.startTouch.clientX + pointer.deltaY = touch.clientY - pointer.startTouch.clientY + var x = pointer.deltaX * pointer.deltaX + var y = pointer.deltaY * pointer.deltaY + pointer.distance = Math.sqrt(x + y) + pointer.isVertical = x < y + + callback(pointer, touch) + } + } + }, + end: function(event, callback) { + for (var i = 0; i < event.changedTouches.length; i++) { + var touch = event.changedTouches[i], + id = touch.identifier, + pointer = Recognizer.pointers[id] + + if (!pointer) continue + + callback(pointer, touch) + + delete Recognizer.pointers[id] + } + }, + //人工触发合成事件 + fire: function(elem, type, props) { + if (elem) { + var event = document.createEvent('Events') + event.initEvent(type, true, true) + Anot.mix(event, props) + elem.dispatchEvent(event) + } + }, + //添加各种识别器 + add: function(name, recognizer) { + function move(event) { + recognizer.touchmove(event) + } + + function end(event) { + recognizer.touchend(event) + + document.removeEventListener('touchmove', move) + + document.removeEventListener('touchend', end) + + document.removeEventListener('touchcancel', cancel) + } + + function cancel(event) { + recognizer.touchcancel(event) + + document.removeEventListener('touchmove', move) + + document.removeEventListener('touchend', end) + + document.removeEventListener('touchcancel', cancel) + } + + recognizer.events.forEach(function(eventName) { + Anot.eventHooks[eventName] = { + fix: function(el, fn) { + if (!el['touch-' + name]) { + el['touch-' + name] = '1' + el.addEventListener('touchstart', function(event) { + recognizer.touchstart(event) + + document.addEventListener('touchmove', move) + + document.addEventListener('touchend', end) + + document.addEventListener('touchcancel', cancel) + }) + } + return fn + } + } + }) + } }) var locations = ['screenX', 'screenY', 'clientX', 'clientY', 'pageX', 'pageY'] // 复制 touch 对象上的有用属性到固定对象上 function mixLocations(target, source) { - if (source) { - locations.forEach(function(key) { - target[key] = source[key] - }) - } - return target + if (source) { + locations.forEach(function(key) { + target[key] = source[key] + }) + } + return target } var supportPointer = !!navigator.pointerEnabled || !!navigator.msPointerEnabled if (supportPointer) { - // 支持pointer的设备可用样式来取消click事件的300毫秒延迟 - root.style.msTouchAction = root.style.touchAction = 'none' + // 支持pointer的设备可用样式来取消click事件的300毫秒延迟 + root.style.msTouchAction = root.style.touchAction = 'none' } var tapRecognizer = { - events: ['tap'], - touchBoundary: 10, - tapDelay: 200, - needClick: function(target) { - //判定是否使用原生的点击事件, 否则使用sendClick方法手动触发一个人工的点击事件 - switch (target.nodeName.toLowerCase()) { - case 'button': - case 'select': - case 'textarea': - if (target.disabled) { - return true - } - - break - case 'input': - // IOS6 pad 上选择文件,如果不是原生的click,弹出的选择界面尺寸错误 - if ( - (deviceIsIOS && target.type === 'file') || - target.disabled - ) { - return true - } - - break - case 'label': - case 'iframe': - case 'video': - return true + events: ['tap'], + touchBoundary: 10, + tapDelay: 200, + needClick: function(target) { + //判定是否使用原生的点击事件, 否则使用sendClick方法手动触发一个人工的点击事件 + switch (target.nodeName.toLowerCase()) { + case 'button': + case 'select': + case 'textarea': + if (target.disabled) { + return true } - return false - }, - needFocus: function(target) { - switch (target.nodeName.toLowerCase()) { - case 'textarea': - case 'select': //实测android下select也需要 - return true - case 'input': - switch (target.type) { - case 'button': - case 'checkbox': - case 'file': - case 'image': - case 'radio': - case 'submit': - return false - } - //如果是只读或disabled状态,就无须获得焦点了 - return !target.disabled && !target.readOnly - default: - return false - } - }, - focus: function(targetElement) { - var length - //在iOS7下, 对一些新表单元素(如date, datetime, time, month)调用focus方法会抛错, - //幸好的是,我们可以改用setSelectionRange获取焦点, 将光标挪到文字的最后 - var type = targetElement.type - if ( - deviceIsIOS && - targetElement.setSelectionRange && - type.indexOf('date') !== 0 && - type !== 'time' && - type !== 'month' - ) { - length = targetElement.value.length - targetElement.setSelectionRange(length, length) - } else { - targetElement.focus() - } - }, - findControl: function(labelElement) { - // 获取label元素所对应的表单元素 - // 可以能过control属性, getElementById, 或用querySelector直接找其内部第一表单元素实现 - if (labelElement.control !== undefined) { - return labelElement.control + break + case 'input': + // IOS6 pad 上选择文件,如果不是原生的click,弹出的选择界面尺寸错误 + if ((deviceIsIOS && target.type === 'file') || target.disabled) { + return true } - if (labelElement.htmlFor) { - return document.getElementById(labelElement.htmlFor) - } - - return labelElement.querySelector( - 'button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea' - ) - }, - fixTarget: function(target) { - if (target.nodeType === 3) { - return target.parentNode - } - if (window.SVGElementInstance && target instanceof SVGElementInstance) { - return target.correspondingUseElement - } - - return target - }, - updateScrollParent: function(targetElement) { - //如果事件源元素位于某一个有滚动条的祖父元素中,那么保持其scrollParent与scrollTop值 - var scrollParent = targetElement.tapScrollParent - - if (!scrollParent || !scrollParent.contains(targetElement)) { - var parentElement = targetElement - do { - if (parentElement.scrollHeight > parentElement.offsetHeight) { - scrollParent = parentElement - targetElement.tapScrollParent = parentElement - break - } - - parentElement = parentElement.parentElement - } while (parentElement) - } - - if (scrollParent) { - scrollParent.lastScrollTop = scrollParent.scrollTop - } - }, - touchHasMoved: function(event) { - //判定是否发生移动,其阀值是10px - var touch = event.changedTouches[0], - boundary = tapRecognizer.touchBoundary - return ( - Math.abs(touch.pageX - tapRecognizer.pageX) > boundary || - Math.abs(touch.pageY - tapRecognizer.pageY) > boundary - ) - }, - - findType: function(targetElement) { - // 安卓chrome浏览器上,模拟的 click 事件不能让 select 打开,故使用 mousedown 事件 - return deviceIsAndroid && - targetElement.tagName.toLowerCase() === 'select' - ? 'mousedown' - : 'click' - }, - sendClick: function(targetElement, event) { - // 在click之前触发tap事件 - Recognizer.fire(targetElement, 'tap', { - touchEvent: event - }) - var clickEvent, touch - //某些安卓设备必须先移除焦点,之后模拟的click事件才能让新元素获取焦点 - if ( - document.activeElement && - document.activeElement !== targetElement - ) { - document.activeElement.blur() - } - - touch = event.changedTouches[0] - // 手动触发点击事件,此时必须使用document.createEvent('MouseEvents')来创建事件 - // 及使用initMouseEvent来初始化它 - clickEvent = document.createEvent('MouseEvents') - clickEvent.initMouseEvent( - tapRecognizer.findType(targetElement), - true, - true, - window, - 1, - touch.screenX, - touch.screenY, - touch.clientX, - touch.clientY, - false, - false, - false, - false, - 0, - null - ) - clickEvent.touchEvent = event - targetElement.dispatchEvent(clickEvent) - }, - touchstart: function(event) { - //忽略多点触摸 - if (event.targetTouches.length !== 1) { - return true - } - //修正事件源对象 - var targetElement = tapRecognizer.fixTarget(event.target) - var touch = event.targetTouches[0] - if (deviceIsIOS) { - // 判断是否是点击文字,进行选择等操作,如果是,不需要模拟click - var selection = window.getSelection() - if (selection.rangeCount && !selection.isCollapsed) { - return true - } - var id = touch.identifier - //当 alert 或 confirm 时,点击其他地方,会触发touch事件,identifier相同,此事件应该被忽略 - if ( - id && - isFinite(tapRecognizer.lastTouchIdentifier) && - tapRecognizer.lastTouchIdentifier === id - ) { - event.preventDefault() - return false - } - - tapRecognizer.lastTouchIdentifier = id - - tapRecognizer.updateScrollParent(targetElement) - } - //收集触摸点的信息 - tapRecognizer.status = 'tapping' - tapRecognizer.startTime = Date.now() - tapRecognizer.element = targetElement - tapRecognizer.pageX = touch.pageX - tapRecognizer.pageY = touch.pageY - // 如果点击太快,阻止双击带来的放大收缩行为 - if ( - tapRecognizer.startTime - tapRecognizer.lastTime < - tapRecognizer.tapDelay - ) { - event.preventDefault() - } - }, - touchmove: function(event) { - if (tapRecognizer.status !== 'tapping') { - return true - } - // 如果事件源元素发生改变,或者发生了移动,那么就取消触发点击事件 - if ( - tapRecognizer.element !== tapRecognizer.fixTarget(event.target) || - tapRecognizer.touchHasMoved(event) - ) { - tapRecognizer.status = tapRecognizer.element = 0 - } - }, - touchend: function(event) { - var targetElement = tapRecognizer.element - var now = Date.now() - //如果是touchstart与touchend相隔太久,可以认为是长按,那么就直接返回 - //或者是在touchstart, touchmove阶段,判定其不该触发点击事件,也直接返回 - if ( - !targetElement || - now - tapRecognizer.startTime > tapRecognizer.tapDelay - ) { - return true - } - - tapRecognizer.lastTime = now - - var startTime = tapRecognizer.startTime - tapRecognizer.status = tapRecognizer.startTime = 0 - - targetTagName = targetElement.tagName.toLowerCase() - if (targetTagName === 'label') { - //尝试触发label上可能绑定的tap事件 - Recognizer.fire(targetElement, 'tap', { - touchEvent: event - }) - var forElement = tapRecognizer.findControl(targetElement) - if (forElement) { - tapRecognizer.focus(targetElement) - targetElement = forElement - } - } else if (tapRecognizer.needFocus(targetElement)) { - // 如果元素从touchstart到touchend经历时间过长,那么不应该触发点击事 - // 或者此元素是iframe中的input元素,那么它也无法获点焦点 - if ( - now - startTime > 100 || - (deviceIsIOS && - window.top !== window && - targetTagName === 'input') - ) { - tapRecognizer.element = 0 - return false - } - - tapRecognizer.focus(targetElement) - deviceIsAndroid && tapRecognizer.sendClick(targetElement, event) + break + case 'label': + case 'iframe': + case 'video': + return true + } + return false + }, + needFocus: function(target) { + switch (target.nodeName.toLowerCase()) { + case 'textarea': + case 'select': //实测android下select也需要 + return true + case 'input': + switch (target.type) { + case 'button': + case 'checkbox': + case 'file': + case 'image': + case 'radio': + case 'submit': return false } - - if (deviceIsIOS) { - //如果它的父容器的滚动条发生改变,那么应该识别为划动或拖动事件,不应该触发点击事件 - var scrollParent = targetElement.tapScrollParent - if ( - scrollParent && - scrollParent.lastScrollTop !== scrollParent.scrollTop - ) { - return true - } - } - //如果这不是一个需要使用原生click的元素,则屏蔽原生事件,避免触发两次click - if (!tapRecognizer.needClick(targetElement)) { - event.preventDefault() - // 触发一次模拟的click - tapRecognizer.sendClick(targetElement, event) - } - }, - touchcancel: function() { - tapRecognizer.startTime = tapRecognizer.element = 0 + //如果是只读或disabled状态,就无须获得焦点了 + return !target.disabled && !target.readOnly + default: + return false } + }, + focus: function(targetElement) { + var length + //在iOS7下, 对一些新表单元素(如date, datetime, time, month)调用focus方法会抛错, + //幸好的是,我们可以改用setSelectionRange获取焦点, 将光标挪到文字的最后 + var type = targetElement.type + if ( + deviceIsIOS && + targetElement.setSelectionRange && + type.indexOf('date') !== 0 && + type !== 'time' && + type !== 'month' + ) { + length = targetElement.value.length + targetElement.setSelectionRange(length, length) + } else { + targetElement.focus() + } + }, + findControl: function(labelElement) { + // 获取label元素所对应的表单元素 + // 可以能过control属性, getElementById, 或用querySelector直接找其内部第一表单元素实现 + if (labelElement.control !== undefined) { + return labelElement.control + } + + if (labelElement.htmlFor) { + return document.getElementById(labelElement.htmlFor) + } + + return labelElement.querySelector( + 'button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea' + ) + }, + fixTarget: function(target) { + if (target.nodeType === 3) { + return target.parentNode + } + if (window.SVGElementInstance && target instanceof SVGElementInstance) { + return target.correspondingUseElement + } + + return target + }, + updateScrollParent: function(targetElement) { + //如果事件源元素位于某一个有滚动条的祖父元素中,那么保持其scrollParent与scrollTop值 + var scrollParent = targetElement.tapScrollParent + + if (!scrollParent || !scrollParent.contains(targetElement)) { + var parentElement = targetElement + do { + if (parentElement.scrollHeight > parentElement.offsetHeight) { + scrollParent = parentElement + targetElement.tapScrollParent = parentElement + break + } + + parentElement = parentElement.parentElement + } while (parentElement) + } + + if (scrollParent) { + scrollParent.lastScrollTop = scrollParent.scrollTop + } + }, + touchHasMoved: function(event) { + //判定是否发生移动,其阀值是10px + var touch = event.changedTouches[0], + boundary = tapRecognizer.touchBoundary + return ( + Math.abs(touch.pageX - tapRecognizer.pageX) > boundary || + Math.abs(touch.pageY - tapRecognizer.pageY) > boundary + ) + }, + + findType: function(targetElement) { + // 安卓chrome浏览器上,模拟的 click 事件不能让 select 打开,故使用 mousedown 事件 + return deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select' + ? 'mousedown' + : 'click' + }, + sendClick: function(targetElement, event) { + // 在click之前触发tap事件 + Recognizer.fire(targetElement, 'tap', { + touchEvent: event + }) + var clickEvent, touch + //某些安卓设备必须先移除焦点,之后模拟的click事件才能让新元素获取焦点 + if (document.activeElement && document.activeElement !== targetElement) { + document.activeElement.blur() + } + + touch = event.changedTouches[0] + // 手动触发点击事件,此时必须使用document.createEvent('MouseEvents')来创建事件 + // 及使用initMouseEvent来初始化它 + clickEvent = document.createEvent('MouseEvents') + clickEvent.initMouseEvent( + tapRecognizer.findType(targetElement), + true, + true, + window, + 1, + touch.screenX, + touch.screenY, + touch.clientX, + touch.clientY, + false, + false, + false, + false, + 0, + null + ) + clickEvent.touchEvent = event + targetElement.dispatchEvent(clickEvent) + }, + touchstart: function(event) { + //忽略多点触摸 + if (event.targetTouches.length !== 1) { + return true + } + //修正事件源对象 + var targetElement = tapRecognizer.fixTarget(event.target) + var touch = event.targetTouches[0] + if (deviceIsIOS) { + // 判断是否是点击文字,进行选择等操作,如果是,不需要模拟click + var selection = window.getSelection() + if (selection.rangeCount && !selection.isCollapsed) { + return true + } + var id = touch.identifier + //当 alert 或 confirm 时,点击其他地方,会触发touch事件,identifier相同,此事件应该被忽略 + if ( + id && + isFinite(tapRecognizer.lastTouchIdentifier) && + tapRecognizer.lastTouchIdentifier === id + ) { + event.preventDefault() + return false + } + + tapRecognizer.lastTouchIdentifier = id + + tapRecognizer.updateScrollParent(targetElement) + } + //收集触摸点的信息 + tapRecognizer.status = 'tapping' + tapRecognizer.startTime = Date.now() + tapRecognizer.element = targetElement + tapRecognizer.pageX = touch.pageX + tapRecognizer.pageY = touch.pageY + // 如果点击太快,阻止双击带来的放大收缩行为 + if ( + tapRecognizer.startTime - tapRecognizer.lastTime < + tapRecognizer.tapDelay + ) { + event.preventDefault() + } + }, + touchmove: function(event) { + if (tapRecognizer.status !== 'tapping') { + return true + } + // 如果事件源元素发生改变,或者发生了移动,那么就取消触发点击事件 + if ( + tapRecognizer.element !== tapRecognizer.fixTarget(event.target) || + tapRecognizer.touchHasMoved(event) + ) { + tapRecognizer.status = tapRecognizer.element = 0 + } + }, + touchend: function(event) { + var targetElement = tapRecognizer.element + var now = Date.now() + //如果是touchstart与touchend相隔太久,可以认为是长按,那么就直接返回 + //或者是在touchstart, touchmove阶段,判定其不该触发点击事件,也直接返回 + if ( + !targetElement || + now - tapRecognizer.startTime > tapRecognizer.tapDelay + ) { + return true + } + + tapRecognizer.lastTime = now + + var startTime = tapRecognizer.startTime + tapRecognizer.status = tapRecognizer.startTime = 0 + + targetTagName = targetElement.tagName.toLowerCase() + if (targetTagName === 'label') { + //尝试触发label上可能绑定的tap事件 + Recognizer.fire(targetElement, 'tap', { + touchEvent: event + }) + var forElement = tapRecognizer.findControl(targetElement) + if (forElement) { + tapRecognizer.focus(targetElement) + targetElement = forElement + } + } else if (tapRecognizer.needFocus(targetElement)) { + // 如果元素从touchstart到touchend经历时间过长,那么不应该触发点击事 + // 或者此元素是iframe中的input元素,那么它也无法获点焦点 + if ( + now - startTime > 100 || + (deviceIsIOS && window.top !== window && targetTagName === 'input') + ) { + tapRecognizer.element = 0 + return false + } + + tapRecognizer.focus(targetElement) + deviceIsAndroid && tapRecognizer.sendClick(targetElement, event) + + return false + } + + if (deviceIsIOS) { + //如果它的父容器的滚动条发生改变,那么应该识别为划动或拖动事件,不应该触发点击事件 + var scrollParent = targetElement.tapScrollParent + if ( + scrollParent && + scrollParent.lastScrollTop !== scrollParent.scrollTop + ) { + return true + } + } + //如果这不是一个需要使用原生click的元素,则屏蔽原生事件,避免触发两次click + if (!tapRecognizer.needClick(targetElement)) { + event.preventDefault() + // 触发一次模拟的click + tapRecognizer.sendClick(targetElement, event) + } + }, + touchcancel: function() { + tapRecognizer.startTime = tapRecognizer.element = 0 + } } Recognizer.add('tap', tapRecognizer) var pressRecognizer = { - events: ['longtap', 'doubletap'], - cancelPress: function(pointer) { - clearTimeout(pointer.pressingHandler) - pointer.pressingHandler = null - }, - touchstart: function(event) { - Recognizer.start(event, function(pointer, touch) { - pointer.pressingHandler = setTimeout(function() { - if (pointer.status === 'tapping') { - Recognizer.fire(event.target, 'longtap', { - touch: touch, - touchEvent: event - }) - } - pressRecognizer.cancelPress(pointer) - }, 800) - if (event.changedTouches.length !== 1) { - pointer.status = 0 - } - }) - }, - touchmove: function(event) { - Recognizer.move(event, function(pointer) { - if (pointer.distance > 10 && pointer.pressingHandler) { - pressRecognizer.cancelPress(pointer) - if (pointer.status === 'tapping') { - pointer.status = 'panning' - } - } - }) - }, - touchend: function(event) { - Recognizer.end(event, function(pointer, touch) { - pressRecognizer.cancelPress(pointer) - if (pointer.status === 'tapping') { - pointer.lastTime = Date.now() - if ( - pressRecognizer.lastTap && - pointer.lastTime - pressRecognizer.lastTap.lastTime < 300 - ) { - Recognizer.fire(pointer.element, 'doubletap', { - touch: touch, - touchEvent: event - }) - } + events: ['longtap', 'doubletap'], + cancelPress: function(pointer) { + clearTimeout(pointer.pressingHandler) + pointer.pressingHandler = null + }, + touchstart: function(event) { + Recognizer.start(event, function(pointer, touch) { + pointer.pressingHandler = setTimeout(function() { + if (pointer.status === 'tapping') { + Recognizer.fire(event.target, 'longtap', { + touch: touch, + touchEvent: event + }) + } + pressRecognizer.cancelPress(pointer) + }, 800) + if (event.changedTouches.length !== 1) { + pointer.status = 0 + } + }) + }, + touchmove: function(event) { + Recognizer.move(event, function(pointer) { + if (pointer.distance > 10 && pointer.pressingHandler) { + pressRecognizer.cancelPress(pointer) + if (pointer.status === 'tapping') { + pointer.status = 'panning' + } + } + }) + }, + touchend: function(event) { + Recognizer.end(event, function(pointer, touch) { + pressRecognizer.cancelPress(pointer) + if (pointer.status === 'tapping') { + pointer.lastTime = Date.now() + if ( + pressRecognizer.lastTap && + pointer.lastTime - pressRecognizer.lastTap.lastTime < 300 + ) { + Recognizer.fire(pointer.element, 'doubletap', { + touch: touch, + touchEvent: event + }) + } - pressRecognizer.lastTap = pointer - } - }) - }, - touchcancel: function(event) { - Recognizer.end(event, function(pointer) { - pressRecognizer.cancelPress(pointer) - }) - } + pressRecognizer.lastTap = pointer + } + }) + }, + touchcancel: function(event) { + Recognizer.end(event, function(pointer) { + pressRecognizer.cancelPress(pointer) + }) + } } Recognizer.add('press', pressRecognizer) var swipeRecognizer = { - events: ['swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown'], - getAngle: function(x, y) { - return Math.atan2(y, x) * 180 / Math.PI - }, - getDirection: function(x, y) { - var angle = swipeRecognizer.getAngle(x, y) - if (angle < -45 && angle > -135) { - return 'up' - } else if (angle >= 45 && angle < 315) { - return 'down' - } else if (angle > -45 && angle <= 45) { - return 'right' - } else { - return 'left' - } - }, - touchstart: function(event) { - Recognizer.start(event, noop) - }, - touchmove: function(event) { - Recognizer.move(event, noop) - }, - touchend: function(event) { - if (event.changedTouches.length !== 1) { - return - } - Recognizer.end(event, function(pointer, touch) { - var isflick = - pointer.distance > 30 && - pointer.distance / pointer.duration > 0.65 - if (isflick) { - var extra = { - deltaX: pointer.deltaX, - deltaY: pointer.deltaY, - touch: touch, - touchEvent: event, - direction: swipeRecognizer.getDirection( - pointer.deltaX, - pointer.deltaY - ), - isVertical: pointer.isVertical - } - var target = pointer.element - Recognizer.fire(target, 'swipe', extra) - Recognizer.fire(target, 'swipe' + extra.direction, extra) - } - }) + events: ['swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown'], + getAngle: function(x, y) { + return Math.atan2(y, x) * 180 / Math.PI + }, + getDirection: function(x, y) { + var angle = swipeRecognizer.getAngle(x, y) + if (angle < -45 && angle > -135) { + return 'up' + } else if (angle >= 45 && angle < 315) { + return 'down' + } else if (angle > -45 && angle <= 45) { + return 'right' + } else { + return 'left' } + }, + touchstart: function(event) { + Recognizer.start(event, noop) + }, + touchmove: function(event) { + Recognizer.move(event, noop) + }, + touchend: function(event) { + if (event.changedTouches.length !== 1) { + return + } + Recognizer.end(event, function(pointer, touch) { + var isflick = + pointer.distance > 30 && pointer.distance / pointer.duration > 0.65 + if (isflick) { + var extra = { + deltaX: pointer.deltaX, + deltaY: pointer.deltaY, + touch: touch, + touchEvent: event, + direction: swipeRecognizer.getDirection( + pointer.deltaX, + pointer.deltaY + ), + isVertical: pointer.isVertical + } + var target = pointer.element + Recognizer.fire(target, 'swipe', extra) + Recognizer.fire(target, 'swipe' + extra.direction, extra) + } + }) + } } swipeRecognizer.touchcancel = swipeRecognizer.touchend