/** * @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);