mui.pullToRefresh.js 12 KB

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