wheel.esm.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*!
  2. * better-scroll / wheel
  3. * (c) 2016-2021 ustbhuangyi
  4. * Released under the MIT License.
  5. */
  6. // ssr support
  7. var inBrowser = typeof window !== 'undefined';
  8. var ua = inBrowser && navigator.userAgent.toLowerCase();
  9. !!(ua && /wechatdevtools/.test(ua));
  10. ua && ua.indexOf('android') > 0;
  11. /* istanbul ignore next */
  12. ((function () {
  13. if (typeof ua === 'string') {
  14. var regex = /os (\d\d?_\d(_\d)?)/;
  15. var matches = regex.exec(ua);
  16. if (!matches)
  17. return false;
  18. var parts = matches[1].split('_').map(function (item) {
  19. return parseInt(item, 10);
  20. });
  21. // ios version >= 13.4 issue 982
  22. return !!(parts[0] === 13 && parts[1] >= 4);
  23. }
  24. return false;
  25. }))();
  26. /* istanbul ignore next */
  27. var supportsPassive = false;
  28. /* istanbul ignore next */
  29. if (inBrowser) {
  30. var EventName = 'test-passive';
  31. try {
  32. var opts = {};
  33. Object.defineProperty(opts, 'passive', {
  34. get: function () {
  35. supportsPassive = true;
  36. },
  37. }); // https://github.com/facebook/flow/issues/285
  38. window.addEventListener(EventName, function () { }, opts);
  39. }
  40. catch (e) { }
  41. }
  42. var extend = function (target, source) {
  43. for (var key in source) {
  44. target[key] = source[key];
  45. }
  46. return target;
  47. };
  48. var elementStyle = (inBrowser &&
  49. document.createElement('div').style);
  50. var vendor = (function () {
  51. /* istanbul ignore if */
  52. if (!inBrowser) {
  53. return false;
  54. }
  55. var transformNames = [
  56. {
  57. key: 'standard',
  58. value: 'transform',
  59. },
  60. {
  61. key: 'webkit',
  62. value: 'webkitTransform',
  63. },
  64. {
  65. key: 'Moz',
  66. value: 'MozTransform',
  67. },
  68. {
  69. key: 'O',
  70. value: 'OTransform',
  71. },
  72. {
  73. key: 'ms',
  74. value: 'msTransform',
  75. },
  76. ];
  77. for (var _i = 0, transformNames_1 = transformNames; _i < transformNames_1.length; _i++) {
  78. var obj = transformNames_1[_i];
  79. if (elementStyle[obj.value] !== undefined) {
  80. return obj.key;
  81. }
  82. }
  83. /* istanbul ignore next */
  84. return false;
  85. })();
  86. /* istanbul ignore next */
  87. function prefixStyle(style) {
  88. if (vendor === false) {
  89. return style;
  90. }
  91. if (vendor === 'standard') {
  92. if (style === 'transitionEnd') {
  93. return 'transitionend';
  94. }
  95. return style;
  96. }
  97. return vendor + style.charAt(0).toUpperCase() + style.substr(1);
  98. }
  99. vendor && vendor !== 'standard' ? '-' + vendor.toLowerCase() + '-' : '';
  100. var transform = prefixStyle('transform');
  101. var transition = prefixStyle('transition');
  102. inBrowser && prefixStyle('perspective') in elementStyle;
  103. var style = {
  104. transform: transform,
  105. transition: transition,
  106. transitionTimingFunction: prefixStyle('transitionTimingFunction'),
  107. transitionDuration: prefixStyle('transitionDuration'),
  108. transitionDelay: prefixStyle('transitionDelay'),
  109. transformOrigin: prefixStyle('transformOrigin'),
  110. transitionEnd: prefixStyle('transitionEnd'),
  111. transitionProperty: prefixStyle('transitionProperty'),
  112. };
  113. function hasClass(el, className) {
  114. var reg = new RegExp('(^|\\s)' + className + '(\\s|$)');
  115. return reg.test(el.className);
  116. }
  117. function HTMLCollectionToArray(el) {
  118. return Array.prototype.slice.call(el, 0);
  119. }
  120. var ease = {
  121. // easeOutQuint
  122. swipe: {
  123. style: 'cubic-bezier(0.23, 1, 0.32, 1)',
  124. fn: function (t) {
  125. return 1 + --t * t * t * t * t;
  126. }
  127. },
  128. // easeOutQuard
  129. swipeBounce: {
  130. style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
  131. fn: function (t) {
  132. return t * (2 - t);
  133. }
  134. },
  135. // easeOutQuart
  136. bounce: {
  137. style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
  138. fn: function (t) {
  139. return 1 - --t * t * t * t;
  140. }
  141. }
  142. };
  143. var sourcePrefix = 'plugins.wheel';
  144. var propertiesMap = [
  145. {
  146. key: 'wheelTo',
  147. name: 'wheelTo',
  148. },
  149. {
  150. key: 'getSelectedIndex',
  151. name: 'getSelectedIndex',
  152. },
  153. {
  154. key: 'restorePosition',
  155. name: 'restorePosition',
  156. },
  157. ];
  158. var propertiesConfig = propertiesMap.map(function (item) {
  159. return {
  160. key: item.key,
  161. sourceKey: sourcePrefix + "." + item.name,
  162. };
  163. });
  164. var WHEEL_INDEX_CHANGED_EVENT_NAME = 'wheelIndexChanged';
  165. var CONSTANTS = {
  166. rate: 4
  167. };
  168. var Wheel = /** @class */ (function () {
  169. function Wheel(scroll) {
  170. this.scroll = scroll;
  171. this.init();
  172. }
  173. Wheel.prototype.init = function () {
  174. this.handleBScroll();
  175. this.handleOptions();
  176. this.handleHooks();
  177. // init boundary for Wheel
  178. this.refreshBoundary();
  179. this.setSelectedIndex(this.options.selectedIndex);
  180. };
  181. Wheel.prototype.handleBScroll = function () {
  182. this.scroll.proxy(propertiesConfig);
  183. this.scroll.registerType([WHEEL_INDEX_CHANGED_EVENT_NAME]);
  184. };
  185. Wheel.prototype.handleOptions = function () {
  186. var userOptions = (this.scroll.options.wheel === true
  187. ? {}
  188. : this.scroll.options.wheel);
  189. var defaultOptions = {
  190. wheelWrapperClass: 'wheel-scroll',
  191. wheelItemClass: 'wheel-item',
  192. rotate: 25,
  193. adjustTime: 400,
  194. selectedIndex: 0,
  195. wheelDisabledItemClass: 'wheel-disabled-item'
  196. };
  197. this.options = extend(defaultOptions, userOptions);
  198. };
  199. Wheel.prototype.handleHooks = function () {
  200. var _this = this;
  201. var scroll = this.scroll;
  202. var scroller = this.scroll.scroller;
  203. var actionsHandler = scroller.actionsHandler, scrollBehaviorX = scroller.scrollBehaviorX, scrollBehaviorY = scroller.scrollBehaviorY, animater = scroller.animater;
  204. var prevContent = scroller.content;
  205. // BScroll
  206. scroll.on(scroll.eventTypes.scrollEnd, function (position) {
  207. var index = _this.findNearestValidWheel(position.y).index;
  208. if (scroller.animater.forceStopped && !_this.isAdjustingPosition) {
  209. _this.target = _this.items[index];
  210. // since stopped from an animation.
  211. // prevent user's scrollEnd callback triggered twice
  212. return true;
  213. }
  214. else {
  215. _this.setSelectedIndex(index);
  216. if (_this.isAdjustingPosition) {
  217. _this.isAdjustingPosition = false;
  218. }
  219. }
  220. });
  221. // BScroll.hooks
  222. this.scroll.hooks.on(this.scroll.hooks.eventTypes.refresh, function (content) {
  223. if (content !== prevContent) {
  224. prevContent = content;
  225. _this.setSelectedIndex(_this.options.selectedIndex, true);
  226. }
  227. // rotate all wheel-items
  228. // because position may not change
  229. _this.rotateX(_this.scroll.y);
  230. // check we are stop at a disable item or not
  231. _this.wheelTo(_this.selectedIndex, 0);
  232. });
  233. this.scroll.hooks.on(this.scroll.hooks.eventTypes.beforeInitialScrollTo, function (position) {
  234. // selectedIndex has higher priority than bs.options.startY
  235. position.x = 0;
  236. position.y = -(_this.selectedIndex * _this.itemHeight);
  237. });
  238. // Scroller
  239. scroller.hooks.on(scroller.hooks.eventTypes.checkClick, function () {
  240. var index = HTMLCollectionToArray(_this.items).indexOf(_this.target);
  241. if (index === -1)
  242. return true;
  243. _this.wheelTo(index, _this.options.adjustTime, ease.swipe);
  244. return true;
  245. });
  246. scroller.hooks.on(scroller.hooks.eventTypes.scrollTo, function (endPoint) {
  247. endPoint.y = _this.findNearestValidWheel(endPoint.y).y;
  248. });
  249. // when content is scrolling
  250. // click wheel-item DOM repeatedly and crazily will cause scrollEnd not triggered
  251. // so reset forceStopped
  252. scroller.hooks.on(scroller.hooks.eventTypes.minDistanceScroll, function () {
  253. var animater = scroller.animater;
  254. if (animater.forceStopped === true) {
  255. animater.forceStopped = false;
  256. }
  257. });
  258. scroller.hooks.on(scroller.hooks.eventTypes.scrollToElement, function (el, pos) {
  259. if (!hasClass(el, _this.options.wheelItemClass)) {
  260. return true;
  261. }
  262. else {
  263. pos.top = _this.findNearestValidWheel(pos.top).y;
  264. }
  265. });
  266. // ActionsHandler
  267. actionsHandler.hooks.on(actionsHandler.hooks.eventTypes.beforeStart, function (e) {
  268. _this.target = e.target;
  269. });
  270. // ScrollBehaviorX
  271. // Wheel has no x direction now
  272. scrollBehaviorX.hooks.on(scrollBehaviorX.hooks.eventTypes.computeBoundary, function (boundary) {
  273. boundary.maxScrollPos = 0;
  274. boundary.minScrollPos = 0;
  275. });
  276. // ScrollBehaviorY
  277. scrollBehaviorY.hooks.on(scrollBehaviorY.hooks.eventTypes.computeBoundary, function (boundary) {
  278. _this.items = _this.scroll.scroller.content.children;
  279. _this.checkWheelAllDisabled();
  280. _this.itemHeight =
  281. _this.items.length > 0
  282. ? scrollBehaviorY.contentSize / _this.items.length
  283. : 0;
  284. boundary.maxScrollPos = -_this.itemHeight * (_this.items.length - 1);
  285. boundary.minScrollPos = 0;
  286. });
  287. scrollBehaviorY.hooks.on(scrollBehaviorY.hooks.eventTypes.momentum, function (momentumInfo) {
  288. momentumInfo.rate = CONSTANTS.rate;
  289. momentumInfo.destination = _this.findNearestValidWheel(momentumInfo.destination).y;
  290. });
  291. scrollBehaviorY.hooks.on(scrollBehaviorY.hooks.eventTypes.end, function (momentumInfo) {
  292. var validWheel = _this.findNearestValidWheel(scrollBehaviorY.currentPos);
  293. momentumInfo.destination = validWheel.y;
  294. momentumInfo.duration = _this.options.adjustTime;
  295. });
  296. // Animater
  297. animater.hooks.on(animater.hooks.eventTypes.time, function (time) {
  298. _this.transitionDuration(time);
  299. });
  300. animater.hooks.on(animater.hooks.eventTypes.timeFunction, function (easing) {
  301. _this.timeFunction(easing);
  302. });
  303. // bs.stop() to make wheel stop at a correct position when pending
  304. animater.hooks.on(animater.hooks.eventTypes.callStop, function () {
  305. var index = _this.findNearestValidWheel(_this.scroll.y).index;
  306. _this.isAdjustingPosition = true;
  307. _this.wheelTo(index, 0);
  308. });
  309. // Translater
  310. animater.translater.hooks.on(animater.translater.hooks.eventTypes.translate, function (endPoint) {
  311. _this.rotateX(endPoint.y);
  312. });
  313. };
  314. Wheel.prototype.refreshBoundary = function () {
  315. var _a = this.scroll.scroller, scrollBehaviorX = _a.scrollBehaviorX, scrollBehaviorY = _a.scrollBehaviorY, content = _a.content;
  316. scrollBehaviorX.refresh(content);
  317. scrollBehaviorY.refresh(content);
  318. };
  319. Wheel.prototype.setSelectedIndex = function (index, contentChanged) {
  320. if (contentChanged === void 0) { contentChanged = false; }
  321. var prevSelectedIndex = this.selectedIndex;
  322. this.selectedIndex = index;
  323. // if content DOM changed, should not trigger event
  324. if (prevSelectedIndex !== index && !contentChanged) {
  325. this.scroll.trigger(WHEEL_INDEX_CHANGED_EVENT_NAME, index);
  326. }
  327. };
  328. Wheel.prototype.getSelectedIndex = function () {
  329. return this.selectedIndex;
  330. };
  331. Wheel.prototype.wheelTo = function (index, time, ease) {
  332. if (index === void 0) { index = 0; }
  333. if (time === void 0) { time = 0; }
  334. var y = -index * this.itemHeight;
  335. this.scroll.scrollTo(0, y, time, ease);
  336. };
  337. Wheel.prototype.restorePosition = function () {
  338. // bs is scrolling
  339. var isPending = this.scroll.pending;
  340. if (isPending) {
  341. var selectedIndex = this.getSelectedIndex();
  342. this.scroll.scroller.animater.clearTimer();
  343. this.wheelTo(selectedIndex, 0);
  344. }
  345. };
  346. Wheel.prototype.transitionDuration = function (time) {
  347. for (var i = 0; i < this.items.length; i++) {
  348. this.items[i].style[style.transitionDuration] =
  349. time + 'ms';
  350. }
  351. };
  352. Wheel.prototype.timeFunction = function (easing) {
  353. for (var i = 0; i < this.items.length; i++) {
  354. this.items[i].style[style.transitionTimingFunction] = easing;
  355. }
  356. };
  357. Wheel.prototype.rotateX = function (y) {
  358. var _a = this.options.rotate, rotate = _a === void 0 ? 25 : _a;
  359. for (var i = 0; i < this.items.length; i++) {
  360. var deg = rotate * (y / this.itemHeight + i);
  361. // Too small value is invalid in some phones, issue 1026
  362. var SafeDeg = deg.toFixed(3);
  363. this.items[i].style[style.transform] = "rotateX(" + SafeDeg + "deg)";
  364. }
  365. };
  366. Wheel.prototype.findNearestValidWheel = function (y) {
  367. y = y > 0 ? 0 : y < this.scroll.maxScrollY ? this.scroll.maxScrollY : y;
  368. var currentIndex = Math.abs(Math.round(-y / this.itemHeight));
  369. var cacheIndex = currentIndex;
  370. var items = this.items;
  371. var wheelDisabledItemClassName = this.options
  372. .wheelDisabledItemClass;
  373. // implement web native select element
  374. // first, check whether there is a enable item whose index is smaller than currentIndex
  375. // then, check whether there is a enable item whose index is bigger than currentIndex
  376. // otherwise, there are all disabled items, just keep currentIndex unchange
  377. while (currentIndex >= 0) {
  378. if (!hasClass(items[currentIndex], wheelDisabledItemClassName)) {
  379. break;
  380. }
  381. currentIndex--;
  382. }
  383. if (currentIndex < 0) {
  384. currentIndex = cacheIndex;
  385. while (currentIndex <= items.length - 1) {
  386. if (!hasClass(items[currentIndex], wheelDisabledItemClassName)) {
  387. break;
  388. }
  389. currentIndex++;
  390. }
  391. }
  392. // keep it unchange when all the items are disabled
  393. if (currentIndex === items.length) {
  394. currentIndex = cacheIndex;
  395. }
  396. // when all the items are disabled, selectedIndex should always be -1
  397. return {
  398. index: this.wheelItemsAllDisabled ? -1 : currentIndex,
  399. y: -currentIndex * this.itemHeight
  400. };
  401. };
  402. Wheel.prototype.checkWheelAllDisabled = function () {
  403. var wheelDisabledItemClassName = this.options.wheelDisabledItemClass;
  404. var items = this.items;
  405. this.wheelItemsAllDisabled = true;
  406. for (var i = 0; i < items.length; i++) {
  407. if (!hasClass(items[i], wheelDisabledItemClassName)) {
  408. this.wheelItemsAllDisabled = false;
  409. break;
  410. }
  411. }
  412. };
  413. Wheel.pluginName = 'wheel';
  414. return Wheel;
  415. }());
  416. export default Wheel;