indicators.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*!
  2. * better-scroll / indicators
  3. * (c) 2016-2021 ustbhuangyi
  4. * Released under the MIT License.
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  8. typeof define === 'function' && define.amd ? define(factory) :
  9. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Indicators = factory());
  10. }(this, (function () { '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. var __assign = function() {
  24. __assign = Object.assign || function __assign(t) {
  25. for (var s, i = 1, n = arguments.length; i < n; i++) {
  26. s = arguments[i];
  27. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
  28. }
  29. return t;
  30. };
  31. return __assign.apply(this, arguments);
  32. };
  33. function assert(condition, msg) {
  34. if (!condition) {
  35. throw new Error('[BScroll] ' + msg);
  36. }
  37. }
  38. // ssr support
  39. var inBrowser = typeof window !== 'undefined';
  40. var ua = inBrowser && navigator.userAgent.toLowerCase();
  41. !!(ua && /wechatdevtools/.test(ua));
  42. ua && ua.indexOf('android') > 0;
  43. /* istanbul ignore next */
  44. ((function () {
  45. if (typeof ua === 'string') {
  46. var regex = /os (\d\d?_\d(_\d)?)/;
  47. var matches = regex.exec(ua);
  48. if (!matches)
  49. return false;
  50. var parts = matches[1].split('_').map(function (item) {
  51. return parseInt(item, 10);
  52. });
  53. // ios version >= 13.4 issue 982
  54. return !!(parts[0] === 13 && parts[1] >= 4);
  55. }
  56. return false;
  57. }))();
  58. /* istanbul ignore next */
  59. var supportsPassive = false;
  60. /* istanbul ignore next */
  61. if (inBrowser) {
  62. var EventName = 'test-passive';
  63. try {
  64. var opts = {};
  65. Object.defineProperty(opts, 'passive', {
  66. get: function () {
  67. supportsPassive = true;
  68. },
  69. }); // https://github.com/facebook/flow/issues/285
  70. window.addEventListener(EventName, function () { }, opts);
  71. }
  72. catch (e) { }
  73. }
  74. function getNow() {
  75. return window.performance &&
  76. window.performance.now &&
  77. window.performance.timing
  78. ? window.performance.now() + window.performance.timing.navigationStart
  79. : +new Date();
  80. }
  81. function between(x, min, max) {
  82. if (x < min) {
  83. return min;
  84. }
  85. if (x > max) {
  86. return max;
  87. }
  88. return x;
  89. }
  90. var elementStyle = (inBrowser &&
  91. document.createElement('div').style);
  92. var vendor = (function () {
  93. /* istanbul ignore if */
  94. if (!inBrowser) {
  95. return false;
  96. }
  97. var transformNames = [
  98. {
  99. key: 'standard',
  100. value: 'transform',
  101. },
  102. {
  103. key: 'webkit',
  104. value: 'webkitTransform',
  105. },
  106. {
  107. key: 'Moz',
  108. value: 'MozTransform',
  109. },
  110. {
  111. key: 'O',
  112. value: 'OTransform',
  113. },
  114. {
  115. key: 'ms',
  116. value: 'msTransform',
  117. },
  118. ];
  119. for (var _i = 0, transformNames_1 = transformNames; _i < transformNames_1.length; _i++) {
  120. var obj = transformNames_1[_i];
  121. if (elementStyle[obj.value] !== undefined) {
  122. return obj.key;
  123. }
  124. }
  125. /* istanbul ignore next */
  126. return false;
  127. })();
  128. /* istanbul ignore next */
  129. function prefixStyle(style) {
  130. if (vendor === false) {
  131. return style;
  132. }
  133. if (vendor === 'standard') {
  134. if (style === 'transitionEnd') {
  135. return 'transitionend';
  136. }
  137. return style;
  138. }
  139. return vendor + style.charAt(0).toUpperCase() + style.substr(1);
  140. }
  141. function addEvent(el, type, fn, capture) {
  142. var useCapture = supportsPassive
  143. ? {
  144. passive: false,
  145. capture: !!capture,
  146. }
  147. : !!capture;
  148. el.addEventListener(type, fn, useCapture);
  149. }
  150. function removeEvent(el, type, fn, capture) {
  151. el.removeEventListener(type, fn, {
  152. capture: !!capture,
  153. });
  154. }
  155. vendor && vendor !== 'standard' ? '-' + vendor.toLowerCase() + '-' : '';
  156. var transform = prefixStyle('transform');
  157. var transition = prefixStyle('transition');
  158. inBrowser && prefixStyle('perspective') in elementStyle;
  159. var style = {
  160. transform: transform,
  161. transition: transition,
  162. transitionTimingFunction: prefixStyle('transitionTimingFunction'),
  163. transitionDuration: prefixStyle('transitionDuration'),
  164. transitionDelay: prefixStyle('transitionDelay'),
  165. transformOrigin: prefixStyle('transformOrigin'),
  166. transitionEnd: prefixStyle('transitionEnd'),
  167. transitionProperty: prefixStyle('transitionProperty'),
  168. };
  169. function getRect(el) {
  170. /* istanbul ignore if */
  171. if (el instanceof window.SVGElement) {
  172. var rect = el.getBoundingClientRect();
  173. return {
  174. top: rect.top,
  175. left: rect.left,
  176. width: rect.width,
  177. height: rect.height,
  178. };
  179. }
  180. else {
  181. return {
  182. top: el.offsetTop,
  183. left: el.offsetLeft,
  184. width: el.offsetWidth,
  185. height: el.offsetHeight,
  186. };
  187. }
  188. }
  189. function getClientSize(el) {
  190. return {
  191. width: el.clientWidth,
  192. height: el.clientHeight,
  193. };
  194. }
  195. var EventRegister = /** @class */ (function () {
  196. function EventRegister(wrapper, events) {
  197. this.wrapper = wrapper;
  198. this.events = events;
  199. this.addDOMEvents();
  200. }
  201. EventRegister.prototype.destroy = function () {
  202. this.removeDOMEvents();
  203. this.events = [];
  204. };
  205. EventRegister.prototype.addDOMEvents = function () {
  206. this.handleDOMEvents(addEvent);
  207. };
  208. EventRegister.prototype.removeDOMEvents = function () {
  209. this.handleDOMEvents(removeEvent);
  210. };
  211. EventRegister.prototype.handleDOMEvents = function (eventOperation) {
  212. var _this = this;
  213. var wrapper = this.wrapper;
  214. this.events.forEach(function (event) {
  215. eventOperation(wrapper, event.name, _this, !!event.capture);
  216. });
  217. };
  218. EventRegister.prototype.handleEvent = function (e) {
  219. var eventType = e.type;
  220. this.events.some(function (event) {
  221. if (event.name === eventType) {
  222. event.handler(e);
  223. return true;
  224. }
  225. return false;
  226. });
  227. };
  228. return EventRegister;
  229. }());
  230. var resolveRatioOption = function (ratioConfig) {
  231. var ret = {
  232. ratioX: 0,
  233. ratioY: 0,
  234. };
  235. /* istanbul ignore if */
  236. if (!ratioConfig) {
  237. return ret;
  238. }
  239. if (typeof ratioConfig === 'number') {
  240. ret.ratioX = ret.ratioY = ratioConfig;
  241. }
  242. else if (typeof ratioConfig === 'object' && ratioConfig) {
  243. ret.ratioX = ratioConfig.x || 0;
  244. ret.ratioY = ratioConfig.y || 0;
  245. }
  246. return ret;
  247. };
  248. var handleBubbleAndCancelable = function (e) {
  249. e.preventDefault();
  250. e.stopPropagation();
  251. };
  252. var Indicator = /** @class */ (function () {
  253. function Indicator(scroll, options) {
  254. this.scroll = scroll;
  255. this.options = options;
  256. this.currentPos = {
  257. x: 0,
  258. y: 0,
  259. };
  260. this.hooksFn = [];
  261. this.handleDOM();
  262. this.handleHooks();
  263. this.handleInteractive();
  264. }
  265. Indicator.prototype.handleDOM = function () {
  266. var _a = this.options, relationElement = _a.relationElement, _b = _a.relationElementHandleElementIndex, relationElementHandleElementIndex = _b === void 0 ? 0 : _b;
  267. this.wrapper = relationElement;
  268. this.indicatorEl = this.wrapper.children[relationElementHandleElementIndex];
  269. };
  270. Indicator.prototype.handleHooks = function () {
  271. var _this = this;
  272. var scroll = this.scroll;
  273. var scrollHooks = scroll.hooks;
  274. var translaterHooks = scroll.scroller.translater.hooks;
  275. var animaterHooks = scroll.scroller.animater.hooks;
  276. this.registerHooks(scrollHooks, scrollHooks.eventTypes.refresh, this.refresh);
  277. this.registerHooks(translaterHooks, translaterHooks.eventTypes.translate, function (pos) {
  278. _this.updatePosition(pos);
  279. });
  280. this.registerHooks(animaterHooks, animaterHooks.eventTypes.time, this.transitionTime);
  281. this.registerHooks(animaterHooks, animaterHooks.eventTypes.timeFunction, this.transitionTimingFunction);
  282. };
  283. Indicator.prototype.transitionTime = function (time) {
  284. if (time === void 0) { time = 0; }
  285. this.indicatorEl.style[style.transitionDuration] = time + 'ms';
  286. };
  287. Indicator.prototype.transitionTimingFunction = function (easing) {
  288. this.indicatorEl.style[style.transitionTimingFunction] = easing;
  289. };
  290. Indicator.prototype.handleInteractive = function () {
  291. if (this.options.interactive !== false) {
  292. this.registerEvents();
  293. }
  294. };
  295. Indicator.prototype.registerHooks = function (hooks, name, handler) {
  296. hooks.on(name, handler, this);
  297. this.hooksFn.push([hooks, name, handler]);
  298. };
  299. Indicator.prototype.registerEvents = function () {
  300. var _a = this.scroll.options, disableMouse = _a.disableMouse, disableTouch = _a.disableTouch;
  301. var startEvents = [];
  302. var moveEvents = [];
  303. var endEvents = [];
  304. if (!disableMouse) {
  305. startEvents.push({
  306. name: 'mousedown',
  307. handler: this.start.bind(this),
  308. });
  309. moveEvents.push({
  310. name: 'mousemove',
  311. handler: this.move.bind(this),
  312. });
  313. endEvents.push({
  314. name: 'mouseup',
  315. handler: this.end.bind(this),
  316. });
  317. }
  318. if (!disableTouch) {
  319. startEvents.push({
  320. name: 'touchstart',
  321. handler: this.start.bind(this),
  322. });
  323. moveEvents.push({
  324. name: 'touchmove',
  325. handler: this.move.bind(this),
  326. });
  327. endEvents.push({
  328. name: 'touchend',
  329. handler: this.end.bind(this),
  330. }, {
  331. name: 'touchcancel',
  332. handler: this.end.bind(this),
  333. });
  334. }
  335. this.startEventRegister = new EventRegister(this.indicatorEl, startEvents);
  336. this.moveEventRegister = new EventRegister(window, moveEvents);
  337. this.endEventRegister = new EventRegister(window, endEvents);
  338. };
  339. Indicator.prototype.refresh = function () {
  340. var _a = this.scroll, x = _a.x, y = _a.y, hasHorizontalScroll = _a.hasHorizontalScroll, hasVerticalScroll = _a.hasVerticalScroll, maxBScrollX = _a.maxScrollX, maxBScrollY = _a.maxScrollY;
  341. var _b = resolveRatioOption(this.options.ratio), ratioX = _b.ratioX, ratioY = _b.ratioY;
  342. var _c = getClientSize(this.wrapper), wrapperWidth = _c.width, wrapperHeight = _c.height;
  343. var _d = getRect(this.indicatorEl), indicatorWidth = _d.width, indicatorHeight = _d.height;
  344. if (hasHorizontalScroll) {
  345. this.maxScrollX = wrapperWidth - indicatorWidth;
  346. this.translateXSign =
  347. this.maxScrollX > 0 ? -1 /* Positive */ : 1 /* NotPositive */;
  348. this.minScrollX = 0;
  349. // ensure positive
  350. this.ratioX = ratioX ? ratioX : Math.abs(this.maxScrollX / maxBScrollX);
  351. }
  352. if (hasVerticalScroll) {
  353. this.maxScrollY = wrapperHeight - indicatorHeight;
  354. this.translateYSign =
  355. this.maxScrollY > 0 ? -1 /* Positive */ : 1 /* NotPositive */;
  356. this.minScrollY = 0;
  357. this.ratioY = ratioY ? ratioY : Math.abs(this.maxScrollY / maxBScrollY);
  358. }
  359. this.updatePosition({
  360. x: x,
  361. y: y,
  362. });
  363. };
  364. Indicator.prototype.start = function (e) {
  365. if (this.BScrollIsDisabled()) {
  366. return;
  367. }
  368. var point = (e.touches ? e.touches[0] : e);
  369. handleBubbleAndCancelable(e);
  370. this.initiated = true;
  371. this.moved = false;
  372. this.lastPointX = point.pageX;
  373. this.lastPointY = point.pageY;
  374. this.startTime = getNow();
  375. this.scroll.scroller.hooks.trigger(this.scroll.scroller.hooks.eventTypes.beforeScrollStart);
  376. };
  377. Indicator.prototype.BScrollIsDisabled = function () {
  378. return !this.scroll.enabled;
  379. };
  380. Indicator.prototype.move = function (e) {
  381. if (!this.initiated) {
  382. return;
  383. }
  384. var point = (e.touches ? e.touches[0] : e);
  385. var pointX = point.pageX;
  386. var pointY = point.pageY;
  387. handleBubbleAndCancelable(e);
  388. var deltaX = pointX - this.lastPointX;
  389. var deltaY = pointY - this.lastPointY;
  390. this.lastPointX = pointX;
  391. this.lastPointY = pointY;
  392. if (!this.moved && !this.indicatorNotMoved(deltaX, deltaY)) {
  393. this.moved = true;
  394. this.scroll.scroller.hooks.trigger(this.scroll.scroller.hooks.eventTypes.scrollStart);
  395. }
  396. if (this.moved) {
  397. var newPos = this.getBScrollPosByRatio(this.currentPos, deltaX, deltaY);
  398. this.syncBScroll(newPos);
  399. }
  400. };
  401. Indicator.prototype.end = function (e) {
  402. if (!this.initiated) {
  403. return;
  404. }
  405. this.initiated = false;
  406. handleBubbleAndCancelable(e);
  407. if (this.moved) {
  408. var _a = this.scroll, x = _a.x, y = _a.y;
  409. this.scroll.scroller.hooks.trigger(this.scroll.scroller.hooks.eventTypes.scrollEnd, {
  410. x: x,
  411. y: y,
  412. });
  413. }
  414. };
  415. Indicator.prototype.getBScrollPosByRatio = function (currentPos, deltaX, deltaY) {
  416. var currentX = currentPos.x, currentY = currentPos.y;
  417. var _a = this.scroll, hasHorizontalScroll = _a.hasHorizontalScroll, hasVerticalScroll = _a.hasVerticalScroll, BScrollMinScrollX = _a.minScrollX, BScrollMaxScrollX = _a.maxScrollX, BScrollMinScrollY = _a.minScrollY, BScrollMaxScrollY = _a.maxScrollY;
  418. var _b = this.scroll, x = _b.x, y = _b.y;
  419. if (hasHorizontalScroll) {
  420. var newPosX = between(currentX + deltaX, Math.min(this.minScrollX, this.maxScrollX), Math.max(this.minScrollX, this.maxScrollX));
  421. var roundX = Math.round((newPosX / this.ratioX) * this.translateXSign);
  422. x = between(roundX, BScrollMaxScrollX, BScrollMinScrollX);
  423. }
  424. if (hasVerticalScroll) {
  425. var newPosY = between(currentY + deltaY, Math.min(this.minScrollY, this.maxScrollY), Math.max(this.minScrollY, this.maxScrollY));
  426. var roundY = Math.round((newPosY / this.ratioY) * this.translateYSign);
  427. y = between(roundY, BScrollMaxScrollY, BScrollMinScrollY);
  428. }
  429. return { x: x, y: y };
  430. };
  431. Indicator.prototype.indicatorNotMoved = function (deltaX, deltaY) {
  432. var _a = this.currentPos, x = _a.x, y = _a.y;
  433. var xNotMoved = (x === this.minScrollX && deltaX <= 0) ||
  434. (x === this.maxScrollX && deltaX >= 0);
  435. var yNotMoved = (y === this.minScrollY && deltaY <= 0) ||
  436. (y === this.maxScrollY && deltaY >= 0);
  437. return xNotMoved && yNotMoved;
  438. };
  439. Indicator.prototype.syncBScroll = function (newPos) {
  440. var timestamp = getNow();
  441. var _a = this.scroll, options = _a.options, scroller = _a.scroller;
  442. var probeType = options.probeType, momentumLimitTime = options.momentumLimitTime;
  443. scroller.translater.translate(newPos);
  444. // dispatch scroll in interval time
  445. if (timestamp - this.startTime > momentumLimitTime) {
  446. this.startTime = timestamp;
  447. if (probeType === 1 /* Throttle */) {
  448. scroller.hooks.trigger(scroller.hooks.eventTypes.scroll, newPos);
  449. }
  450. }
  451. // dispatch scroll all the time
  452. if (probeType > 1 /* Throttle */) {
  453. scroller.hooks.trigger(scroller.hooks.eventTypes.scroll, newPos);
  454. }
  455. };
  456. Indicator.prototype.updatePosition = function (BScrollPos) {
  457. var newIndicatorPos = this.getIndicatorPosByRatio(BScrollPos);
  458. this.applyTransformProperty(newIndicatorPos);
  459. this.currentPos = __assign({}, newIndicatorPos);
  460. };
  461. Indicator.prototype.applyTransformProperty = function (pos) {
  462. var translateZ = this.scroll.options.translateZ;
  463. var transformProperties = [
  464. "translateX(" + pos.x + "px)",
  465. "translateY(" + pos.y + "px)",
  466. "" + translateZ,
  467. ];
  468. this.indicatorEl.style[style.transform] = transformProperties.join(' ');
  469. };
  470. Indicator.prototype.getIndicatorPosByRatio = function (BScrollPos) {
  471. var x = BScrollPos.x, y = BScrollPos.y;
  472. var _a = this.scroll, hasHorizontalScroll = _a.hasHorizontalScroll, hasVerticalScroll = _a.hasVerticalScroll;
  473. var position = __assign({}, this.currentPos);
  474. if (hasHorizontalScroll) {
  475. var roundX = Math.round(this.ratioX * x * this.translateXSign);
  476. // maybe maxScrollX is negative
  477. position.x = between(roundX, Math.min(this.minScrollX, this.maxScrollX), Math.max(this.minScrollX, this.maxScrollX));
  478. }
  479. if (hasVerticalScroll) {
  480. var roundY = Math.round(this.ratioY * y * this.translateYSign);
  481. // maybe maxScrollY is negative
  482. position.y = between(roundY, Math.min(this.minScrollY, this.maxScrollY), Math.max(this.minScrollY, this.maxScrollY));
  483. }
  484. return position;
  485. };
  486. Indicator.prototype.destroy = function () {
  487. if (this.options.interactive !== false) {
  488. this.startEventRegister.destroy();
  489. this.moveEventRegister.destroy();
  490. this.endEventRegister.destroy();
  491. }
  492. this.hooksFn.forEach(function (item) {
  493. var hooks = item[0];
  494. var hooksName = item[1];
  495. var handlerFn = item[2];
  496. hooks.off(hooksName, handlerFn);
  497. });
  498. this.hooksFn.length = 0;
  499. };
  500. return Indicator;
  501. }());
  502. var Indicators = /** @class */ (function () {
  503. function Indicators(scroll) {
  504. this.scroll = scroll;
  505. this.options = [];
  506. this.indicators = [];
  507. this.handleOptions();
  508. this.handleHooks();
  509. }
  510. Indicators.prototype.handleOptions = function () {
  511. var UserIndicatorsOptions = this.scroll.options.indicators;
  512. assert(Array.isArray(UserIndicatorsOptions), "'indicators' must be an array.");
  513. for (var _i = 0, UserIndicatorsOptions_1 = UserIndicatorsOptions; _i < UserIndicatorsOptions_1.length; _i++) {
  514. var indicatorOptions = UserIndicatorsOptions_1[_i];
  515. assert(!!indicatorOptions.relationElement, "'relationElement' must be a HTMLElement.");
  516. this.createIndicators(indicatorOptions);
  517. }
  518. };
  519. Indicators.prototype.createIndicators = function (options) {
  520. this.indicators.push(new Indicator(this.scroll, options));
  521. };
  522. Indicators.prototype.handleHooks = function () {
  523. var _this = this;
  524. var scrollHooks = this.scroll.hooks;
  525. scrollHooks.on(scrollHooks.eventTypes.destroy, function () {
  526. for (var _i = 0, _a = _this.indicators; _i < _a.length; _i++) {
  527. var indicator = _a[_i];
  528. indicator.destroy();
  529. }
  530. _this.indicators = [];
  531. });
  532. };
  533. Indicators.pluginName = 'indicators';
  534. return Indicators;
  535. }());
  536. return Indicators;
  537. })));