You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
15 KiB
395 lines
15 KiB
/*!
|
|
* better-scroll / wheel
|
|
* (c) 2016-2020 ustbhuangyi
|
|
* Released under the MIT License.
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Wheel = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
// ssr support
|
|
var inBrowser = typeof window !== 'undefined';
|
|
var ua = inBrowser && navigator.userAgent.toLowerCase();
|
|
var isWeChatDevTools = !!(ua && /wechatdevtools/.test(ua));
|
|
var isAndroid = ua && ua.indexOf('android') > 0;
|
|
/* istanbul ignore next */
|
|
var isIOSBadVersion = (function () {
|
|
if (typeof ua === 'string') {
|
|
var regex = /os (\d\d?_\d(_\d)?)/;
|
|
var matches = regex.exec(ua);
|
|
if (!matches)
|
|
return false;
|
|
var parts = matches[1].split('_').map(function (item) {
|
|
return parseInt(item, 10);
|
|
});
|
|
// ios version >= 13.4 issue 982
|
|
return !!(parts[0] >= 13 && parts[1] >= 4);
|
|
}
|
|
return false;
|
|
})();
|
|
|
|
var extend = function (target, source) {
|
|
for (var key in source) {
|
|
target[key] = source[key];
|
|
}
|
|
return target;
|
|
};
|
|
|
|
var elementStyle = (inBrowser &&
|
|
document.createElement('div').style);
|
|
var vendor = (function () {
|
|
/* istanbul ignore if */
|
|
if (!inBrowser) {
|
|
return false;
|
|
}
|
|
var transformNames = [
|
|
{
|
|
key: 'standard',
|
|
value: 'transform',
|
|
},
|
|
{
|
|
key: 'webkit',
|
|
value: 'webkitTransform',
|
|
},
|
|
{
|
|
key: 'Moz',
|
|
value: 'MozTransform',
|
|
},
|
|
{
|
|
key: 'O',
|
|
value: 'OTransform',
|
|
},
|
|
{
|
|
key: 'ms',
|
|
value: 'msTransform',
|
|
},
|
|
];
|
|
for (var _i = 0, transformNames_1 = transformNames; _i < transformNames_1.length; _i++) {
|
|
var obj = transformNames_1[_i];
|
|
if (elementStyle[obj.value] !== undefined) {
|
|
return obj.key;
|
|
}
|
|
}
|
|
/* istanbul ignore next */
|
|
return false;
|
|
})();
|
|
/* istanbul ignore next */
|
|
function prefixStyle(style) {
|
|
if (vendor === false) {
|
|
return style;
|
|
}
|
|
if (vendor === 'standard') {
|
|
if (style === 'transitionEnd') {
|
|
return 'transitionend';
|
|
}
|
|
return style;
|
|
}
|
|
return vendor + style.charAt(0).toUpperCase() + style.substr(1);
|
|
}
|
|
var cssVendor = vendor && vendor !== 'standard' ? '-' + vendor.toLowerCase() + '-' : '';
|
|
var transform = prefixStyle('transform');
|
|
var transition = prefixStyle('transition');
|
|
var hasPerspective = inBrowser && prefixStyle('perspective') in elementStyle;
|
|
var style = {
|
|
transform: transform,
|
|
transition: transition,
|
|
transitionTimingFunction: prefixStyle('transitionTimingFunction'),
|
|
transitionDuration: prefixStyle('transitionDuration'),
|
|
transitionDelay: prefixStyle('transitionDelay'),
|
|
transformOrigin: prefixStyle('transformOrigin'),
|
|
transitionEnd: prefixStyle('transitionEnd'),
|
|
transitionProperty: prefixStyle('transitionProperty'),
|
|
};
|
|
function hasClass(el, className) {
|
|
var reg = new RegExp('(^|\\s)' + className + '(\\s|$)');
|
|
return reg.test(el.className);
|
|
}
|
|
|
|
var ease = {
|
|
// easeOutQuint
|
|
swipe: {
|
|
style: 'cubic-bezier(0.23, 1, 0.32, 1)',
|
|
fn: function (t) {
|
|
return 1 + --t * t * t * t * t;
|
|
}
|
|
},
|
|
// easeOutQuard
|
|
swipeBounce: {
|
|
style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
|
fn: function (t) {
|
|
return t * (2 - t);
|
|
}
|
|
},
|
|
// easeOutQuart
|
|
bounce: {
|
|
style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
|
|
fn: function (t) {
|
|
return 1 - --t * t * t * t;
|
|
}
|
|
}
|
|
};
|
|
|
|
var sourcePrefix = 'plugins.wheel';
|
|
var propertiesMap = [
|
|
{
|
|
key: 'wheelTo',
|
|
name: 'wheelTo'
|
|
},
|
|
{
|
|
key: 'getSelectedIndex',
|
|
name: 'getSelectedIndex'
|
|
}
|
|
];
|
|
var propertiesConfig = propertiesMap.map(function (item) {
|
|
return {
|
|
key: item.key,
|
|
sourceKey: sourcePrefix + "." + item.name
|
|
};
|
|
});
|
|
|
|
var CONSTANTS = {
|
|
rate: 4,
|
|
};
|
|
var Wheel = /** @class */ (function () {
|
|
function Wheel(scroll) {
|
|
this.scroll = scroll;
|
|
this.init();
|
|
}
|
|
Wheel.prototype.init = function () {
|
|
this.handleBScroll();
|
|
this.handleOptions();
|
|
this.handleHooks();
|
|
// init boundary for Wheel
|
|
this.refreshBoundary();
|
|
this.handleSelectedIndex();
|
|
};
|
|
Wheel.prototype.handleBScroll = function () {
|
|
this.scroll.proxy(propertiesConfig);
|
|
};
|
|
Wheel.prototype.handleOptions = function () {
|
|
var userOptions = (this.scroll.options.wheel === true
|
|
? {}
|
|
: this.scroll.options.wheel);
|
|
var defaultOptions = {
|
|
wheelWrapperClass: 'wheel-scroll',
|
|
wheelItemClass: 'wheel-item',
|
|
rotate: 25,
|
|
adjustTime: 400,
|
|
selectedIndex: 0,
|
|
wheelDisabledItemClass: 'wheel-disabled-item',
|
|
};
|
|
this.options = extend(defaultOptions, userOptions);
|
|
};
|
|
Wheel.prototype.handleHooks = function () {
|
|
var _this = this;
|
|
var scroller = this.scroll.scroller;
|
|
var actionsHandler = scroller.actionsHandler, scrollBehaviorX = scroller.scrollBehaviorX, scrollBehaviorY = scroller.scrollBehaviorY, animater = scroller.animater;
|
|
var prevContent = scroller.content;
|
|
// BScroll
|
|
this.scroll.hooks.on(this.scroll.hooks.eventTypes.refresh, function (content) {
|
|
if (content !== prevContent) {
|
|
prevContent = content;
|
|
_this.handleSelectedIndex();
|
|
}
|
|
// check we are stop at a disable item
|
|
_this.wheelTo(_this.selectedIndex, 0);
|
|
});
|
|
this.scroll.hooks.on(this.scroll.hooks.eventTypes.beforeInitialScrollTo, function (position) {
|
|
// selectedIndex has a better priority than bs.options.startY
|
|
position.x = 0;
|
|
position.y = -(_this.selectedIndex * _this.itemHeight);
|
|
});
|
|
// Scroller
|
|
scroller.hooks.on(scroller.hooks.eventTypes.checkClick, function () {
|
|
var index = Array.prototype.slice
|
|
.call(_this.items, 0)
|
|
.indexOf(_this.target);
|
|
if (index === -1)
|
|
return true;
|
|
_this.wheelToAfterClick(index, _this.options.adjustTime, ease.swipe);
|
|
return true;
|
|
});
|
|
scroller.hooks.on(scroller.hooks.eventTypes.scrollTo, function (endPoint) {
|
|
endPoint.y = _this.findNearestValidWheel(endPoint.y).y;
|
|
});
|
|
scroller.hooks.on(scroller.hooks.eventTypes.scrollToElement, function (el, pos) {
|
|
if (!hasClass(el, _this.options.wheelItemClass)) {
|
|
return true;
|
|
}
|
|
else {
|
|
pos.top = _this.findNearestValidWheel(pos.top).y;
|
|
}
|
|
});
|
|
// ActionsHandler
|
|
actionsHandler.hooks.on(actionsHandler.hooks.eventTypes.beforeStart, function (e) {
|
|
_this.target = e.target;
|
|
});
|
|
// ScrollBehaviorX
|
|
// Wheel has no x direction now
|
|
scrollBehaviorX.hooks.on(scrollBehaviorX.hooks.eventTypes.computeBoundary, function (boundary) {
|
|
boundary.maxScrollPos = 0;
|
|
boundary.minScrollPos = 0;
|
|
});
|
|
// ScrollBehaviorY
|
|
scrollBehaviorY.hooks.on(scrollBehaviorY.hooks.eventTypes.computeBoundary, function (boundary) {
|
|
_this.items = _this.scroll.scroller.content.children;
|
|
_this.checkWheelAllDisabled();
|
|
_this.itemHeight =
|
|
_this.items.length > 0
|
|
? scrollBehaviorY.contentSize / _this.items.length
|
|
: 0;
|
|
boundary.maxScrollPos = -_this.itemHeight * (_this.items.length - 1);
|
|
boundary.minScrollPos = 0;
|
|
});
|
|
scrollBehaviorY.hooks.on(scrollBehaviorY.hooks.eventTypes.momentum, function (momentumInfo, distance) {
|
|
momentumInfo.rate = CONSTANTS.rate;
|
|
momentumInfo.destination = _this.findNearestValidWheel(momentumInfo.destination).y;
|
|
// TODO algorithm optimize
|
|
var maxDistance = 1000;
|
|
var minDuration = 800;
|
|
if (distance < maxDistance) {
|
|
momentumInfo.duration = Math.max(minDuration, (distance / maxDistance) * _this.scroll.options.swipeTime);
|
|
}
|
|
});
|
|
scrollBehaviorY.hooks.on(scrollBehaviorY.hooks.eventTypes.end, function (momentumInfo) {
|
|
var validWheel = _this.findNearestValidWheel(scrollBehaviorY.currentPos);
|
|
momentumInfo.destination = validWheel.y;
|
|
momentumInfo.duration = _this.options.adjustTime;
|
|
_this.selectedIndex = validWheel.index;
|
|
});
|
|
// Animater
|
|
animater.hooks.on(animater.hooks.eventTypes.time, function (time) {
|
|
_this.transitionDuration(time);
|
|
});
|
|
animater.hooks.on(animater.hooks.eventTypes.timeFunction, function (easing) {
|
|
_this.timeFunction(easing);
|
|
});
|
|
animater.hooks.on(animater.hooks.eventTypes.beforeForceStop, function (_a) {
|
|
var y = _a.y;
|
|
_this.target = _this.items[_this.findNearestValidWheel(y).index];
|
|
// don't dispatch scrollEnd when forceStop from transition or animation
|
|
return true;
|
|
});
|
|
// bs.stop() to make wheel stop at a correct position
|
|
animater.hooks.on(animater.hooks.eventTypes.callStop, function () {
|
|
var index = Array.prototype.slice
|
|
.call(_this.items, 0)
|
|
.indexOf(_this.target);
|
|
if (index > 0) {
|
|
var y = -(index * _this.itemHeight);
|
|
animater.translate({ x: 0, y: y });
|
|
}
|
|
});
|
|
// Translater
|
|
animater.translater.hooks.on(animater.translater.hooks.eventTypes.translate, function (endPoint) {
|
|
_this.rotateX(endPoint.y);
|
|
_this.selectedIndex = _this.findNearestValidWheel(endPoint.y).index;
|
|
});
|
|
};
|
|
Wheel.prototype.refreshBoundary = function () {
|
|
var _a = this.scroll.scroller, scrollBehaviorX = _a.scrollBehaviorX, scrollBehaviorY = _a.scrollBehaviorY, content = _a.content;
|
|
scrollBehaviorX.refresh(content);
|
|
scrollBehaviorY.refresh(content);
|
|
};
|
|
Wheel.prototype.handleSelectedIndex = function () {
|
|
this.selectedIndex = this.options.selectedIndex;
|
|
};
|
|
Wheel.prototype.getSelectedIndex = function () {
|
|
return this.selectedIndex;
|
|
};
|
|
Wheel.prototype.wheelTo = function (index, time, ease) {
|
|
if (index === void 0) { index = 0; }
|
|
if (time === void 0) { time = 0; }
|
|
var y = -index * this.itemHeight;
|
|
var currentY = Math.round(this.scroll.y);
|
|
this.scroll.scrollTo(0, y, time, ease);
|
|
return y === currentY;
|
|
};
|
|
Wheel.prototype.wheelToAfterClick = function (index, time, ease) {
|
|
if (index === void 0) { index = 0; }
|
|
if (time === void 0) { time = 0; }
|
|
var needDispatchScrollEnd = this.wheelTo(index, time, ease);
|
|
// startpoint === endpoint
|
|
// manually trigger scrollEnd
|
|
if (needDispatchScrollEnd) {
|
|
var hooks = this.scroll.scroller.hooks;
|
|
hooks.trigger(hooks.eventTypes.scrollEnd);
|
|
}
|
|
};
|
|
Wheel.prototype.transitionDuration = function (time) {
|
|
for (var i = 0; i < this.items.length; i++) {
|
|
this.items[i].style[style.transitionDuration] =
|
|
time + 'ms';
|
|
}
|
|
};
|
|
Wheel.prototype.timeFunction = function (easing) {
|
|
for (var i = 0; i < this.items.length; i++) {
|
|
this.items[i].style[style.transitionTimingFunction] = easing;
|
|
}
|
|
};
|
|
Wheel.prototype.rotateX = function (y) {
|
|
var _a = this.options.rotate, rotate = _a === void 0 ? 25 : _a;
|
|
for (var i = 0; i < this.items.length; i++) {
|
|
var deg = rotate * (y / this.itemHeight + i);
|
|
// Too small value is invalid in some phones, issue 1026
|
|
var SafeDeg = deg.toFixed(3);
|
|
this.items[i].style[style.transform] = "rotateX(" + SafeDeg + "deg)";
|
|
}
|
|
};
|
|
Wheel.prototype.findNearestValidWheel = function (y) {
|
|
y = y > 0 ? 0 : y < this.scroll.maxScrollY ? this.scroll.maxScrollY : y;
|
|
var currentIndex = Math.abs(Math.round(-y / this.itemHeight));
|
|
var cacheIndex = currentIndex;
|
|
var items = this.items;
|
|
var wheelDisabledItemClassName = this.options
|
|
.wheelDisabledItemClass;
|
|
// Impersonation web native select
|
|
// first, check whether there is a enable item whose index is smaller than currentIndex
|
|
// then, check whether there is a enable item whose index is bigger than currentIndex
|
|
// otherwise, there are all disabled items, just keep currentIndex unchange
|
|
while (currentIndex >= 0) {
|
|
if (!hasClass(items[currentIndex], wheelDisabledItemClassName)) {
|
|
break;
|
|
}
|
|
currentIndex--;
|
|
}
|
|
if (currentIndex < 0) {
|
|
currentIndex = cacheIndex;
|
|
while (currentIndex <= items.length - 1) {
|
|
if (!hasClass(items[currentIndex], wheelDisabledItemClassName)) {
|
|
break;
|
|
}
|
|
currentIndex++;
|
|
}
|
|
}
|
|
// keep it unchange when all the items are disabled
|
|
if (currentIndex === items.length) {
|
|
currentIndex = cacheIndex;
|
|
}
|
|
// when all the items are disabled, this.selectedIndex should always be -1
|
|
return {
|
|
index: this.wheelItemsAllDisabled ? -1 : currentIndex,
|
|
y: -currentIndex * this.itemHeight,
|
|
};
|
|
};
|
|
Wheel.prototype.checkWheelAllDisabled = function () {
|
|
var wheelDisabledItemClassName = this.options.wheelDisabledItemClass;
|
|
var items = this.items;
|
|
this.wheelItemsAllDisabled = true;
|
|
for (var i = 0; i < items.length; i++) {
|
|
if (!hasClass(items[i], wheelDisabledItemClassName)) {
|
|
this.wheelItemsAllDisabled = false;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
Wheel.pluginName = 'wheel';
|
|
return Wheel;
|
|
}());
|
|
|
|
return Wheel;
|
|
|
|
})));
|