mui.pullToRefresh.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. (function($, window, document) {
  2. var STATE_BEFORECHANGEOFFSET = 'beforeChangeOffset';
  3. var STATE_AFTERCHANGEOFFSET = 'afterChangeOffset';
  4. var EVENT_PULLSTART = 'pullstart';
  5. var EVENT_PULLING = 'pulling';
  6. var EVENT_BEFORECHANGEOFFSET = STATE_BEFORECHANGEOFFSET;
  7. var EVENT_AFTERCHANGEOFFSET = STATE_AFTERCHANGEOFFSET;
  8. var EVENT_DRAGENDAFTERCHANGEOFFSET = 'dragEndAfterChangeOffset';
  9. var CLASS_TRANSITIONING = $.className('transitioning');
  10. var CLASS_PULL_TOP_TIPS = $.className('pull-top-tips');
  11. var CLASS_PULL_BOTTOM_TIPS = $.className('pull-bottom-tips');
  12. var CLASS_PULL_LOADING = $.className('pull-loading');
  13. var CLASS_SCROLL = $.className('scroll');
  14. var CLASS_PULL_TOP_ARROW = $.className('pull-loading') + ' ' + $.className('icon') + ' ' + $.className('icon-pulldown');
  15. var CLASS_PULL_TOP_ARROW_REVERSE = CLASS_PULL_TOP_ARROW + ' ' + $.className('reverse');
  16. var CLASS_PULL_TOP_SPINNER = $.className('pull-loading') + ' ' + $.className('spinner');
  17. var CLASS_HIDDEN = $.className('hidden');
  18. var SELECTOR_PULL_LOADING = '.' + CLASS_PULL_LOADING;
  19. $.PullToRefresh = $.Class.extend({
  20. init: function(element, options) {
  21. this.element = element;
  22. this.options = $.extend(true, {
  23. down: {
  24. height: 75,
  25. callback: false,
  26. },
  27. up: {
  28. auto: false,
  29. offset: 100, //距离底部高度(到达该高度即触发)
  30. show: true,
  31. contentdown: '上拉显示更多',
  32. contentrefresh: '正在加载...',
  33. contentnomore: '没有更多数据了',
  34. callback: false
  35. },
  36. preventDefaultException: {
  37. tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/
  38. }
  39. }, options);
  40. this.stopped = this.isNeedRefresh = this.isDragging = false;
  41. this.state = STATE_BEFORECHANGEOFFSET;
  42. this.isInScroll = this.element.classList.contains(CLASS_SCROLL);
  43. this.initPullUpTips();
  44. this.initEvent();
  45. },
  46. _preventDefaultException: function(el, exceptions) {
  47. for (var i in exceptions) {
  48. if (exceptions[i].test(el[i])) {
  49. return true;
  50. }
  51. }
  52. return false;
  53. },
  54. initEvent: function() {
  55. if ($.isFunction(this.options.down.callback)) {
  56. this.element.addEventListener('touchstart', this);
  57. this.element.addEventListener('drag', this);
  58. this.element.addEventListener('dragend', this);
  59. }
  60. if (this.pullUpTips) {
  61. this.element.addEventListener('dragup', this);
  62. if (this.isInScroll) {
  63. this.element.addEventListener('scrollbottom', this);
  64. } else {
  65. window.addEventListener('scroll', this);
  66. }
  67. }
  68. },
  69. handleEvent: function(e) {
  70. switch (e.type) {
  71. case 'touchstart':
  72. this.isInScroll && this._canPullDown() && e.target && !this._preventDefaultException(e.target, this.options.preventDefaultException) && e.preventDefault();
  73. break;
  74. case 'drag':
  75. this._drag(e);
  76. break;
  77. case 'dragend':
  78. this._dragend(e);
  79. break;
  80. case 'webkitTransitionEnd':
  81. this._transitionEnd(e);
  82. break;
  83. case 'dragup':
  84. case 'scroll':
  85. this._dragup(e);
  86. break;
  87. case 'scrollbottom':
  88. this.pullUpLoading(e);
  89. break;
  90. }
  91. },
  92. initPullDownTips: function() {
  93. var self = this;
  94. if ($.isFunction(self.options.down.callback)) {
  95. self.pullDownTips = (function() {
  96. var element = document.querySelector('.' + CLASS_PULL_TOP_TIPS);
  97. if (element) {
  98. element.parentNode.removeChild(element);
  99. }
  100. if (!element) {
  101. element = document.createElement('div');
  102. element.classList.add(CLASS_PULL_TOP_TIPS);
  103. element.innerHTML = '<div class="mui-pull-top-wrapper"><span class="mui-pull-loading mui-icon mui-icon-pulldown"></span></div>';
  104. element.addEventListener('webkitTransitionEnd', self);
  105. }
  106. self.pullDownTipsIcon = element.querySelector(SELECTOR_PULL_LOADING);
  107. document.body.appendChild(element);
  108. return element;
  109. }());
  110. }
  111. },
  112. initPullUpTips: function() {
  113. var self = this;
  114. if ($.isFunction(self.options.up.callback)) {
  115. self.pullUpTips = (function() {
  116. var element = self.element.querySelector('.' + CLASS_PULL_BOTTOM_TIPS);
  117. if (!element) {
  118. element = document.createElement('div');
  119. element.classList.add(CLASS_PULL_BOTTOM_TIPS);
  120. if (!self.options.up.show) {
  121. element.classList.add(CLASS_HIDDEN);
  122. }
  123. element.innerHTML = '<div class="mui-pull-bottom-wrapper"><span class="mui-pull-loading">' + self.options.up.contentdown + '</span></div>';
  124. self.element.appendChild(element);
  125. }
  126. self.pullUpTipsIcon = element.querySelector(SELECTOR_PULL_LOADING);
  127. return element;
  128. }());
  129. }
  130. },
  131. _transitionEnd: function(e) {
  132. if (e.target === this.pullDownTips && this.removing) {
  133. this.removePullDownTips();
  134. }
  135. },
  136. _dragup: function(e) {
  137. var self = this;
  138. if (self.loading) {
  139. return;
  140. }
  141. if (e && e.detail && $.gestures.session.drag) {
  142. self.isDraggingUp = true;
  143. } else {
  144. if (!self.isDraggingUp) { //scroll event
  145. return;
  146. }
  147. }
  148. if (!self.isDragging) {
  149. if (self._canPullUp()) {
  150. self.pullUpLoading(e);
  151. }
  152. }
  153. },
  154. _canPullUp: function() {
  155. if (this.removing) {
  156. return false;
  157. }
  158. if (this.isInScroll) {
  159. var scrollId = this.element.parentNode.getAttribute('data-scroll');
  160. if (scrollId) {
  161. var scrollApi = $.data[scrollId];
  162. return scrollApi.y === scrollApi.maxScrollY;
  163. }
  164. }
  165. return window.pageYOffset + window.innerHeight + this.options.up.offset >= document.documentElement.scrollHeight;
  166. },
  167. _canPullDown: function() {
  168. if (this.removing) {
  169. return false;
  170. }
  171. if (this.isInScroll) {
  172. var scrollId = this.element.parentNode.getAttribute('data-scroll');
  173. if (scrollId) {
  174. var scrollApi = $.data[scrollId];
  175. return scrollApi.y === 0;
  176. }
  177. }
  178. return document.body.scrollTop === 0;
  179. },
  180. _drag: function(e) {
  181. if (this.loading || this.stopped) {
  182. e.stopPropagation();
  183. e.detail.gesture.preventDefault();
  184. return;
  185. }
  186. var detail = e.detail;
  187. if (!this.isDragging) {
  188. if (detail.direction === 'down' && this._canPullDown()) {
  189. if (document.querySelector('.' + CLASS_PULL_TOP_TIPS)) {
  190. e.stopPropagation();
  191. e.detail.gesture.preventDefault();
  192. return;
  193. }
  194. this.isDragging = true;
  195. this.removing = false;
  196. this.startDeltaY = detail.deltaY;
  197. $.gestures.session.lockDirection = true; //锁定方向
  198. $.gestures.session.startDirection = detail.direction;
  199. this._pullStart(this.startDeltaY);
  200. }
  201. }
  202. if (this.isDragging) {
  203. e.stopPropagation();
  204. e.detail.gesture.preventDefault();
  205. var deltaY = detail.deltaY - this.startDeltaY;
  206. deltaY = Math.min(deltaY, 1.5 * this.options.down.height);
  207. this.deltaY = deltaY;
  208. this._pulling(deltaY);
  209. var state = deltaY > this.options.down.height ? STATE_AFTERCHANGEOFFSET : STATE_BEFORECHANGEOFFSET;
  210. if (this.state !== state) {
  211. this.state = state;
  212. if (this.state === STATE_AFTERCHANGEOFFSET) {
  213. this.removing = false;
  214. this.isNeedRefresh = true;
  215. } else {
  216. this.removing = true;
  217. this.isNeedRefresh = false;
  218. }
  219. this['_' + state](deltaY);
  220. }
  221. if ($.os.ios && parseFloat($.os.version) >= 8) {
  222. var clientY = detail.gesture.touches[0].clientY;
  223. if ((clientY + 10) > window.innerHeight || clientY < 10) {
  224. this._dragend(e);
  225. return;
  226. }
  227. }
  228. }
  229. },
  230. _dragend: function(e) {
  231. var self = this;
  232. if (self.isDragging) {
  233. self.isDragging = false;
  234. self._dragEndAfterChangeOffset(self.isNeedRefresh);
  235. }
  236. if (self.isPullingUp) {
  237. if (self.pullingUpTimeout) {
  238. clearTimeout(self.pullingUpTimeout);
  239. }
  240. self.pullingUpTimeout = setTimeout(function() {
  241. self.isPullingUp = false;
  242. }, 1000);
  243. }
  244. },
  245. _pullStart: function(startDeltaY) {
  246. this.pullStart(startDeltaY);
  247. $.trigger(this.element, EVENT_PULLSTART, {
  248. api: this,
  249. startDeltaY: startDeltaY
  250. });
  251. },
  252. _pulling: function(deltaY) {
  253. this.pulling(deltaY);
  254. $.trigger(this.element, EVENT_PULLING, {
  255. api: this,
  256. deltaY: deltaY
  257. });
  258. },
  259. _beforeChangeOffset: function(deltaY) {
  260. this.beforeChangeOffset(deltaY);
  261. $.trigger(this.element, EVENT_BEFORECHANGEOFFSET, {
  262. api: this,
  263. deltaY: deltaY
  264. });
  265. },
  266. _afterChangeOffset: function(deltaY) {
  267. this.afterChangeOffset(deltaY);
  268. $.trigger(this.element, EVENT_AFTERCHANGEOFFSET, {
  269. api: this,
  270. deltaY: deltaY
  271. });
  272. },
  273. _dragEndAfterChangeOffset: function(isNeedRefresh) {
  274. this.dragEndAfterChangeOffset(isNeedRefresh);
  275. $.trigger(this.element, EVENT_DRAGENDAFTERCHANGEOFFSET, {
  276. api: this,
  277. isNeedRefresh: isNeedRefresh
  278. });
  279. },
  280. removePullDownTips: function() {
  281. if (this.pullDownTips) {
  282. try {
  283. this.pullDownTips.parentNode && this.pullDownTips.parentNode.removeChild(this.pullDownTips);
  284. this.pullDownTips = null;
  285. this.removing = false;
  286. } catch (e) {}
  287. }
  288. },
  289. pullStart: function(startDeltaY) {
  290. this.initPullDownTips(startDeltaY);
  291. },
  292. pulling: function(deltaY) {
  293. this.pullDownTips.style.webkitTransform = 'translate3d(0,' + deltaY + 'px,0)';
  294. },
  295. beforeChangeOffset: function(deltaY) {
  296. this.pullDownTipsIcon.className = CLASS_PULL_TOP_ARROW;
  297. },
  298. afterChangeOffset: function(deltaY) {
  299. this.pullDownTipsIcon.className = CLASS_PULL_TOP_ARROW_REVERSE;
  300. },
  301. dragEndAfterChangeOffset: function(isNeedRefresh) {
  302. if (isNeedRefresh) {
  303. this.pullDownTipsIcon.className = CLASS_PULL_TOP_SPINNER;
  304. this.pullDownLoading();
  305. } else {
  306. this.pullDownTipsIcon.className = CLASS_PULL_TOP_ARROW;
  307. this.endPullDownToRefresh();
  308. }
  309. },
  310. pullDownLoading: function() {
  311. if (this.loading) {
  312. return;
  313. }
  314. if (!this.pullDownTips) {
  315. this.initPullDownTips();
  316. this.dragEndAfterChangeOffset(true);
  317. return;
  318. }
  319. this.loading = true;
  320. this.pullDownTips.classList.add(CLASS_TRANSITIONING);
  321. this.pullDownTips.style.webkitTransform = 'translate3d(0,' + this.options.down.height + 'px,0)';
  322. this.options.down.callback.apply(this);
  323. },
  324. pullUpLoading: function(e) {
  325. if (this.loading || this.finished) {
  326. return;
  327. }
  328. this.loading = true;
  329. this.isDraggingUp = false;
  330. this.pullUpTips.classList.remove(CLASS_HIDDEN);
  331. e && e.detail && e.detail.gesture && e.detail.gesture.preventDefault();
  332. this.pullUpTipsIcon.innerHTML = this.options.up.contentrefresh;
  333. this.options.up.callback.apply(this);
  334. },
  335. endPullDownToRefresh: function() {
  336. this.loading = false;
  337. this.pullUpTips && this.pullUpTips.classList.remove(CLASS_HIDDEN);
  338. this.pullDownTips.classList.add(CLASS_TRANSITIONING);
  339. this.pullDownTips.style.webkitTransform = 'translate3d(0,0,0)';
  340. if (this.deltaY <= 0) {
  341. this.removePullDownTips();
  342. } else {
  343. this.removing = true;
  344. }
  345. },
  346. endPullUpToRefresh: function(finished) {
  347. if (finished) {
  348. this.finished = true;
  349. this.pullUpTipsIcon.innerHTML = this.options.up.contentnomore;
  350. this.element.removeEventListener('dragup', this);
  351. window.removeEventListener('scroll', this);
  352. } else {
  353. this.pullUpTipsIcon.innerHTML = this.options.up.contentdown;
  354. }
  355. this.loading = false;
  356. },
  357. setStopped: function(stopped) {
  358. if (stopped != this.stopped) {
  359. this.stopped = stopped;
  360. this.pullUpTips && this.pullUpTips.classList[stopped ? 'add' : 'remove'](CLASS_HIDDEN);
  361. }
  362. },
  363. refresh: function(isReset) {
  364. if (isReset && this.finished && this.pullUpTipsIcon) {
  365. this.pullUpTipsIcon.innerHTML = this.options.up.contentdown;
  366. this.element.addEventListener('dragup', this);
  367. window.addEventListener('scroll', this);
  368. this.finished = false;
  369. }
  370. }
  371. });
  372. $.fn.pullToRefresh = function(options) {
  373. var pullRefreshApis = [];
  374. options = options || {};
  375. this.each(function() {
  376. var self = this;
  377. var pullRefreshApi = null;
  378. var id = self.getAttribute('data-pullToRefresh');
  379. if (!id) {
  380. id = ++$.uuid;
  381. $.data[id] = pullRefreshApi = new $.PullToRefresh(self, options);
  382. self.setAttribute('data-pullToRefresh', id);
  383. } else {
  384. pullRefreshApi = $.data[id];
  385. }
  386. if (options.up && options.up.auto) { //如果设置了auto,则自动上拉一次
  387. pullRefreshApi.pullUpLoading();
  388. }
  389. pullRefreshApis.push(pullRefreshApi);
  390. });
  391. return pullRefreshApis.length === 1 ? pullRefreshApis[0] : pullRefreshApis;
  392. }
  393. })(mui, window, document);