/** * @name zepto.extend * @file 对Zepto做了些扩展,以下所有JS都依赖与此文件 * @desc 对Zepto一些扩展,组件必须依赖 * @import core/zepto.js */ (function($){ $.extend($, { contains: function(parent, node) { /** * modified by chenluyang * @reason ios4 safari下,无法判断包含文字节点的情况 * @original return parent !== node && parent.contains(node) */ return parent.compareDocumentPosition ? !!(parent.compareDocumentPosition(node) & 16) : parent !== node && parent.contains(node) } }); })(Zepto); //Core.js ;(function($, undefined) { //扩展在Zepto静态类上 $.extend($, { /** * @grammar $.toString(obj) ⇒ string * @name $.toString * @desc toString转化 */ toString: function(obj) { return Object.prototype.toString.call(obj); }, /** * @desc 从集合中截取部分数据,这里说的集合,可以是数组,也可以是跟数组性质很像的对象,比如arguments * @name $.slice * @grammar $.slice(collection, [index]) ⇒ array * @example (function(){ * var args = $.slice(arguments, 2); * console.log(args); // => [3] * })(1, 2, 3); */ slice: function(array, index) { return Array.prototype.slice.call(array, index || 0); }, /** * @name $.later * @grammar $.later(fn, [when, [periodic, [context, [data]]]]) ⇒ timer * @desc 延迟执行fn * **参数:** * - ***fn***: 将要延时执行的方法 * - ***when***: *可选(默认 0)* 什么时间后执行 * - ***periodic***: *可选(默认 false)* 设定是否是周期性的执行 * - ***context***: *可选(默认 undefined)* 给方法设定上下文 * - ***data***: *可选(默认 undefined)* 给方法设定传入参数 * @example $.later(function(str){ * console.log(this.name + ' ' + str); // => Example hello * }, 250, false, {name:'Example'}, ['hello']); */ later: function(fn, when, periodic, context, data) { return window['set' + (periodic ? 'Interval' : 'Timeout')](function() { fn.apply(context, data); }, when || 0); }, /** * @desc 解析模版 * @grammar $.parseTpl(str, data) ⇒ string * @name $.parseTpl * @example var str = "

<%=name%>

", * obj = {name: 'ajean'}; * console.log($.parseTpl(str, data)); // =>

ajean

*/ parseTpl: function(str, data) { var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g, function(match, code) { return "'," + code.replace(/\\'/g, "'") + ",'"; }).replace(/<%([\s\S]+?)%>/g, function(match, code) { return "');" + code.replace(/\\'/g, "'").replace(/[\r\n\t]/g, ' ') + "__p.push('"; }).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t') + "');}return __p.join('');"; var func = new Function('obj', tmpl); return data ? func(data) : func; }, /** * @desc 减少执行频率, 多次调用,在指定的时间内,只会执行一次。 * **options:** * - ***delay***: 延时时间 * - ***fn***: 被稀释的方法 * - ***debounce_mode***: 是否开启防震动模式, true:start, false:end * * ||||||||||||||||||||||||| (空闲) ||||||||||||||||||||||||| * X X X X X X X X X X X X * * @grammar $.throttle(delay, fn) ⇒ function * @name $.throttle * @example var touchmoveHander = function(){ * //.... * } * //绑定事件 * $(document).bind('touchmove', $.throttle(250, touchmoveHander));//频繁滚动,每250ms,执行一次touchmoveHandler * * //解绑事件 * $(document).unbind('touchmove', touchmoveHander);//注意这里面unbind还是touchmoveHander,而不是$.throttle返回的function, 当然unbind那个也是一样的效果 * */ throttle: function(delay, fn, debounce_mode) { var last = 0, timeId; if (typeof fn !== 'function') { debounce_mode = fn; fn = delay; delay = 250; } function wrapper() { var that = this, period = Date.now() - last, args = arguments; function exec() { last = Date.now(); fn.apply(that, args); }; function clear() { timeId = undefined; }; if (debounce_mode && !timeId) { // debounce模式 && 第一次调用 exec(); } timeId && clearTimeout(timeId); if (debounce_mode === undefined && period > delay) { // throttle, 执行到了delay时间 exec(); } else { // debounce, 如果是start就clearTimeout timeId = setTimeout(debounce_mode ? clear : exec, debounce_mode === undefined ? delay - period : delay); } }; // for event bind | unbind wrapper._zid = fn._zid = fn._zid || $.proxy(fn)._zid; return wrapper; }, /** * @desc 减少执行频率, 在指定的时间内, 多次调用,只会执行一次。 * **options:** * - ***delay***: 延时时间 * - ***fn***: 被稀释的方法 * - ***t***: 指定是在开始处执行,还是结束是执行, true:start, false:end * * 非at_begin模式 * ||||||||||||||||||||||||| (空闲) ||||||||||||||||||||||||| * X X * at_begin模式 * ||||||||||||||||||||||||| (空闲) ||||||||||||||||||||||||| * X X * * @grammar $.debounce(delay, fn[, at_begin]) ⇒ function * @name $.debounce * @example var touchmoveHander = function(){ * //.... * } * //绑定事件 * $(document).bind('touchmove', $.debounce(250, touchmoveHander));//频繁滚动,只要间隔时间不大于250ms, 在一系列移动后,只会执行一次 * * //解绑事件 * $(document).unbind('touchmove', touchmoveHander);//注意这里面unbind还是touchmoveHander,而不是$.debounce返回的function, 当然unbind那个也是一样的效果 */ debounce: function(delay, fn, t) { return fn === undefined ? $.throttle(250, delay, false) : $.throttle(delay, fn, t === undefined ? false : t !== false); } }); /** * 扩展类型判断 * @param {Any} obj * @see isString, isBoolean, isRegExp, isNumber, isDate, isObject, isNull, isUdefined */ /** * @name $.isString * @grammar $.isString(val) ⇒ Boolean * @desc 判断变量类型是否为***String*** * @example console.log($.isString({}));// => false * console.log($.isString(123));// => false * console.log($.isString('123'));// => true */ /** * @name $.isBoolean * @grammar $.isBoolean(val) ⇒ Boolean * @desc 判断变量类型是否为***Boolean*** * @example console.log($.isBoolean(1));// => false * console.log($.isBoolean('true'));// => false * console.log($.isBoolean(false));// => true */ /** * @name $.isRegExp * @grammar $.isRegExp(val) ⇒ Boolean * @desc 判断变量类型是否为***RegExp*** * @example console.log($.isRegExp(1));// => false * console.log($.isRegExp('test'));// => false * console.log($.isRegExp(/test/));// => true */ /** * @name $.isNumber * @grammar $.isNumber(val) ⇒ Boolean * @desc 判断变量类型是否为***Number*** * @example console.log($.isNumber('123'));// => false * console.log($.isNumber(true));// => false * console.log($.isNumber(123));// => true */ /** * @name $.isDate * @grammar $.isDate(val) ⇒ Boolean * @desc 判断变量类型是否为***Date*** * @example console.log($.isDate('123'));// => false * console.log($.isDate('2012-12-12'));// => false * console.log($.isDate(new Date()));// => true */ /** * @name $.isObject * @grammar $.isObject(val) ⇒ Boolean * @desc 判断变量类型是否为***Object*** * @example console.log($.isObject('123'));// => false * console.log($.isObject(true));// => false * console.log($.isObject({}));// => true */ /** * @name $.isNull * @grammar $.isNull(val) ⇒ Boolean * @desc 判断变量类型是否为***null*** * @example console.log($.isNull(false));// => false * console.log($.isNull(0));// => false * console.log($.isNull(null));// => true */ /** * @name $.isUndefined * @grammar $.isUndefined(val) ⇒ Boolean * @desc 判断变量类型是否为***undefined*** * @example * console.log($.isUndefined(false));// => false * console.log($.isUndefined(0));// => false * console.log($.isUndefined(a));// => true */ $.each("String Boolean RegExp Number Date Object Null Undefined".split(" "), function( i, name ){ var fn; if( 'is' + name in $ ) return;//already defined then ignore. switch (name) { case 'Null': fn = function(obj){ return obj === null; }; break; case 'Undefined': fn = function(obj){ return obj === undefined; }; break; default: fn = function(obj){ return new RegExp(name + ']', 'i').test( toString(obj) )}; } $['is'+name] = fn; }); var toString = $.toString; })(Zepto); //Support.js (function($, undefined) { var ua = navigator.userAgent, na = navigator.appVersion, br = $.browser; /** * @name $.browser * @desc 扩展zepto中对browser的检测 * * **可用属性** * - ***qq*** 检测qq浏览器 * - ***chrome*** 检测chrome浏览器 * - ***uc*** 检测uc浏览器 * - ***version*** 检测浏览器版本 * * @example * if ($.browser.qq) { //在qq浏览器上打出此log * console.log('this is qq browser'); * } */ $.extend( br, { qq: /qq/i.test(ua), uc: /UC/i.test(ua) || /UC/i.test(na) } ); br.uc = br.uc || !br.qq && !br.chrome && !br.firefox && !/safari/i.test(ua); try { br.version = br.uc ? na.match(/UC(?:Browser)?\/([\d.]+)/)[1] : br.qq ? ua.match(/MQQBrowser\/([\d.]+)/)[1] : br.version; } catch (e) {} /** * @name $.support * @desc 检测设备对某些属性或方法的支持情况 * * **可用属性** * - ***orientation*** 检测是否支持转屏事件,UC中存在orientaion,但转屏不会触发该事件,故UC属于不支持转屏事件(iOS 4上qq, chrome都有这个现象) * - ***touch*** 检测是否支持touch相关事件 * - ***cssTransitions*** 检测是否支持css3的transition * - ***has3d*** 检测是否支持translate3d的硬件加速 * * @example * if ($.support.has3d) { //在支持3d的设备上使用 * console.log('you can use transtion3d'); * } */ $.support = $.extend($.support || {}, { orientation: !(br.uc || (parseFloat($.os.version)<5 && (br.qq || br.chrome))) && !($.os.android && parseFloat($.os.version) > 3) && "orientation" in window && "onorientationchange" in window, touch: "ontouchend" in document, cssTransitions: "WebKitTransitionEvent" in window, has3d: 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix() }); })(Zepto); //Event.js (function($) { /** * @name $.matchMedia * @grammar $.matchMedia(query) ⇒ MediaQueryList * @desc 是原生的window.matchMedia方法的polyfill,对于不支持matchMedia的方法系统和浏览器,按照[w3c window.matchMedia](http://www.w3.org/TR/cssom-view/#dom-window-matchmedia)的接口 * 定义,对matchMedia方法进行了封装。原理是用css media query及transitionEnd事件来完成的。在页面中插入media query样式及元素,当query条件满足时改变该元素样式,同时这个样式是transition作用的属性, * 满足条件后即会触发transitionEnd,由此创建MediaQueryList的事件监听。由于transition的duration time为0.001ms,故若直接使用MediaQueryList对象的matches去判断当前是否与query匹配,会有部分延迟, * 建议注册addListener的方式去监听query的改变。$.matchMedia的详细实现原理及采用该方法实现的转屏统一解决方案详见 * [GMU Pages: 转屏解决方案($.matchMedia)](https://github.com/gmuteam/GMU/wiki/%E8%BD%AC%E5%B1%8F%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88$.matchMedia) * * **MediaQueryList对象包含的属性** * - ***matches*** 是否满足query * - ***query*** 查询的css query,类似\'screen and (orientation: portrait)\' * - ***addListener*** 添加MediaQueryList对象监听器,接收回调函数,回调参数为MediaQueryList对象 * - ***removeListener*** 移除MediaQueryList对象监听器 * * @example * $.matchMedia('screen and (orientation: portrait)').addListener(fn); */ $.matchMedia = (function() { var mediaId = 0, cls = 'gmu-media-detect', transitionEnd = $.fx.transitionEnd, cssPrefix = $.fx.cssPrefix, $style = $('').append('.' + cls + '{' + cssPrefix + 'transition: width 0.001ms; width: 0; position: relative; bottom: -999999px;}\n').appendTo('head'); return function (query) { var id = cls + mediaId++, $mediaElem = $('
').appendTo('body'), listeners = [], ret; $style.append('@media ' + query + ' { #' + id + ' { width: 100px; } }\n') ; //原生matchMedia也需要添加对应的@media才能生效 // if ('matchMedia' in window) { // return window.matchMedia(query); // } $mediaElem.on(transitionEnd, function() { ret.matches = $mediaElem.width() === 100; $.each(listeners, function (i,fn) { $.isFunction(fn) && fn.call(ret, ret); }); }); ret = { matches: $mediaElem.width() === 100 , media: query, addListener: function (callback) { listeners.push(callback); return this; }, removeListener: function (callback) { var index = listeners.indexOf(callback); ~index && listeners.splice(index, 1); return this; } }; return ret; }; }()); $(function () { var handleOrtchange = function (mql) { if ( state !== mql.matches ) { $( window ).trigger( 'ortchange' ); state = mql.matches; } }, state = true; $.mediaQuery = { ortchange: 'screen and (width: ' + window.innerWidth + 'px)' }; $.matchMedia($.mediaQuery.ortchange).addListener(handleOrtchange); }); /** * @name Trigger Events * @theme event * @desc 扩展的事件 * - ***scrollStop*** : scroll停下来时触发, 考虑前进或者后退后scroll事件不触发情况。 * - ***ortchange*** : 当转屏的时候触发,兼容uc和其他不支持orientationchange的设备,利用css media query实现,解决了转屏延时及orientation事件的兼容性问题 * @example $(document).on('scrollStop', function () { //scroll停下来时显示scrollStop * console.log('scrollStop'); * }); * * $(window).on('ortchange', function () { //当转屏的时候触发 * console.log('ortchange'); * }); */ /** dispatch scrollStop */ function _registerScrollStop(){ $(window).on('scroll', $.debounce(80, function() { $(document).trigger('scrollStop'); }, false)); } //在离开页面,前进或后退回到页面后,重新绑定scroll, 需要off掉所有的scroll,否则scroll时间不触发 function _touchstartHander() { $(window).off('scroll'); _registerScrollStop(); } _registerScrollStop(); $(window).on('pageshow', function(e){ if(e.persisted) {//如果是从bfcache中加载页面 $(document).off('touchstart', _touchstartHander).one('touchstart', _touchstartHander); } }); })(Zepto);