UNPKG

cute-clever-carousel

Version:
243 lines (242 loc) 10.9 kB
"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;