nested-scroll.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. /*!
  2. * better-scroll / nested-scroll
  3. * (c) 2016-2021 ustbhuangyi
  4. * Released under the MIT License.
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  8. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  9. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.NestedScroll = {}));
  10. }(this, (function (exports) { 'use strict';
  11. /*! *****************************************************************************
  12. Copyright (c) Microsoft Corporation.
  13. Permission to use, copy, modify, and/or distribute this software for any
  14. purpose with or without fee is hereby granted.
  15. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  16. REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  17. AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  18. INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  19. LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  20. OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  21. PERFORMANCE OF THIS SOFTWARE.
  22. ***************************************************************************** */
  23. function __spreadArrays() {
  24. for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
  25. for (var r = Array(s), k = 0, i = 0; i < il; i++)
  26. for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
  27. r[k] = a[j];
  28. return r;
  29. }
  30. function warn(msg) {
  31. console.error("[BScroll warn]: " + msg);
  32. }
  33. // ssr support
  34. var inBrowser = typeof window !== 'undefined';
  35. var ua = inBrowser && navigator.userAgent.toLowerCase();
  36. !!(ua && /wechatdevtools/.test(ua));
  37. ua && ua.indexOf('android') > 0;
  38. /* istanbul ignore next */
  39. ((function () {
  40. if (typeof ua === 'string') {
  41. var regex = /os (\d\d?_\d(_\d)?)/;
  42. var matches = regex.exec(ua);
  43. if (!matches)
  44. return false;
  45. var parts = matches[1].split('_').map(function (item) {
  46. return parseInt(item, 10);
  47. });
  48. // ios version >= 13.4 issue 982
  49. return !!(parts[0] === 13 && parts[1] >= 4);
  50. }
  51. return false;
  52. }))();
  53. /* istanbul ignore next */
  54. var supportsPassive = false;
  55. /* istanbul ignore next */
  56. if (inBrowser) {
  57. var EventName = 'test-passive';
  58. try {
  59. var opts = {};
  60. Object.defineProperty(opts, 'passive', {
  61. get: function () {
  62. supportsPassive = true;
  63. },
  64. }); // https://github.com/facebook/flow/issues/285
  65. window.addEventListener(EventName, function () { }, opts);
  66. }
  67. catch (e) { }
  68. }
  69. var extend = function (target, source) {
  70. for (var key in source) {
  71. target[key] = source[key];
  72. }
  73. return target;
  74. };
  75. function findIndex(ary, fn) {
  76. if (ary.findIndex) {
  77. return ary.findIndex(fn);
  78. }
  79. var index = -1;
  80. ary.some(function (item, i, ary) {
  81. var ret = fn(item, i, ary);
  82. if (ret) {
  83. index = i;
  84. return ret;
  85. }
  86. });
  87. return index;
  88. }
  89. var elementStyle = (inBrowser &&
  90. document.createElement('div').style);
  91. var vendor = (function () {
  92. /* istanbul ignore if */
  93. if (!inBrowser) {
  94. return false;
  95. }
  96. var transformNames = [
  97. {
  98. key: 'standard',
  99. value: 'transform',
  100. },
  101. {
  102. key: 'webkit',
  103. value: 'webkitTransform',
  104. },
  105. {
  106. key: 'Moz',
  107. value: 'MozTransform',
  108. },
  109. {
  110. key: 'O',
  111. value: 'OTransform',
  112. },
  113. {
  114. key: 'ms',
  115. value: 'msTransform',
  116. },
  117. ];
  118. for (var _i = 0, transformNames_1 = transformNames; _i < transformNames_1.length; _i++) {
  119. var obj = transformNames_1[_i];
  120. if (elementStyle[obj.value] !== undefined) {
  121. return obj.key;
  122. }
  123. }
  124. /* istanbul ignore next */
  125. return false;
  126. })();
  127. /* istanbul ignore next */
  128. function prefixStyle(style) {
  129. if (vendor === false) {
  130. return style;
  131. }
  132. if (vendor === 'standard') {
  133. if (style === 'transitionEnd') {
  134. return 'transitionend';
  135. }
  136. return style;
  137. }
  138. return vendor + style.charAt(0).toUpperCase() + style.substr(1);
  139. }
  140. vendor && vendor !== 'standard' ? '-' + vendor.toLowerCase() + '-' : '';
  141. var transform = prefixStyle('transform');
  142. var transition = prefixStyle('transition');
  143. inBrowser && prefixStyle('perspective') in elementStyle;
  144. ({
  145. transform: transform,
  146. transition: transition,
  147. transitionTimingFunction: prefixStyle('transitionTimingFunction'),
  148. transitionDuration: prefixStyle('transitionDuration'),
  149. transitionDelay: prefixStyle('transitionDelay'),
  150. transformOrigin: prefixStyle('transformOrigin'),
  151. transitionEnd: prefixStyle('transitionEnd'),
  152. transitionProperty: prefixStyle('transitionProperty'),
  153. });
  154. var BScrollFamily = /** @class */ (function () {
  155. function BScrollFamily(scroll) {
  156. this.ancestors = [];
  157. this.descendants = [];
  158. this.hooksManager = [];
  159. this.analyzed = false;
  160. this.selfScroll = scroll;
  161. }
  162. BScrollFamily.create = function (scroll) {
  163. return new BScrollFamily(scroll);
  164. };
  165. BScrollFamily.prototype.hasAncestors = function (bscrollFamily) {
  166. var index = findIndex(this.ancestors, function (_a) {
  167. var item = _a[0];
  168. return item === bscrollFamily;
  169. });
  170. return index > -1;
  171. };
  172. BScrollFamily.prototype.hasDescendants = function (bscrollFamily) {
  173. var index = findIndex(this.descendants, function (_a) {
  174. var item = _a[0];
  175. return item === bscrollFamily;
  176. });
  177. return index > -1;
  178. };
  179. BScrollFamily.prototype.addAncestor = function (bscrollFamily, distance) {
  180. var ancestors = this.ancestors;
  181. ancestors.push([bscrollFamily, distance]);
  182. // by ascend
  183. ancestors.sort(function (a, b) {
  184. return a[1] - b[1];
  185. });
  186. };
  187. BScrollFamily.prototype.addDescendant = function (bscrollFamily, distance) {
  188. var descendants = this.descendants;
  189. descendants.push([bscrollFamily, distance]);
  190. // by ascend
  191. descendants.sort(function (a, b) {
  192. return a[1] - b[1];
  193. });
  194. };
  195. BScrollFamily.prototype.removeAncestor = function (bscrollFamily) {
  196. var ancestors = this.ancestors;
  197. if (ancestors.length) {
  198. var index = findIndex(this.ancestors, function (_a) {
  199. var item = _a[0];
  200. return item === bscrollFamily;
  201. });
  202. if (index > -1) {
  203. return ancestors.splice(index, 1);
  204. }
  205. }
  206. };
  207. BScrollFamily.prototype.removeDescendant = function (bscrollFamily) {
  208. var descendants = this.descendants;
  209. if (descendants.length) {
  210. var index = findIndex(this.descendants, function (_a) {
  211. var item = _a[0];
  212. return item === bscrollFamily;
  213. });
  214. if (index > -1) {
  215. return descendants.splice(index, 1);
  216. }
  217. }
  218. };
  219. BScrollFamily.prototype.registerHooks = function (hook, eventType, handler) {
  220. hook.on(eventType, handler);
  221. this.hooksManager.push([hook, eventType, handler]);
  222. };
  223. BScrollFamily.prototype.setAnalyzed = function (flag) {
  224. if (flag === void 0) { flag = false; }
  225. this.analyzed = flag;
  226. };
  227. BScrollFamily.prototype.purge = function () {
  228. var _this = this;
  229. // remove self from graph
  230. this.ancestors.forEach(function (_a) {
  231. var bscrollFamily = _a[0];
  232. bscrollFamily.removeDescendant(_this);
  233. });
  234. this.descendants.forEach(function (_a) {
  235. var bscrollFamily = _a[0];
  236. bscrollFamily.removeAncestor(_this);
  237. });
  238. // remove all hook handlers
  239. this.hooksManager.forEach(function (_a) {
  240. var hooks = _a[0], eventType = _a[1], handler = _a[2];
  241. hooks.off(eventType, handler);
  242. });
  243. this.hooksManager = [];
  244. };
  245. return BScrollFamily;
  246. }());
  247. var sourcePrefix = 'plugins.nestedScroll';
  248. var propertiesMap = [
  249. {
  250. key: 'purgeNestedScroll',
  251. name: 'purgeNestedScroll',
  252. },
  253. ];
  254. var propertiesConfig = propertiesMap.map(function (item) {
  255. return {
  256. key: item.key,
  257. sourceKey: sourcePrefix + "." + item.name,
  258. };
  259. });
  260. var DEFAUL_GROUP_ID = 'INTERNAL_NESTED_SCROLL';
  261. var forceScrollStopHandler = function (scrolls) {
  262. scrolls.forEach(function (scroll) {
  263. if (scroll.pending) {
  264. scroll.stop();
  265. scroll.resetPosition();
  266. }
  267. });
  268. };
  269. var enableScrollHander = function (scrolls) {
  270. scrolls.forEach(function (scroll) {
  271. scroll.enable();
  272. });
  273. };
  274. var disableScrollHander = function (scrolls, currentScroll) {
  275. scrolls.forEach(function (scroll) {
  276. if (scroll.hasHorizontalScroll === currentScroll.hasHorizontalScroll ||
  277. scroll.hasVerticalScroll === currentScroll.hasVerticalScroll) {
  278. scroll.disable();
  279. }
  280. });
  281. };
  282. var syncTouchstartData = function (scrolls) {
  283. scrolls.forEach(function (scroll) {
  284. var _a = scroll.scroller, actions = _a.actions, scrollBehaviorX = _a.scrollBehaviorX, scrollBehaviorY = _a.scrollBehaviorY;
  285. // prevent click triggering many times
  286. actions.fingerMoved = true;
  287. actions.contentMoved = false;
  288. actions.directionLockAction.reset();
  289. scrollBehaviorX.start();
  290. scrollBehaviorY.start();
  291. scrollBehaviorX.resetStartPos();
  292. scrollBehaviorY.resetStartPos();
  293. actions.startTime = +new Date();
  294. });
  295. };
  296. var isOutOfBoundary = function (scroll) {
  297. var hasHorizontalScroll = scroll.hasHorizontalScroll, hasVerticalScroll = scroll.hasVerticalScroll, x = scroll.x, y = scroll.y, minScrollX = scroll.minScrollX, maxScrollX = scroll.maxScrollX, minScrollY = scroll.minScrollY, maxScrollY = scroll.maxScrollY, movingDirectionX = scroll.movingDirectionX, movingDirectionY = scroll.movingDirectionY;
  298. var ret = false;
  299. var outOfLeftBoundary = x >= minScrollX && movingDirectionX === -1 /* Negative */;
  300. var outOfRightBoundary = x <= maxScrollX && movingDirectionX === 1 /* Positive */;
  301. var outOfTopBoundary = y >= minScrollY && movingDirectionY === -1 /* Negative */;
  302. var outOfBottomBoundary = y <= maxScrollY && movingDirectionY === 1 /* Positive */;
  303. if (hasVerticalScroll) {
  304. ret = outOfTopBoundary || outOfBottomBoundary;
  305. }
  306. else if (hasHorizontalScroll) {
  307. ret = outOfLeftBoundary || outOfRightBoundary;
  308. }
  309. return ret;
  310. };
  311. var isResettingPosition = function (scroll) {
  312. var hasHorizontalScroll = scroll.hasHorizontalScroll, hasVerticalScroll = scroll.hasVerticalScroll, x = scroll.x, y = scroll.y, minScrollX = scroll.minScrollX, maxScrollX = scroll.maxScrollX, minScrollY = scroll.minScrollY, maxScrollY = scroll.maxScrollY;
  313. var ret = false;
  314. var outOfLeftBoundary = x > minScrollX;
  315. var outOfRightBoundary = x < maxScrollX;
  316. var outOfTopBoundary = y > minScrollY;
  317. var outOfBottomBoundary = y < maxScrollY;
  318. if (hasVerticalScroll) {
  319. ret = outOfTopBoundary || outOfBottomBoundary;
  320. }
  321. else if (hasHorizontalScroll) {
  322. ret = outOfLeftBoundary || outOfRightBoundary;
  323. }
  324. return ret;
  325. };
  326. var resetPositionHandler = function (scroll) {
  327. scroll.scroller.reflow();
  328. scroll.resetPosition(0 /* Immediately */);
  329. };
  330. var calculateDistance = function (childNode, parentNode) {
  331. var distance = 0;
  332. var parent = childNode.parentNode;
  333. while (parent && parent !== parentNode) {
  334. distance++;
  335. parent = parent.parentNode;
  336. }
  337. return distance;
  338. };
  339. var NestedScroll = /** @class */ (function () {
  340. function NestedScroll(scroll) {
  341. var groupId = this.handleOptions(scroll);
  342. var instance = NestedScroll.instancesMap[groupId];
  343. if (!instance) {
  344. instance = NestedScroll.instancesMap[groupId] = this;
  345. instance.store = [];
  346. instance.hooksFn = [];
  347. }
  348. instance.init(scroll);
  349. return instance;
  350. }
  351. NestedScroll.getAllNestedScrolls = function () {
  352. var instancesMap = NestedScroll.instancesMap;
  353. return Object.keys(instancesMap).map(function (key) { return instancesMap[key]; });
  354. };
  355. NestedScroll.purgeAllNestedScrolls = function () {
  356. var nestedScrolls = NestedScroll.getAllNestedScrolls();
  357. nestedScrolls.forEach(function (ns) { return ns.purgeNestedScroll(); });
  358. };
  359. NestedScroll.prototype.handleOptions = function (scroll) {
  360. var userOptions = (scroll.options.nestedScroll === true
  361. ? {}
  362. : scroll.options.nestedScroll);
  363. var defaultOptions = {
  364. groupId: DEFAUL_GROUP_ID,
  365. };
  366. this.options = extend(defaultOptions, userOptions);
  367. var groupIdType = typeof this.options.groupId;
  368. if (groupIdType !== 'string' && groupIdType !== 'number') {
  369. warn('groupId must be string or number for NestedScroll plugin');
  370. }
  371. return this.options.groupId;
  372. };
  373. NestedScroll.prototype.init = function (scroll) {
  374. scroll.proxy(propertiesConfig);
  375. this.addBScroll(scroll);
  376. this.buildBScrollGraph();
  377. this.analyzeBScrollGraph();
  378. this.ensureEventInvokeSequence();
  379. this.handleHooks(scroll);
  380. };
  381. NestedScroll.prototype.handleHooks = function (scroll) {
  382. var _this = this;
  383. this.registerHooks(scroll.hooks, scroll.hooks.eventTypes.destroy, function () {
  384. _this.deleteScroll(scroll);
  385. });
  386. };
  387. NestedScroll.prototype.deleteScroll = function (scroll) {
  388. var wrapper = scroll.wrapper;
  389. wrapper.isBScrollContainer = undefined;
  390. var store = this.store;
  391. var hooksFn = this.hooksFn;
  392. var i = findIndex(store, function (bscrollFamily) {
  393. return bscrollFamily.selfScroll === scroll;
  394. });
  395. if (i > -1) {
  396. var bscrollFamily = store[i];
  397. bscrollFamily.purge();
  398. store.splice(i, 1);
  399. }
  400. var k = findIndex(hooksFn, function (_a) {
  401. var hooks = _a[0];
  402. return hooks === scroll.hooks;
  403. });
  404. if (k > -1) {
  405. var _a = hooksFn[k], hooks = _a[0], eventType = _a[1], handler = _a[2];
  406. hooks.off(eventType, handler);
  407. hooksFn.splice(k, 1);
  408. }
  409. };
  410. NestedScroll.prototype.addBScroll = function (scroll) {
  411. this.store.push(BScrollFamily.create(scroll));
  412. };
  413. NestedScroll.prototype.buildBScrollGraph = function () {
  414. var store = this.store;
  415. var bf1;
  416. var bf2;
  417. var wrapper1;
  418. var wrapper2;
  419. var len = this.store.length;
  420. // build graph
  421. for (var i = 0; i < len; i++) {
  422. bf1 = store[i];
  423. wrapper1 = bf1.selfScroll.wrapper;
  424. for (var j = 0; j < len; j++) {
  425. bf2 = store[j];
  426. wrapper2 = bf2.selfScroll.wrapper;
  427. // same bs
  428. if (bf1 === bf2)
  429. continue;
  430. if (!wrapper1.contains(wrapper2))
  431. continue;
  432. // bs1 contains bs2
  433. var distance = calculateDistance(wrapper2, wrapper1);
  434. if (!bf1.hasDescendants(bf2)) {
  435. bf1.addDescendant(bf2, distance);
  436. }
  437. if (!bf2.hasAncestors(bf1)) {
  438. bf2.addAncestor(bf1, distance);
  439. }
  440. }
  441. }
  442. };
  443. NestedScroll.prototype.analyzeBScrollGraph = function () {
  444. this.store.forEach(function (bscrollFamily) {
  445. if (bscrollFamily.analyzed) {
  446. return;
  447. }
  448. var ancestors = bscrollFamily.ancestors, descendants = bscrollFamily.descendants, currentScroll = bscrollFamily.selfScroll;
  449. var beforeScrollStartHandler = function () {
  450. // always get the latest scroll
  451. var ancestorScrolls = ancestors.map(function (_a) {
  452. var bscrollFamily = _a[0];
  453. return bscrollFamily.selfScroll;
  454. });
  455. var descendantScrolls = descendants.map(function (_a) {
  456. var bscrollFamily = _a[0];
  457. return bscrollFamily.selfScroll;
  458. });
  459. forceScrollStopHandler(__spreadArrays(ancestorScrolls, descendantScrolls));
  460. if (isResettingPosition(currentScroll)) {
  461. resetPositionHandler(currentScroll);
  462. }
  463. syncTouchstartData(ancestorScrolls);
  464. disableScrollHander(ancestorScrolls, currentScroll);
  465. };
  466. var touchEndHandler = function () {
  467. var ancestorScrolls = ancestors.map(function (_a) {
  468. var bscrollFamily = _a[0];
  469. return bscrollFamily.selfScroll;
  470. });
  471. var descendantScrolls = descendants.map(function (_a) {
  472. var bscrollFamily = _a[0];
  473. return bscrollFamily.selfScroll;
  474. });
  475. enableScrollHander(__spreadArrays(ancestorScrolls, descendantScrolls));
  476. };
  477. bscrollFamily.registerHooks(currentScroll, currentScroll.eventTypes.beforeScrollStart, beforeScrollStartHandler);
  478. bscrollFamily.registerHooks(currentScroll, currentScroll.eventTypes.touchEnd, touchEndHandler);
  479. var selfActionsHooks = currentScroll.scroller.actions.hooks;
  480. bscrollFamily.registerHooks(selfActionsHooks, selfActionsHooks.eventTypes.detectMovingDirection, function () {
  481. var ancestorScrolls = ancestors.map(function (_a) {
  482. var bscrollFamily = _a[0];
  483. return bscrollFamily.selfScroll;
  484. });
  485. var parentScroll = ancestorScrolls[0];
  486. var otherAncestorScrolls = ancestorScrolls.slice(1);
  487. var contentMoved = currentScroll.scroller.actions.contentMoved;
  488. var isTopScroll = ancestorScrolls.length === 0;
  489. if (contentMoved) {
  490. disableScrollHander(ancestorScrolls, currentScroll);
  491. }
  492. else if (!isTopScroll) {
  493. if (isOutOfBoundary(currentScroll)) {
  494. disableScrollHander([currentScroll], currentScroll);
  495. if (parentScroll) {
  496. enableScrollHander([parentScroll]);
  497. }
  498. disableScrollHander(otherAncestorScrolls, currentScroll);
  499. return true;
  500. }
  501. }
  502. });
  503. bscrollFamily.setAnalyzed(true);
  504. });
  505. };
  506. // make sure touchmove|touchend invoke from child to parent
  507. NestedScroll.prototype.ensureEventInvokeSequence = function () {
  508. var copied = this.store.slice();
  509. var sequencedScroll = copied.sort(function (a, b) {
  510. return a.descendants.length - b.descendants.length;
  511. });
  512. sequencedScroll.forEach(function (bscrollFamily) {
  513. var scroll = bscrollFamily.selfScroll;
  514. scroll.scroller.actionsHandler.rebindDOMEvents();
  515. });
  516. };
  517. NestedScroll.prototype.registerHooks = function (hooks, name, handler) {
  518. hooks.on(name, handler, this);
  519. this.hooksFn.push([hooks, name, handler]);
  520. };
  521. NestedScroll.prototype.purgeNestedScroll = function () {
  522. var groupId = this.options.groupId;
  523. this.store.forEach(function (bscrollFamily) {
  524. bscrollFamily.purge();
  525. });
  526. this.store = [];
  527. this.hooksFn.forEach(function (_a) {
  528. var hooks = _a[0], eventType = _a[1], handler = _a[2];
  529. hooks.off(eventType, handler);
  530. });
  531. this.hooksFn = [];
  532. delete NestedScroll.instancesMap[groupId];
  533. };
  534. NestedScroll.pluginName = 'nestedScroll';
  535. NestedScroll.instancesMap = {};
  536. return NestedScroll;
  537. }());
  538. exports.DEFAUL_GROUP_ID = DEFAUL_GROUP_ID;
  539. exports.default = NestedScroll;
  540. Object.defineProperty(exports, '__esModule', { value: true });
  541. })));