UNPKG

alloytouch

Version:

super tiny size touch and physical motion library for the web

392 lines (355 loc) 17.8 kB
/* AlloyTouch v0.2.6 * By AlloyTeam http://www.alloyteam.com/ * Github: https://github.com/AlloyTeam/AlloyTouch * MIT Licensed. */ ;(function () { 'use strict'; if (!Date.now) Date.now = function () { return new Date().getTime(); }; var vendors = ['webkit', 'moz']; for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { var vp = vendors[i]; window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; window.cancelAnimationFrame = (window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']); } if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy || !window.requestAnimationFrame || !window.cancelAnimationFrame) { var lastTime = 0; window.requestAnimationFrame = function (callback) { var now = Date.now(); var nextTime = Math.max(lastTime + 16, now); return setTimeout(function () { callback(lastTime = nextTime); }, nextTime - now); }; window.cancelAnimationFrame = clearTimeout; } }()); (function () { function bind(element, type, callback) { element.addEventListener(type, callback, false); } function ease(x) { return Math.sqrt(1 - Math.pow(x - 1, 2)); } function reverseEase(y) { return 1 - Math.sqrt(1 - y * y); } function preventDefaultTest(el, exceptions) { for (var i in exceptions) { if (exceptions[i].test(el[i])) { return true; } } return false; } var AlloyTouch = function (option) { this.reverse = this._getValue(option.reverse, false); this.element = typeof option.touch === "string" ? document.querySelector(option.touch) : option.touch; this.target = this._getValue(option.target, this.element); var followersArr = this._getValue(option.followers, []); this.followers = followersArr.map(function(follower){ return { element: typeof follower.element === 'string' ? document.querySelector(follower.element) : follower.element, offset: follower.offset, } }) this.vertical = this._getValue(option.vertical, true); this.property = option.property; this.tickID = 0; this.value = this._getValue(option.value, this.target[this.property]); this.target[this.property] = this.value; this.followers.forEach(function(follower){ follower.element[this.property] = this.value + follower.offset; }.bind(this)) this.fixed = this._getValue(option.fixed, false); this.sensitivity = this._getValue(option.sensitivity, 1); this.moveFactor = this._getValue(option.moveFactor, 1); this.factor = this._getValue(option.factor, 1); this.outFactor = this._getValue(option.outFactor, 0.3); this.min = option.min this.max = option.max this.deceleration = this._getValue(option.deceleration, 0.0006); this.maxRegion = this._getValue(option.maxRegion, 600); this.springMaxRegion = this._getValue(option.springMaxRegion, 60); this.maxSpeed = option.maxSpeed; this.hasMaxSpeed = !(this.maxSpeed === void 0); this.lockDirection = this._getValue(option.lockDirection, true); var noop = function () { }; const alwaysTrue = function () { return true; }; this.change = option.change || noop; this.touchEnd = option.touchEnd || noop; this.touchStart = option.touchStart || noop; this.touchMove = option.touchMove || noop; this.touchCancel = option.touchCancel || noop; this.reboundEnd = option.reboundEnd || noop; this.animationEnd = option.animationEnd || noop; this.correctionEnd = option.correctionEnd || noop; this.tap = option.tap || noop; this.pressMove = option.pressMove || noop; this.shouldRebound = option.shouldRebound || alwaysTrue; this.preventDefault = this._getValue(option.preventDefault, true); this.preventDefaultException = { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }; this.hasMin = !(this.min === void 0); this.hasMax = !(this.max === void 0); this.isTouchStart = false; this.step = option.step; this.inertia = this._getValue(option.inertia, true); this._calculateIndex(); this.eventTarget = window; if(option.bindSelf){ this.eventTarget = this.element; } this._moveHandler = this._move.bind(this); bind(this.element, "touchstart", this._start.bind(this)); bind(this.eventTarget, "touchend", this._end.bind(this)); bind(this.eventTarget, "touchcancel", this._cancel.bind(this)); this.eventTarget.addEventListener("touchmove", this._moveHandler, { passive: false, capture: false }); this.x1 = this.x2 = this.y1 = this.y2 = null; }; AlloyTouch.prototype = { isAtMax: function () { return this.hasMax && this.target[this.property] >= this.max; }, isAtMin: function () { return this.hasMin && this.target[this.property] <= this.min; }, _getValue: function (obj, defaultValue) { return obj === void 0 ? defaultValue : obj; }, stop:function(){ cancelAnimationFrame(this.tickID); this._calculateIndex(); }, _start: function (evt) { this.isTouchStart = true; this.touchStart.call(this, evt, this.target[this.property]); cancelAnimationFrame(this.tickID); this._calculateIndex(); this.startTime = new Date().getTime(); this.x1 = this.preX = evt.touches[0].pageX; this.y1 = this.preY = evt.touches[0].pageY; this.start = this.vertical ? this.preY : this.preX; this._firstTouchMove = true; this._preventMove = false; }, _move: function (evt) { if (this.isTouchStart) { var len = evt.touches.length, currentX = evt.touches[0].pageX, currentY = evt.touches[0].pageY; if (this._firstTouchMove && this.lockDirection) { var dDis = Math.abs(currentX - this.x1) - Math.abs(currentY - this.y1); if (dDis > 0 && this.vertical) { this._preventMove = true; } else if (dDis < 0 && !this.vertical) { this._preventMove = true; } this._firstTouchMove = false; } if(!this._preventMove) { var d = (this.vertical ? currentY - this.preY : currentX - this.preX) * this.sensitivity; var f = this.moveFactor; if (this.isAtMax() && (this.reverse ? -d : d) > 0) { f = this.outFactor; } else if (this.isAtMin() && (this.reverse ? -d : d) < 0) { f = this.outFactor; } d *= f; this.preX = currentX; this.preY = currentY; if (!this.fixed) { var detalD = this.reverse ? -d : d; this.target[this.property] += detalD; this.followers.forEach(function(follower){ follower.element[this.property] += detalD; }.bind(this)) } this.change.call(this, this.target[this.property]); var timestamp = new Date().getTime(); if (timestamp - this.startTime > 300) { this.startTime = timestamp; this.start = this.vertical ? this.preY : this.preX; } this.touchMove.call(this, evt, this.target[this.property]); } if (this.preventDefault && !preventDefaultTest(evt.target, this.preventDefaultException)) { evt.preventDefault(); } if (len === 1) { if (this.x2 !== null) { evt.deltaX = currentX - this.x2; evt.deltaY = currentY - this.y2; } else { evt.deltaX = 0; evt.deltaY = 0; } this.pressMove.call(this, evt, this.target[this.property]); } this.x2 = currentX; this.y2 = currentY; } }, _cancel: function (evt) { var current = this.target[this.property]; this.touchCancel.call(this, evt, current); this._end(evt); }, to: function (v, time, user_ease, callback) { this._to(v, this._getValue(time, 600), user_ease || ease, this.change, function (value) { this._calculateIndex(); this.reboundEnd.call(this, value); this.animationEnd.call(this, value); callback && callback.call(this, value); }.bind(this)); }, _calculateIndex: function () { if (this.hasMax && this.hasMin) { this.currentPage = Math.round((this.max - this.target[this.property]) / this.step); } }, _end: function (evt) { if (this.isTouchStart) { this.isTouchStart = false; var self = this, current = this.target[this.property], triggerTap = (Math.abs(evt.changedTouches[0].pageX - this.x1) < 30 && Math.abs(evt.changedTouches[0].pageY - this.y1) < 30); if (triggerTap) { this.tap.call(this, evt, current); } if (this.touchEnd.call(this, evt, current, this.currentPage) === false) return; if (this.hasMax && current > this.max) { if (!this.shouldRebound(current)) { return; } this._to(this.max, 200, ease, this.change, function (value) { this.reboundEnd.call(this, value); this.animationEnd.call(this, value); }.bind(this)); } else if (this.hasMin && current < this.min) { if (!this.shouldRebound(current)) { return; } this._to(this.min, 200, ease, this.change, function (value) { this.reboundEnd.call(this, value); this.animationEnd.call(this, value); }.bind(this)); } else if (this.inertia && !triggerTap && !this._preventMove && !this.fixed) { var dt = new Date().getTime() - this.startTime; if (dt < 300) { var distance = ((this.vertical ? evt.changedTouches[0].pageY : evt.changedTouches[0].pageX) - this.start) * this.sensitivity, speed = Math.abs(distance) / dt, actualSpeed = this.factor * speed; if (this.hasMaxSpeed && actualSpeed > this.maxSpeed) { actualSpeed = this.maxSpeed; } var direction = distance < 0 ? -1 : 1; if (this.reverse) { direction = -direction; } var destination = current + (actualSpeed * actualSpeed) / (2 * this.deceleration) * direction; var tRatio = 1; if (destination < this.min) { if (destination < this.min - this.maxRegion) { tRatio = reverseEase((current - this.min + this.springMaxRegion) / (current - destination)); destination = this.min - this.springMaxRegion; } else { tRatio = reverseEase((current - this.min + this.springMaxRegion * (this.min - destination) / this.maxRegion) / (current - destination)); destination = this.min - this.springMaxRegion * (this.min - destination) / this.maxRegion; } } else if (destination > this.max) { if (destination > this.max + this.maxRegion) { tRatio = reverseEase((this.max + this.springMaxRegion - current) / (destination - current)); destination = this.max + this.springMaxRegion; } else { tRatio = reverseEase((this.max + this.springMaxRegion * (destination - this.max) / this.maxRegion - current) / (destination - current)); destination = this.max + this.springMaxRegion * (destination - this.max) / this.maxRegion; } } var duration = Math.round(speed / self.deceleration) * tRatio; self._to(Math.round(destination), duration, ease, self.change, function (value) { if (self.hasMax && self.target[self.property] > self.max) { if (!this.shouldRebound(self.target[self.property])) { return; } cancelAnimationFrame(self.tickID); self._to(self.max, 600, ease, self.change, self.animationEnd); } else if (self.hasMin && self.target[self.property] < self.min) { if (!this.shouldRebound(self.target[self.property])) { return; } cancelAnimationFrame(self.tickID); self._to(self.min, 600, ease, self.change, self.animationEnd); } else { if(self.step) { self._correction() }else{ self.animationEnd.call(self, value); } } self.change.call(this, value); }); } else { self._correction(); } } else { self._correction(); } } this.x1 = this.x2 = this.y1 = this.y2 = null; }, _to: function (value, time, ease, onChange, onEnd) { var el = this.target, property = this.property; var followers = this.followers; var current = el[property]; var dv = value - current; var beginTime = +new Date(); var self = this; var toTick = function () { var dt = +new Date() - beginTime; if (dt >= time) { el[property] = value; onChange && onChange.call(self, value); onEnd && onEnd.call(self, value); return; } var nextPosition = dv * ease(dt / time) + current el[property] = nextPosition; followers.forEach(function(follower){ follower.element[property] = nextPosition + follower.offset; }) self.tickID = requestAnimationFrame(toTick); onChange && onChange.call(self, el[property]); }; toTick(); }, _correction: function () { if (this.step === void 0) return; var el = this.target, property = this.property; var value = el[property]; var rpt = Math.floor(Math.abs(value / this.step)); var dy = value % this.step; if (Math.abs(dy) > this.step / 2) { this._to((value < 0 ? -1 : 1) * (rpt + 1) * this.step, 400, ease, this.change, function (value) { this._calculateIndex(); this.correctionEnd.call(this, value); this.animationEnd.call(this, value); }.bind(this)); } else { this._to((value < 0 ? -1 : 1) * rpt * this.step, 400, ease, this.change, function (value) { this._calculateIndex(); this.correctionEnd.call(this, value); this.animationEnd.call(this, value); }.bind(this)); } } }; if (typeof module !== 'undefined' && typeof exports === 'object') { module.exports = AlloyTouch; } else { window.AlloyTouch = AlloyTouch; } })();