cute-clever-carousel
Version:
243 lines (242 loc) • 10.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var events_1 = require("events");
var StyleKey_1 = require("./util/StyleKey");
var isPassiveSupported_1 = require("./util/isPassiveSupported");
var Options_1 = require("./Options");
var Carousel = /** @class */ (function () {
function Carousel(element, options) {
var _this = this;
/**
* [-items[n].x, -items[n-1].x,..., -items[0].x]
*/
this.grid = [];
this.touchingInfo = null;
this.itemsX = 0;
this.inertiaCurrentVelocity = 0;
this.inertiaTimerId = null;
this.eventEmitter = new events_1.EventEmitter();
this.onTransitionEnd = function (e) {
_this.emit('slideEnd', {
currentIndex: _this.grid.length - 1 - _this.grid.indexOf(_this.getNearestGridX(_this.itemsX)),
total: _this.grid.length
});
};
this.onTouchStart = function (event) {
_this.stopInertiaMove();
var touch = event.touches[0];
_this.touchingInfo = {
touchStartX: touch.pageX,
touchStartY: touch.pageY,
itemsStartX: _this.itemsX,
lastTouchX: touch.pageX,
lastUpdatedAtMS: Date.now(),
deltaTouchX: 0,
deltaUpdatedAtMS: 0
};
};
this.onTouchMove = function (event) {
if (!_this.touchingInfo) {
return;
}
// 縦スクロールだとわかっているときは何もしない
if (_this.touchingInfo.isHorizontalScroll === false) {
return;
}
var touch = event.touches[0];
var dX = touch.pageX - _this.touchingInfo.touchStartX;
if (typeof _this.touchingInfo.isHorizontalScroll === 'undefined') {
var dY = touch.pageY - _this.touchingInfo.touchStartY;
_this.touchingInfo.isHorizontalScroll = Math.abs(dX) - Math.abs(dY) > 0;
}
var now = Date.now();
_this.touchingInfo.deltaTouchX = _this.touchingInfo.lastTouchX - touch.pageX;
_this.touchingInfo.deltaUpdatedAtMS = now - _this.touchingInfo.lastUpdatedAtMS;
_this.touchingInfo.lastTouchX = touch.pageX;
_this.touchingInfo.lastUpdatedAtMS = now;
if (_this.touchingInfo.isHorizontalScroll) {
event.preventDefault();
_this.moveFrameTo(_this.touchingInfo.itemsStartX + dX * _this.options.scrollDeltaCoefficient);
}
};
this.onTouchEnd = function (event) {
if (!_this.touchingInfo) {
return;
}
if (_this.touchingInfo.deltaUpdatedAtMS) {
var initialVelocity = _this.touchingInfo.deltaTouchX * _this.options.scrollDeltaCoefficient / _this.touchingInfo.deltaUpdatedAtMS; // px/msec
// 初速が0に近い場合は直接アニメーションで動かします。
if (!_this.options.inertia || !initialVelocity || _this.abs(initialVelocity) < 0.1) {
_this.moveFrameTo(_this.getNearestGridX(_this.itemsX), _this.options.transitionDurationSec);
}
else {
_this.startInertiaMove(-initialVelocity);
}
}
_this.touchingInfo = null;
};
this.onWindowResize = function () {
_this.reset();
};
this.options = tslib_1.__assign(tslib_1.__assign({}, Options_1.defaultOptions), options);
this.styleKey = new StyleKey_1.default();
this.rootElement = element;
this.frameElement = element.getElementsByClassName(this.options.frameClassName)[0];
this.itemsElement = element.getElementsByClassName(this.options.itemsClassName)[0];
this.touchEventListenerOption = isPassiveSupported_1.default() ? { passive: false } : false;
this.reset();
this.frameElement.addEventListener('touchstart', this.onTouchStart, this.touchEventListenerOption);
this.frameElement.addEventListener('touchmove', this.onTouchMove, this.touchEventListenerOption);
this.frameElement.addEventListener('touchend', this.onTouchEnd);
this.itemsElement.addEventListener('transitionend', this.onTransitionEnd);
this.itemsElement.addEventListener('webkitTransitionEnd', this.onTransitionEnd);
window.addEventListener('resize', this.onWindowResize);
}
Object.defineProperty(Carousel.prototype, "hasNext", {
get: function () {
return 0 <= this.nextGridIndex();
},
enumerable: false,
configurable: true
});
Object.defineProperty(Carousel.prototype, "hasPrev", {
get: function () {
return this.prevGridIndex() <= this.grid.length - 1;
},
enumerable: false,
configurable: true
});
Carousel.prototype.destroy = function () {
this.frameElement.removeEventListener('touchstart', this.onTouchStart);
this.frameElement.removeEventListener('touchmove', this.onTouchMove);
this.frameElement.removeEventListener('touchend', this.onTouchEnd);
this.itemsElement.removeEventListener('transitionend', this.onTransitionEnd);
this.itemsElement.removeEventListener('webkitTransitionEnd', this.onTransitionEnd);
window.removeEventListener('resize', this.onWindowResize);
};
Carousel.prototype.reset = function () {
this.initializeGrid();
};
Carousel.prototype.next = function () {
if (this.hasNext) {
this.stopInertiaMove();
this.moveFrameTo(this.grid[this.nextGridIndex()], this.options.transitionDurationSec);
}
};
Carousel.prototype.prev = function () {
if (this.hasPrev) {
this.stopInertiaMove();
this.moveFrameTo(this.grid[this.prevGridIndex()], this.options.transitionDurationSec);
}
};
Carousel.prototype.on = function (type, listener) {
this.eventEmitter.on(type, listener);
return this;
};
Carousel.prototype.emit = function (type, event) {
this.eventEmitter.emit(type, event);
};
Carousel.prototype.nextGridIndex = function () {
var currentX = this.getNearestGridX(this.itemsX);
var currentIndex = this.grid.indexOf(currentX);
return currentIndex - 1;
};
Carousel.prototype.prevGridIndex = function () {
var currentX = this.getNearestGridX(this.itemsX);
var currentIndex = this.grid.indexOf(currentX);
return currentIndex + 1;
};
Carousel.prototype.initializeGrid = function () {
var frameComputedStyle = window.getComputedStyle(this.frameElement);
var paddingLeft = Number(frameComputedStyle.paddingLeft.replace('px', ''));
var paddingRight = Number(frameComputedStyle.paddingRight.replace('px', ''));
var maxGridX = this.itemsElement.scrollWidth - this.frameElement.clientWidth + paddingLeft + paddingRight;
if (maxGridX < 0) {
this.grid = [0];
return;
}
var grid = [];
for (var i = 0; i < this.itemsElement.children.length; ++i) {
var x = this.itemsElement.children[i].offsetLeft;
if (x >= maxGridX) {
grid.push(maxGridX);
break;
}
grid.push(x);
}
this.grid = grid.map(function (x) { return -x; }).reverse();
};
Carousel.prototype.moveFrameTo = function (targetX, durationS) {
if (durationS === void 0) { durationS = 0; }
var style = this.itemsElement.style;
style[this.styleKey.transition] = durationS + "s " + this.options.transitionTimingFunction;
style[this.styleKey.transform] = 'translate(' + targetX + 'px, 0)';
this.itemsX = targetX;
};
/**
* => acc[px/ms^2]
* |--------------|
* | .ccc-items | -> initialVelocity[px/ms]
* |--------------|
* ----------------------------------------------> X
* ↑this.itemsX[px]
*/
Carousel.prototype.startInertiaMove = function (initialVelocity) {
var _this = this;
// 与えられた初速から到着地点の nearestGridX を算出
var targetX = this.getNearestGridX(this.itemsX + (initialVelocity > 0 ? 1 : -1) * 0.5 * initialVelocity * initialVelocity / this.options.inertiaAcceleration);
var diffX = targetX - this.itemsX;
// 初速に対して算出された diffX が逆方向の場合、そのまま慣性運動させると永遠に止まらないので、直接アニメーションで動かして終了
if (initialVelocity * diffX < 0) {
this.moveFrameTo(targetX, this.options.transitionDurationSec);
return;
}
// targetX にちょうど到達するための加速度(符号付き)を逆算して微調整
var acc = -0.5 * initialVelocity * initialVelocity / diffX;
var inertiaIntervalMS = this.options.inertiaIntervalMS;
this.inertiaCurrentVelocity = initialVelocity;
this.inertiaTimerId = window.setInterval(function () {
var currentVelocity = _this.inertiaCurrentVelocity;
var nextX = _this.itemsX + currentVelocity * inertiaIntervalMS;
var nextXtargetXDiff = targetX - nextX;
// nextX が targetX に十分近くなったら終了
var done = currentVelocity < 0 ? nextXtargetXDiff > -1 : nextXtargetXDiff < 1;
if (done) {
_this.stopInertiaMove();
_this.moveFrameTo(targetX, Math.min(-1 * currentVelocity / acc * 0.001, _this.options.transitionDurationSec));
return;
}
_this.moveFrameTo(nextX);
_this.inertiaCurrentVelocity = currentVelocity + acc * inertiaIntervalMS;
}, inertiaIntervalMS);
};
Carousel.prototype.stopInertiaMove = function () {
if (this.inertiaTimerId !== null) {
clearInterval(this.inertiaTimerId);
this.inertiaTimerId = null;
}
};
Carousel.prototype.getNearestGridX = function (targetX) {
var grid = this.grid;
if (grid[grid.length - 1] < targetX) {
return grid[grid.length - 1];
}
for (var i = grid.length - 1; i > 0; --i) {
if (grid[i - 1] <= targetX && targetX <= grid[i]) {
if (targetX - grid[i - 1] < grid[i] - targetX) {
return grid[i - 1];
}
else {
return grid[i];
}
}
}
return grid[0];
};
Carousel.prototype.abs = function (x) {
return x < 0 ? -x : x;
};
return Carousel;
}());
exports.default = Carousel;