reeller
Version:
Flexible, powerful and modern library for creating the running horizontal blocks effect, also known as ticker or the «marquee effect».
1,187 lines (1,006 loc) • 32.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.Reeller = {}));
})(this, (function (exports) {
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
_setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
var Base = /*#__PURE__*/function () {
/**
* Base class.
*/
function Base() {
this.events = {};
}
/**
* Attach an event handler function.
*
* @param {string} event Event name.
* @param {function} callback Callback.
*/
var _proto = Base.prototype;
_proto.on = function on(event, callback) {
if (!(this.events[event] instanceof Array)) this.events[event] = [];
this.events[event].push(callback);
}
/**
* Remove an event handler.
*
* @param {string} event Event name.
* @param {function} [callback] Callback.
*/
;
_proto.off = function off(event, callback) {
if (callback) {
this.events[event] = this.events[event].filter(function (f) {
return f !== callback;
});
} else {
this.events[event] = [];
}
}
/**
* Execute all handlers for the given event type.
*
* @param {string} event Event name.
* @param params Extra parameters.
*/
;
_proto.trigger = function trigger(event) {
var _arguments = arguments,
_this = this;
if (!this.events[event]) return;
this.events[event].forEach(function (f) {
return f.call.apply(f, [_this, _this].concat([].slice.call(_arguments, 1)));
});
};
return Base;
}();
var Filler = /*#__PURE__*/function (_Base) {
_inheritsLoose(Filler, _Base);
/**
* @typedef {Object} FillerOptions
* @property {string|HTMLElement|null} container Container element or selector.
* @property {string|HTMLElement|null} wrapper Inner element or selector.
* @property {string|null} itemSelector Items CSS selector.
* @property {string} [cloneClassName] Class name of the new clones.
* @property {boolean} [autoUpdate] Use ResizeObserver to auto update clones number.
* @property {boolean} [clonesOverflow] Create artificial overflow with clones.
* @property {boolean} [clonesFinish] Bring the cycle of clones to an end.
* @property {boolean} [clonesMin] Minimum number of clones.
*/
/**
* Default options.
*
* @type {FillerOptions}
*/
/**
* Create Filler instance.
*
* @param {FillerOptions} [options] Filler options.
*/
function Filler(options) {
var _this;
_this = _Base.call(this) || this;
/** @type {FillerOptions} **/
_this.options = _extends({}, Filler.defaultOptions, options);
_this.container = typeof _this.options.container === 'string' ? document.querySelector(_this.options.container) : _this.options.container;
_this.wrapper = typeof _this.options.wrapper === 'string' ? _this.container.querySelector(_this.options.wrapper) : _this.options.wrapper || _this.options.container;
/** @type Array.<HTMLElement> **/
_this.item = [];
_this.refresh(false);
if (_this.options.autoUpdate) {
_this.bindResizeObserver();
} else {
_this.update();
}
return _this;
}
/**
* Bind ResizeObserver to container for auto update.
*/
var _proto = Filler.prototype;
_proto.bindResizeObserver = function bindResizeObserver() {
var _this2 = this;
this.resizeObserver = new ResizeObserver(function () {
_this2.update();
});
this.resizeObserver.observe(this.container);
}
/**
* Creates and adds clones to end in the desired number from given offset.
*
* @param {number} [count] Number of clones to add.
* @param {number} [offset] Offset from start.
*/
;
_proto.addClones = function addClones(count, offset) {
var _this$wrapper;
if (offset === void 0) {
offset = 0;
}
var clones = [];
for (var i = 0; i < count; i++) {
var item = this.item[(offset + i) % this.item.length].cloneNode(true);
item.classList.add(this.options.cloneClassName);
clones.push(item);
}
(_this$wrapper = this.wrapper).append.apply(_this$wrapper, clones);
}
/**
* Removes the desired number of clones from the end.
*
* @param {number} [count] Number of clones to remove.
*/
;
_proto.removeClones = function removeClones(count) {
if (count === void 0) {
count = 0;
}
var clones = Array.from(this.wrapper.getElementsByClassName(this.options.cloneClassName));
clones.slice(-count).forEach(function (el) {
return el.remove();
});
}
/**
* Sets the desired number of clones.
*
* @param {number} [count] Number of clones.
*/
;
_proto.setClonesCount = function setClonesCount(count) {
if (this.clonesCount === count) return;
if (this.clonesCount < count) this.addClones(count - this.clonesCount, this.clonesCount);
if (this.clonesCount > count) this.removeClones(this.clonesCount - count);
this.clonesCount = count;
}
/**
* Get calculated data object.
*
* @return {Object} Calculated data.
*/
;
_proto.getCalcData = function getCalcData() {
var data = {
clonesCount: 0,
clonesWidth: 0,
containerWidth: this.container.offsetWidth,
fullWidth: 0,
itemWidth: [],
itemsWidth: 0,
lastIndex: 0
};
this.item.map(function (el) {
var style = window.getComputedStyle(el);
var width = el.offsetWidth + parseInt(style.marginLeft) + parseInt(style.marginRight);
data.itemWidth.push(width);
data.itemsWidth += width;
});
var itemLength = data.itemWidth.length;
var width = this.options.clonesOverflow ? data.containerWidth : data.containerWidth - data.itemsWidth;
while (width > data.clonesWidth || data.clonesCount < this.options.clonesMin || this.options.clonesFinish && data.clonesCount % itemLength > 0) {
data.lastIndex = data.clonesCount % itemLength;
data.clonesWidth += data.itemWidth[data.lastIndex];
data.clonesCount++;
}
data.fullWidth = data.clonesWidth + data.itemsWidth;
return data;
}
/**
* Calculates and sets the number of clones.
*/
;
_proto.update = function update() {
this.calcData = this.getCalcData();
this.setClonesCount(this.calcData.clonesCount);
this.trigger('update', this.calcData);
}
/**
* Fully refresh and update all clones.
*
* @param {boolean} [update] Update after refresh.
*/
;
_proto.refresh = function refresh(update) {
if (update === void 0) {
update = true;
}
this.removeClones();
this.item = Array.from(this.container.querySelectorAll(this.options.itemSelector));
this.calcData = {};
this.clonesCount = 0;
this.trigger('refresh');
if (update) this.update();
}
/**
* Destroy Reeller instance.
*
* @param {boolean} [removeClones] Remove clones from DOM.
*/
;
_proto.destroy = function destroy(removeClones) {
if (removeClones === void 0) {
removeClones = false;
}
if (removeClones) this.removeClones();
if (this.resizeObserver) this.resizeObserver.disconnect();
this.trigger('destroy');
};
return Filler;
}(Base);
Filler.defaultOptions = {
container: null,
wrapper: null,
itemSelector: null,
cloneClassName: '-clone',
autoUpdate: true,
clonesOverflow: false,
clonesFinish: false,
clonesMin: 0
};
var Reeller = /*#__PURE__*/function (_Base) {
_inheritsLoose(Reeller, _Base);
/**
* @typedef {Object} ReellerOptions
* @property {string|HTMLElement|null} container Container element or selector.
* @property {string|HTMLElement|null} wrapper Inner element or selector.
* @property {string|null} itemSelector Items CSS selector.
* @property {string} [cloneClassName] Class name of the new clones.
* @property {number} [speed] Movement speed.
* @property {string} [ease] Timing function.
* @property {number} [initialSeek] Initial seek of timeline.
* @property {boolean} [loop] Loop movement.
* @property {boolean} [paused] Initialize in paused mode.
* @property {boolean} [reversed] Reverse mode.
* @property {boolean} [autoPlay] Use IntersectionObserver to auto play/stop movement.
* @property {boolean} [autoUpdate] Use ResizeObserver to auto update clones number.
* @property {boolean} [clonesOverflow] Create artificial overflow with clones.
* @property {boolean} [clonesFinish] Bring the cycle of clones to an end.
* @property {boolean} [clonesMin] Minimum number of clones.
* @property {Object|null} [plugins] Options for plugins.
*/
/**
* Default options.
*
* @type {ReellerOptions}
*/
/**
* Registered plugin storage.
*
* @type {Object}
*/
/**
* Create Reeller instance.
*
* @param {ReellerOptions} [options] Reeller options.
*/
function Reeller(options) {
var _this;
_this = _Base.call(this) || this;
/** @type {ReellerOptions} **/
_this.options = _extends({}, Reeller.defaultOptions, options);
_this.gsap = Reeller.gsap || window.gsap;
_this.paused = _this.options.paused;
_this.createFiller();
_this.createTimeline();
if (_this.options.autoPlay || _this.options.autoStop) _this.bindIntersectionObserver();
if (_this.options.plugins) _this.initPlugins();
return _this;
}
/**
* Register GSAP animation library.
*
* @param {GSAP} gsap GSAP library.
*/
Reeller.registerGSAP = function registerGSAP(gsap) {
Reeller.gsap = gsap;
}
/**
* Register plugins.
*/
;
Reeller.use = function use() {
[].slice.call(arguments).forEach(function (plugin) {
var name = plugin.pluginName;
if (typeof name !== 'string') throw new TypeError('Invalid plugin. Name is required.');
Reeller.plugins[name] = plugin;
});
}
/**
* Create filler.
*/
;
var _proto = Reeller.prototype;
_proto.createFiller = function createFiller() {
var _this2 = this;
this.filler = new Filler(this.options);
this.filler.on('update', function (filler, calcData) {
_this2.invalidate();
_this2.trigger('update', calcData);
});
this.filler.on('refresh', function () {
_this2.trigger('refresh');
});
}
/**
* Create timeline.
*/
;
_proto.createTimeline = function createTimeline() {
var _this3 = this;
this.tl = new this.gsap.timeline({
paused: this.options.paused,
reversed: this.options.reversed,
repeat: -1,
yoyo: !this.options.loop,
onReverseComplete: function onReverseComplete() {
this.progress(1);
}
});
this.gsap.set(this.filler.container, {
overflow: 'hidden'
});
this.tl.fromTo(this.filler.wrapper, {
x: function x() {
if (!_this3.options.clonesOverflow) {
return -(_this3.filler.calcData.fullWidth - _this3.filler.calcData.containerWidth);
}
return -_this3.filler.calcData.itemsWidth;
}
}, {
x: 0,
duration: this.options.speed,
ease: this.options.ease
});
this.tl.seek(this.options.seek);
return this.tl;
}
/**
* Bind IntersectionObserver to container for autoplay.
*/
;
_proto.bindIntersectionObserver = function bindIntersectionObserver() {
var _this4 = this;
this.intersectionObserver = new IntersectionObserver(function (entries) {
if (entries[0].isIntersecting) {
_this4.resume();
} else {
_this4.pause();
}
});
this.intersectionObserver.observe(this.filler.container);
}
/**
* Init plugins from options.
*/
;
_proto.initPlugins = function initPlugins() {
this.plugin = {};
for (var _i = 0, _Object$entries = Object.entries(this.options.plugins); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _Object$entries[_i],
name = _Object$entries$_i[0],
options = _Object$entries$_i[1];
var factory = Reeller.plugins[name];
if (factory) {
this.plugin[name] = new factory(this, options);
} else {
console.error("Plugin " + name + " not found. Make sure you register it with Reeller.use()");
}
}
}
/**
* Destroy initialized plugins.
*/
;
_proto.destroyPlugins = function destroyPlugins() {
for (var _i2 = 0, _Object$values = Object.values(this.plugin); _i2 < _Object$values.length; _i2++) {
var instance = _Object$values[_i2];
if (instance.destroy) instance.destroy();
}
}
/**
* Resume moving.
*/
;
_proto.resume = function resume() {
this.gsap.set(this.filler.container, {
z: '0'
});
this.gsap.set(this.filler.wrapper, {
willChange: 'transform'
});
this.paused = false;
this.tl.resume();
this.trigger('resume');
}
/**
* Set reversed moving.
*
* @param {boolean} [reversed] Is movement reversed?
*/
;
_proto.reverse = function reverse(reversed) {
if (reversed === void 0) {
reversed = true;
}
this.tl.reversed(reversed);
this.resume();
this.trigger('reverse', reversed);
}
/**
* Pause moving.
*/
;
_proto.pause = function pause() {
this.gsap.set(this.filler.container, {
clearProps: 'z'
});
this.gsap.set(this.filler.wrapper, {
willChange: 'auto'
});
this.paused = true;
this.tl.pause();
this.trigger('pause');
}
/**
* Refresh timeline.
*/
;
_proto.invalidate = function invalidate() {
this.tl.invalidate();
this.trigger('invalidate');
}
/**
* Recalculate data.
*/
;
_proto.update = function update() {
this.filler.update();
}
/**
* Fully refresh and update all clones and position.
*
* @param {boolean} [update] Update after refresh.
*/
;
_proto.refresh = function refresh(update) {
if (update === void 0) {
update = true;
}
this.filler.refresh(update);
}
/**
* Destroy Reeller instance.
*
* @param {boolean} [removeClones] Remove clones from DOM.
* @param {boolean} [clearProps] Remove transformations.
*/
;
_proto.destroy = function destroy(removeClones, clearProps) {
if (removeClones === void 0) {
removeClones = false;
}
if (clearProps === void 0) {
clearProps = false;
}
if (this.intersectionObserver) this.intersectionObserver.disconnect();
if (this.options.plugins) this.destroyPlugins();
this.tl.kill();
this.filler.destroy(removeClones);
if (clearProps) {
this.gsap.set(this.filler.container, {
clearProps: 'overflow'
});
this.gsap.set(this.filler.wrapper, {
clearProps: 'x,willChange'
});
}
this.trigger('destroy');
};
return Reeller;
}(Base);
Reeller.defaultOptions = {
container: null,
wrapper: null,
itemSelector: null,
cloneClassName: '-clone',
speed: 10,
ease: 'none',
initialSeek: 10,
loop: true,
paused: true,
reversed: false,
autoPlay: true,
autoUpdate: true,
clonesOverflow: true,
clonesFinish: false,
clonesMin: 0,
plugins: null
};
Reeller.plugins = {};
var DragPlugin = /*#__PURE__*/function () {
/**
* @typedef {Object} DragPluginOptions
* @property {number} [speed] Inertia duration in seconds.
* @property {number} [multiplier] Drag movement multiplier.
* @property {number} [threshold] Minimum release velocity in px/s to start inertia.
* @property {number} [inertiaMultiplier] Inertia distance multiplier.
* @property {number} [activationDistance] Minimum movement in px before drag starts.
* @property {number} [maxVelocity] Maximum release velocity in px/s.
* @property {string} [ease] Timing function.
* @property {boolean} [changeDirection] Change autoplay direction after drag.
* @property {'x'|'y'} [axis] Pointer axis used for drag.
* @property {boolean} [invertAxis] Invert drag direction on the selected axis.
* @property {string|HTMLElement|null} [target] Drag target element or selector.
* @property {boolean} [preventDefault] Prevent default pointer behaviour while dragging.
*/
/**
* Plugin name.
*
* @type {string}
*/
/**
* Default options.
*
* @type {DragPluginOptions}
*/
/**
* Reeller DragPlugin.
*
* @param {Reeller} reeller Reeller instance.
* @param {object} options Options.
*/
function DragPlugin(reeller, options) {
/** @type {DragPluginOptions} **/
this.options = _extends({}, DragPlugin.defaultOptions, options);
this.reeller = reeller;
this.gsap = this.reeller.gsap;
this.tl = this.reeller.tl;
this.pointerId = null;
this.dragging = false;
this.samples = [];
this.basePaused = null;
this.dragDirection = 0;
this.nextReversed = null;
this.init();
}
/**
* Return current movement width for one cycle.
*
* @return {number} Movement width in pixels.
*/
var _proto = DragPlugin.prototype;
_proto.getTrackWidth = function getTrackWidth() {
var _this$reeller$filler$ = this.reeller.filler.calcData,
_this$reeller$filler$2 = _this$reeller$filler$.itemsWidth,
itemsWidth = _this$reeller$filler$2 === void 0 ? 0 : _this$reeller$filler$2,
_this$reeller$filler$3 = _this$reeller$filler$.fullWidth,
fullWidth = _this$reeller$filler$3 === void 0 ? 0 : _this$reeller$filler$3,
_this$reeller$filler$4 = _this$reeller$filler$.containerWidth,
containerWidth = _this$reeller$filler$4 === void 0 ? 0 : _this$reeller$filler$4;
if (this.reeller.options.clonesOverflow) return itemsWidth;
return fullWidth - containerWidth;
}
/**
* Move timeline by drag delta.
*
* @param {number} delta Movement delta in pixels.
*/
;
_proto.applyDelta = function applyDelta(delta) {
var trackWidth = this.getTrackWidth();
if (!trackWidth || !delta) return;
var timeDelta = delta * this.options.multiplier * this.reeller.options.speed / trackWidth;
this.tl.totalTime(this.tl.totalTime() + timeDelta);
}
/**
* Return pointer position for active drag axis.
*
* @param {PointerEvent} event Pointer event.
* @return {number} Pointer position.
*/
;
_proto.getPointerPosition = function getPointerPosition(event) {
var position = this.options.axis === 'y' ? event.clientY : event.clientX;
return this.options.invertAxis ? -position : position;
}
/**
* Save point for release velocity calculation.
*
* @param {number} position Pointer position.
*/
;
_proto.pushSample = function pushSample(position) {
var time = performance.now();
this.samples.push({
position: position,
time: time
});
while (this.samples.length > 5 || time - this.samples[0].time > 120) {
this.samples.shift();
}
}
/**
* Return release velocity in px/s.
*
* @return {number} Release velocity in px/s.
*/
;
_proto.getVelocity = function getVelocity() {
if (this.samples.length < 2) return 0;
var first = this.samples[0];
var last = this.samples[this.samples.length - 1];
var deltaTime = last.time - first.time;
if (!deltaTime) return 0;
return (last.position - first.position) / deltaTime * 1000;
}
/**
* Save autoplay direction to apply after drag.
*
* @param {number} velocity Release velocity in px/s.
*/
;
_proto.saveDirection = function saveDirection(velocity) {
if (!this.options.changeDirection) return;
var direction = Math.sign(velocity) || this.dragDirection;
if (!direction) return;
this.nextReversed = direction < 0;
}
/**
* Start drag interaction and pause autoplay.
*/
;
_proto.beginDrag = function beginDrag() {
this.stopInertia();
this.dragging = true;
if (this.basePaused === null) {
this.basePaused = this.reeller.paused;
}
this.tl.pause();
this.samples = [];
this.pushSample(this.lastPos);
}
/**
* Restore base playback state.
*/
;
_proto.restorePlayback = function restorePlayback() {
if (this.nextReversed !== null) {
this.tl.reversed(this.nextReversed);
}
if (this.basePaused) {
this.tl.pause();
} else {
this.tl.resume();
}
this.basePaused = null;
this.nextReversed = null;
}
/**
* Start inertia tween.
*
* @param {number} velocity Release velocity in px/s.
*/
;
_proto.startInertia = function startInertia(velocity) {
var _this = this;
var maxVelocity = Math.abs(this.options.maxVelocity);
var clampedVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, velocity));
var distance = clampedVelocity * this.options.speed * this.options.inertiaMultiplier;
this.restorePlayback();
if (Math.abs(clampedVelocity) < this.options.threshold || !distance) {
return;
}
var proxy = {
offset: 0
};
var lastOffset = 0;
this.inertiaTween = this.gsap.to(proxy, {
offset: distance,
duration: this.options.speed,
ease: this.options.ease,
overwrite: true,
onUpdate: function onUpdate() {
var delta = proxy.offset - lastOffset;
lastOffset = proxy.offset;
_this.applyDelta(delta);
},
onComplete: function onComplete() {
_this.inertiaTween = null;
}
});
}
/**
* Stop current inertia tween.
*/
;
_proto.stopInertia = function stopInertia() {
if (!this.inertiaTween) return;
this.inertiaTween.kill();
this.inertiaTween = null;
}
/**
* Reset pointer bookkeeping.
*/
;
_proto.resetPointer = function resetPointer() {
this.pointerId = null;
this.dragging = false;
this.startPos = 0;
this.lastPos = 0;
this.dragDirection = 0;
this.samples = [];
}
/**
* Initialize plugin.
*/
;
_proto.init = function init() {
var _this2 = this;
var target = this.options.target;
if (!target) {
this.target = this.reeller.filler.container;
} else if (typeof target === 'string') {
this.target = this.reeller.filler.container.querySelector(target) || document.querySelector(target);
} else {
this.target = target;
}
if (!this.target) {
throw new TypeError('DragPlugin target not found.');
}
this.onPointerDown = function (event) {
if (_this2.pointerId !== null) return;
if (event.pointerType === 'mouse' && event.button !== 0) return;
var position = _this2.getPointerPosition(event);
_this2.stopInertia();
_this2.pointerId = event.pointerId;
_this2.startPos = position;
_this2.lastPos = position;
_this2.samples = [];
if (_this2.target.setPointerCapture) {
_this2.target.setPointerCapture(event.pointerId);
}
};
this.onPointerMove = function (event) {
if (event.pointerId !== _this2.pointerId) return;
var position = _this2.getPointerPosition(event);
if (!_this2.dragging) {
if (Math.abs(position - _this2.startPos) < _this2.options.activationDistance) return;
_this2.beginDrag();
}
if (_this2.options.preventDefault) event.preventDefault();
var delta = position - _this2.lastPos;
_this2.lastPos = position;
if (!delta) return;
_this2.dragDirection = Math.sign(delta);
_this2.pushSample(position);
_this2.applyDelta(delta);
};
this.onPointerUp = function (event) {
if (event.pointerId !== _this2.pointerId) return;
if (_this2.target.releasePointerCapture && (!_this2.target.hasPointerCapture || _this2.target.hasPointerCapture(event.pointerId))) {
_this2.target.releasePointerCapture(event.pointerId);
}
if (!_this2.dragging) {
if (_this2.basePaused !== null) {
_this2.restorePlayback();
}
_this2.resetPointer();
return;
}
if (_this2.options.preventDefault) event.preventDefault();
_this2.pushSample(_this2.getPointerPosition(event));
var velocity = _this2.getVelocity();
_this2.saveDirection(velocity);
_this2.resetPointer();
_this2.startInertia(velocity);
};
this.onPointerCancel = function (event) {
if (event.pointerId !== _this2.pointerId) return;
if (_this2.target.releasePointerCapture && (!_this2.target.hasPointerCapture || _this2.target.hasPointerCapture(event.pointerId))) {
_this2.target.releasePointerCapture(event.pointerId);
}
if (_this2.dragging || _this2.basePaused !== null) {
_this2.restorePlayback();
}
_this2.resetPointer();
};
this.target.addEventListener('pointerdown', this.onPointerDown);
this.target.addEventListener('pointermove', this.onPointerMove);
this.target.addEventListener('pointerup', this.onPointerUp);
this.target.addEventListener('pointercancel', this.onPointerCancel);
}
/**
* Destroy plugin.
*/
;
_proto.destroy = function destroy() {
this.stopInertia();
if (this.target) {
this.target.removeEventListener('pointerdown', this.onPointerDown);
this.target.removeEventListener('pointermove', this.onPointerMove);
this.target.removeEventListener('pointerup', this.onPointerUp);
this.target.removeEventListener('pointercancel', this.onPointerCancel);
}
this.basePaused = null;
this.nextReversed = null;
this.resetPointer();
};
return DragPlugin;
}();
DragPlugin.pluginName = 'drag';
DragPlugin.defaultOptions = {
speed: 1,
multiplier: 1,
threshold: 50,
inertiaMultiplier: 0.2,
activationDistance: 3,
maxVelocity: 3000,
ease: 'expo.out',
changeDirection: false,
axis: 'x',
invertAxis: false,
target: null,
preventDefault: true
};
var ScrollerPlugin = /*#__PURE__*/function () {
/**
* @typedef {Object} ScrollerPluginOptions
* @property {number} [speed] Movement and inertia speed.
* @property {number} [multiplier] Movement multiplier.
* @property {number} [threshold] Movement threshold.
* @property {string} [ease] Timing function.
* @property {boolean} [overwrite] GSAP overwrite mode.
* @property {boolean} [bothDirection] Allow movement in both directions.
* @property {boolean} [reversed] Reverse scroll movement.
* @property {boolean} [stopOnEnd] Use IntersectionObserver to auto stop movement.
* @property {function} [scrollProxy] Use ResizeObserver to auto update clones number.
*/
/**
* Plugin name.
*
* @type {string}
*/
/**
* Default options.
*
* @type {ScrollerPluginOptions}
*/
/**
* Reeller ScrollerPlugin.
*
* @param {Reeller} reeller Reeller instance.
* @param {object} options Options
*/
function ScrollerPlugin(reeller, options) {
/** @type {ScrollerPluginOptions} **/
this.options = _extends({}, ScrollerPlugin.defaultOptions, options);
this.reeller = reeller;
this.gsap = this.reeller.gsap;
this.tl = this.reeller.tl;
this.init();
}
/**
* Return scroll position.
*
* @return {number} Scroll position.
*/
var _proto = ScrollerPlugin.prototype;
_proto.getScrollPos = function getScrollPos() {
if (this.options.scrollProxy) return this.options.scrollProxy();
return window.scrollY;
}
/**
* Initialize plugin.
*/
;
_proto.init = function init() {
var _this = this;
var lastScrollPos = this.getScrollPos();
var lastDirection = 1;
var reachedEnd = true;
var isScrolled = false;
this.tickerFn = function () {
var scrollPos = _this.getScrollPos();
var velocity = scrollPos - lastScrollPos;
if (velocity) isScrolled = true;
if (!isScrolled) return;
if (!_this.options.bothDirection) {
velocity = Math.abs(velocity);
}
if (_this.options.reversed) {
velocity *= -1;
}
if (_this.reeller.paused) {
lastDirection = Math.sign(velocity);
lastScrollPos = scrollPos;
if (!reachedEnd) {
_this.gsap.killTweensOf(_this.tl);
reachedEnd = true;
}
_this.tl.timeScale(lastDirection * _this.options.threshold);
return;
}
if (velocity) {
var delta = velocity * _this.options.multiplier;
var timeScale = delta > 0 ? Math.max(_this.options.threshold, delta) : Math.min(-_this.options.threshold, delta);
_this.tween = _this.gsap.to(_this.tl, {
timeScale: timeScale,
duration: _this.options.speed,
ease: _this.options.ease,
overwrite: _this.options.overwrite
});
reachedEnd = false;
} else {
if (!reachedEnd) {
var _timeScale = _this.options.stopOnEnd ? 0 : lastDirection * _this.options.threshold;
_this.gsap.killTweensOf(_this.tl);
_this.tween = _this.gsap.to(_this.tl, {
timeScale: _timeScale,
duration: _this.options.speed,
overwrite: _this.options.overwrite,
ease: _this.options.ease
});
reachedEnd = true;
}
}
lastDirection = Math.sign(velocity);
lastScrollPos = scrollPos;
};
this.gsap.ticker.add(this.tickerFn);
}
/**
* Destroy plugin.
*/
;
_proto.destroy = function destroy() {
if (this.tickerFn) {
this.gsap.ticker.remove(this.tickerFn);
this.tickerFn = null;
}
if (this.tween) this.tween.kill();
};
return ScrollerPlugin;
}();
ScrollerPlugin.pluginName = 'scroller';
ScrollerPlugin.defaultOptions = {
speed: 1,
multiplier: 0.5,
threshold: 1,
ease: 'expo.out',
overwrite: true,
bothDirection: true,
reversed: false,
stopOnEnd: false,
scrollProxy: null
};
exports.DragPlugin = DragPlugin;
exports.Filler = Filler;
exports.Reeller = Reeller;
exports.ScrollerPlugin = ScrollerPlugin;
exports["default"] = Reeller;
}));