zepto.extend.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /**
  2. * @name zepto.extend
  3. * @file 对Zepto做了些扩展,以下所有JS都依赖与此文件
  4. * @desc 对Zepto一些扩展,组件必须依赖
  5. * @import core/zepto.js
  6. */
  7. (function($){
  8. $.extend($, {
  9. contains: function(parent, node) {
  10. /**
  11. * modified by chenluyang
  12. * @reason ios4 safari下,无法判断包含文字节点的情况
  13. * @original return parent !== node && parent.contains(node)
  14. */
  15. return parent.compareDocumentPosition
  16. ? !!(parent.compareDocumentPosition(node) & 16)
  17. : parent !== node && parent.contains(node)
  18. }
  19. });
  20. })(Zepto);
  21. //Core.js
  22. ;(function($, undefined) {
  23. //扩展在Zepto静态类上
  24. $.extend($, {
  25. /**
  26. * @grammar $.toString(obj) ⇒ string
  27. * @name $.toString
  28. * @desc toString转化
  29. */
  30. toString: function(obj) {
  31. return Object.prototype.toString.call(obj);
  32. },
  33. /**
  34. * @desc 从集合中截取部分数据,这里说的集合,可以是数组,也可以是跟数组性质很像的对象,比如arguments
  35. * @name $.slice
  36. * @grammar $.slice(collection, [index]) ⇒ array
  37. * @example (function(){
  38. * var args = $.slice(arguments, 2);
  39. * console.log(args); // => [3]
  40. * })(1, 2, 3);
  41. */
  42. slice: function(array, index) {
  43. return Array.prototype.slice.call(array, index || 0);
  44. },
  45. /**
  46. * @name $.later
  47. * @grammar $.later(fn, [when, [periodic, [context, [data]]]]) ⇒ timer
  48. * @desc 延迟执行fn
  49. * **参数:**
  50. * - ***fn***: 将要延时执行的方法
  51. * - ***when***: *可选(默认 0)* 什么时间后执行
  52. * - ***periodic***: *可选(默认 false)* 设定是否是周期性的执行
  53. * - ***context***: *可选(默认 undefined)* 给方法设定上下文
  54. * - ***data***: *可选(默认 undefined)* 给方法设定传入参数
  55. * @example $.later(function(str){
  56. * console.log(this.name + ' ' + str); // => Example hello
  57. * }, 250, false, {name:'Example'}, ['hello']);
  58. */
  59. later: function(fn, when, periodic, context, data) {
  60. return window['set' + (periodic ? 'Interval' : 'Timeout')](function() {
  61. fn.apply(context, data);
  62. }, when || 0);
  63. },
  64. /**
  65. * @desc 解析模版
  66. * @grammar $.parseTpl(str, data) ⇒ string
  67. * @name $.parseTpl
  68. * @example var str = "<p><%=name%></p>",
  69. * obj = {name: 'ajean'};
  70. * console.log($.parseTpl(str, data)); // => <p>ajean</p>
  71. */
  72. parseTpl: function(str, data) {
  73. 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) {
  74. return "'," + code.replace(/\\'/g, "'") + ",'";
  75. }).replace(/<%([\s\S]+?)%>/g, function(match, code) {
  76. return "');" + code.replace(/\\'/g, "'").replace(/[\r\n\t]/g, ' ') + "__p.push('";
  77. }).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t') + "');}return __p.join('');";
  78. var func = new Function('obj', tmpl);
  79. return data ? func(data) : func;
  80. },
  81. /**
  82. * @desc 减少执行频率, 多次调用,在指定的时间内,只会执行一次。
  83. * **options:**
  84. * - ***delay***: 延时时间
  85. * - ***fn***: 被稀释的方法
  86. * - ***debounce_mode***: 是否开启防震动模式, true:start, false:end
  87. *
  88. * <code type="text">||||||||||||||||||||||||| (空闲) |||||||||||||||||||||||||
  89. * X X X X X X X X X X X X</code>
  90. *
  91. * @grammar $.throttle(delay, fn) ⇒ function
  92. * @name $.throttle
  93. * @example var touchmoveHander = function(){
  94. * //....
  95. * }
  96. * //绑定事件
  97. * $(document).bind('touchmove', $.throttle(250, touchmoveHander));//频繁滚动,每250ms,执行一次touchmoveHandler
  98. *
  99. * //解绑事件
  100. * $(document).unbind('touchmove', touchmoveHander);//注意这里面unbind还是touchmoveHander,而不是$.throttle返回的function, 当然unbind那个也是一样的效果
  101. *
  102. */
  103. throttle: function(delay, fn, debounce_mode) {
  104. var last = 0,
  105. timeId;
  106. if (typeof fn !== 'function') {
  107. debounce_mode = fn;
  108. fn = delay;
  109. delay = 250;
  110. }
  111. function wrapper() {
  112. var that = this,
  113. period = Date.now() - last,
  114. args = arguments;
  115. function exec() {
  116. last = Date.now();
  117. fn.apply(that, args);
  118. };
  119. function clear() {
  120. timeId = undefined;
  121. };
  122. if (debounce_mode && !timeId) {
  123. // debounce模式 && 第一次调用
  124. exec();
  125. }
  126. timeId && clearTimeout(timeId);
  127. if (debounce_mode === undefined && period > delay) {
  128. // throttle, 执行到了delay时间
  129. exec();
  130. } else {
  131. // debounce, 如果是start就clearTimeout
  132. timeId = setTimeout(debounce_mode ? clear : exec, debounce_mode === undefined ? delay - period : delay);
  133. }
  134. };
  135. // for event bind | unbind
  136. wrapper._zid = fn._zid = fn._zid || $.proxy(fn)._zid;
  137. return wrapper;
  138. },
  139. /**
  140. * @desc 减少执行频率, 在指定的时间内, 多次调用,只会执行一次。
  141. * **options:**
  142. * - ***delay***: 延时时间
  143. * - ***fn***: 被稀释的方法
  144. * - ***t***: 指定是在开始处执行,还是结束是执行, true:start, false:end
  145. *
  146. * 非at_begin模式
  147. * <code type="text">||||||||||||||||||||||||| (空闲) |||||||||||||||||||||||||
  148. * X X</code>
  149. * at_begin模式
  150. * <code type="text">||||||||||||||||||||||||| (空闲) |||||||||||||||||||||||||
  151. * X X </code>
  152. *
  153. * @grammar $.debounce(delay, fn[, at_begin]) ⇒ function
  154. * @name $.debounce
  155. * @example var touchmoveHander = function(){
  156. * //....
  157. * }
  158. * //绑定事件
  159. * $(document).bind('touchmove', $.debounce(250, touchmoveHander));//频繁滚动,只要间隔时间不大于250ms, 在一系列移动后,只会执行一次
  160. *
  161. * //解绑事件
  162. * $(document).unbind('touchmove', touchmoveHander);//注意这里面unbind还是touchmoveHander,而不是$.debounce返回的function, 当然unbind那个也是一样的效果
  163. */
  164. debounce: function(delay, fn, t) {
  165. return fn === undefined ? $.throttle(250, delay, false) : $.throttle(delay, fn, t === undefined ? false : t !== false);
  166. }
  167. });
  168. /**
  169. * 扩展类型判断
  170. * @param {Any} obj
  171. * @see isString, isBoolean, isRegExp, isNumber, isDate, isObject, isNull, isUdefined
  172. */
  173. /**
  174. * @name $.isString
  175. * @grammar $.isString(val) ⇒ Boolean
  176. * @desc 判断变量类型是否为***String***
  177. * @example console.log($.isString({}));// => false
  178. * console.log($.isString(123));// => false
  179. * console.log($.isString('123'));// => true
  180. */
  181. /**
  182. * @name $.isBoolean
  183. * @grammar $.isBoolean(val) ⇒ Boolean
  184. * @desc 判断变量类型是否为***Boolean***
  185. * @example console.log($.isBoolean(1));// => false
  186. * console.log($.isBoolean('true'));// => false
  187. * console.log($.isBoolean(false));// => true
  188. */
  189. /**
  190. * @name $.isRegExp
  191. * @grammar $.isRegExp(val) ⇒ Boolean
  192. * @desc 判断变量类型是否为***RegExp***
  193. * @example console.log($.isRegExp(1));// => false
  194. * console.log($.isRegExp('test'));// => false
  195. * console.log($.isRegExp(/test/));// => true
  196. */
  197. /**
  198. * @name $.isNumber
  199. * @grammar $.isNumber(val) ⇒ Boolean
  200. * @desc 判断变量类型是否为***Number***
  201. * @example console.log($.isNumber('123'));// => false
  202. * console.log($.isNumber(true));// => false
  203. * console.log($.isNumber(123));// => true
  204. */
  205. /**
  206. * @name $.isDate
  207. * @grammar $.isDate(val) ⇒ Boolean
  208. * @desc 判断变量类型是否为***Date***
  209. * @example console.log($.isDate('123'));// => false
  210. * console.log($.isDate('2012-12-12'));// => false
  211. * console.log($.isDate(new Date()));// => true
  212. */
  213. /**
  214. * @name $.isObject
  215. * @grammar $.isObject(val) ⇒ Boolean
  216. * @desc 判断变量类型是否为***Object***
  217. * @example console.log($.isObject('123'));// => false
  218. * console.log($.isObject(true));// => false
  219. * console.log($.isObject({}));// => true
  220. */
  221. /**
  222. * @name $.isNull
  223. * @grammar $.isNull(val) ⇒ Boolean
  224. * @desc 判断变量类型是否为***null***
  225. * @example console.log($.isNull(false));// => false
  226. * console.log($.isNull(0));// => false
  227. * console.log($.isNull(null));// => true
  228. */
  229. /**
  230. * @name $.isUndefined
  231. * @grammar $.isUndefined(val) ⇒ Boolean
  232. * @desc 判断变量类型是否为***undefined***
  233. * @example
  234. * console.log($.isUndefined(false));// => false
  235. * console.log($.isUndefined(0));// => false
  236. * console.log($.isUndefined(a));// => true
  237. */
  238. $.each("String Boolean RegExp Number Date Object Null Undefined".split(" "), function( i, name ){
  239. var fn;
  240. if( 'is' + name in $ ) return;//already defined then ignore.
  241. switch (name) {
  242. case 'Null':
  243. fn = function(obj){ return obj === null; };
  244. break;
  245. case 'Undefined':
  246. fn = function(obj){ return obj === undefined; };
  247. break;
  248. default:
  249. fn = function(obj){ return new RegExp(name + ']', 'i').test( toString(obj) )};
  250. }
  251. $['is'+name] = fn;
  252. });
  253. var toString = $.toString;
  254. })(Zepto);
  255. //Support.js
  256. (function($, undefined) {
  257. var ua = navigator.userAgent,
  258. na = navigator.appVersion,
  259. br = $.browser;
  260. /**
  261. * @name $.browser
  262. * @desc 扩展zepto中对browser的检测
  263. *
  264. * **可用属性**
  265. * - ***qq*** 检测qq浏览器
  266. * - ***chrome*** 检测chrome浏览器
  267. * - ***uc*** 检测uc浏览器
  268. * - ***version*** 检测浏览器版本
  269. *
  270. * @example
  271. * if ($.browser.qq) { //在qq浏览器上打出此log
  272. * console.log('this is qq browser');
  273. * }
  274. */
  275. $.extend( br, {
  276. qq: /qq/i.test(ua),
  277. uc: /UC/i.test(ua) || /UC/i.test(na)
  278. } );
  279. br.uc = br.uc || !br.qq && !br.chrome && !br.firefox && !/safari/i.test(ua);
  280. try {
  281. br.version = br.uc ? na.match(/UC(?:Browser)?\/([\d.]+)/)[1] : br.qq ? ua.match(/MQQBrowser\/([\d.]+)/)[1] : br.version;
  282. } catch (e) {}
  283. /**
  284. * @name $.support
  285. * @desc 检测设备对某些属性或方法的支持情况
  286. *
  287. * **可用属性**
  288. * - ***orientation*** 检测是否支持转屏事件,UC中存在orientaion,但转屏不会触发该事件,故UC属于不支持转屏事件(iOS 4上qq, chrome都有这个现象)
  289. * - ***touch*** 检测是否支持touch相关事件
  290. * - ***cssTransitions*** 检测是否支持css3的transition
  291. * - ***has3d*** 检测是否支持translate3d的硬件加速
  292. *
  293. * @example
  294. * if ($.support.has3d) { //在支持3d的设备上使用
  295. * console.log('you can use transtion3d');
  296. * }
  297. */
  298. $.support = $.extend($.support || {}, {
  299. orientation: !(br.uc || (parseFloat($.os.version)<5 && (br.qq || br.chrome))) && !($.os.android && parseFloat($.os.version) > 3) && "orientation" in window && "onorientationchange" in window,
  300. touch: "ontouchend" in document,
  301. cssTransitions: "WebKitTransitionEvent" in window,
  302. has3d: 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()
  303. });
  304. })(Zepto);
  305. //Event.js
  306. (function($) {
  307. /**
  308. * @name $.matchMedia
  309. * @grammar $.matchMedia(query) ⇒ MediaQueryList
  310. * @desc 是原生的window.matchMedia方法的polyfill,对于不支持matchMedia的方法系统和浏览器,按照[w3c window.matchMedia](http://www.w3.org/TR/cssom-view/#dom-window-matchmedia)的接口
  311. * 定义,对matchMedia方法进行了封装。原理是用css media query及transitionEnd事件来完成的。在页面中插入media query样式及元素,当query条件满足时改变该元素样式,同时这个样式是transition作用的属性,
  312. * 满足条件后即会触发transitionEnd,由此创建MediaQueryList的事件监听。由于transition的duration time为0.001ms,故若直接使用MediaQueryList对象的matches去判断当前是否与query匹配,会有部分延迟,
  313. * 建议注册addListener的方式去监听query的改变。$.matchMedia的详细实现原理及采用该方法实现的转屏统一解决方案详见
  314. * [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)
  315. *
  316. * **MediaQueryList对象包含的属性**
  317. * - ***matches*** 是否满足query
  318. * - ***query*** 查询的css query,类似\'screen and (orientation: portrait)\'
  319. * - ***addListener*** 添加MediaQueryList对象监听器,接收回调函数,回调参数为MediaQueryList对象
  320. * - ***removeListener*** 移除MediaQueryList对象监听器
  321. *
  322. * @example
  323. * $.matchMedia('screen and (orientation: portrait)').addListener(fn);
  324. */
  325. $.matchMedia = (function() {
  326. var mediaId = 0,
  327. cls = 'gmu-media-detect',
  328. transitionEnd = $.fx.transitionEnd,
  329. cssPrefix = $.fx.cssPrefix,
  330. $style = $('<style></style>').append('.' + cls + '{' + cssPrefix + 'transition: width 0.001ms; width: 0; position: relative; bottom: -999999px;}\n').appendTo('head');
  331. return function (query) {
  332. var id = cls + mediaId++,
  333. $mediaElem = $('<div class="' + cls + '" id="' + id + '"></div>').appendTo('body'),
  334. listeners = [],
  335. ret;
  336. $style.append('@media ' + query + ' { #' + id + ' { width: 100px; } }\n') ; //原生matchMedia也需要添加对应的@media才能生效
  337. // if ('matchMedia' in window) {
  338. // return window.matchMedia(query);
  339. // }
  340. $mediaElem.on(transitionEnd, function() {
  341. ret.matches = $mediaElem.width() === 100;
  342. $.each(listeners, function (i,fn) {
  343. $.isFunction(fn) && fn.call(ret, ret);
  344. });
  345. });
  346. ret = {
  347. matches: $mediaElem.width() === 100 ,
  348. media: query,
  349. addListener: function (callback) {
  350. listeners.push(callback);
  351. return this;
  352. },
  353. removeListener: function (callback) {
  354. var index = listeners.indexOf(callback);
  355. ~index && listeners.splice(index, 1);
  356. return this;
  357. }
  358. };
  359. return ret;
  360. };
  361. }());
  362. $(function () {
  363. var handleOrtchange = function (mql) {
  364. if ( state !== mql.matches ) {
  365. $( window ).trigger( 'ortchange' );
  366. state = mql.matches;
  367. }
  368. },
  369. state = true;
  370. $.mediaQuery = {
  371. ortchange: 'screen and (width: ' + window.innerWidth + 'px)'
  372. };
  373. $.matchMedia($.mediaQuery.ortchange).addListener(handleOrtchange);
  374. });
  375. /**
  376. * @name Trigger Events
  377. * @theme event
  378. * @desc 扩展的事件
  379. * - ***scrollStop*** : scroll停下来时触发, 考虑前进或者后退后scroll事件不触发情况。
  380. * - ***ortchange*** : 当转屏的时候触发,兼容uc和其他不支持orientationchange的设备,利用css media query实现,解决了转屏延时及orientation事件的兼容性问题
  381. * @example $(document).on('scrollStop', function () { //scroll停下来时显示scrollStop
  382. * console.log('scrollStop');
  383. * });
  384. *
  385. * $(window).on('ortchange', function () { //当转屏的时候触发
  386. * console.log('ortchange');
  387. * });
  388. */
  389. /** dispatch scrollStop */
  390. function _registerScrollStop(){
  391. $(window).on('scroll', $.debounce(80, function() {
  392. $(document).trigger('scrollStop');
  393. }, false));
  394. }
  395. //在离开页面,前进或后退回到页面后,重新绑定scroll, 需要off掉所有的scroll,否则scroll时间不触发
  396. function _touchstartHander() {
  397. $(window).off('scroll');
  398. _registerScrollStop();
  399. }
  400. _registerScrollStop();
  401. $(window).on('pageshow', function(e){
  402. if(e.persisted) {//如果是从bfcache中加载页面
  403. $(document).off('touchstart', _touchstartHander).one('touchstart', _touchstartHander);
  404. }
  405. });
  406. })(Zepto);