/*!
jQuery.kinetic v1.5
Dave Taylor http://the-taylors.org/jquery.kinetic
The MIT License (MIT)
Copyright (c) <2011>
*/
/*global define,require */
(function ($) {
'use strict';
var DEFAULT_SETTINGS = { decelerate: true
, triggerHardware: false
, y: true
, x: true
, slowdown: 0.9
, maxvelocity: 40
, throttleFPS: 60
, movingClass: {
up: 'kinetic-moving-up'
, down: 'kinetic-moving-down'
, left: 'kinetic-moving-left'
, right: 'kinetic-moving-right'
}
, deceleratingClass: {
up: 'kinetic-decelerating-up'
, down: 'kinetic-decelerating-down'
, left: 'kinetic-decelerating-left'
, right: 'kinetic-decelerating-right'
}
},
SETTINGS_KEY = 'kinetic-settings',
ACTIVE_CLASS = 'kinetic-active';
/**
* Provides requestAnimationFrame in a cross browser way.
* http://paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (function () {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function ( /* function FrameRequestCallback */callback, /* DOMElement Element */element) {
window.setTimeout(callback, 1000 / 60);
};
} ());
}
// add touch checker to jQuery.support
$.support = $.support || {};
$.extend($.support, {
touch: "ontouchend" in document
});
var selectStart = function () { return false; };
var decelerateVelocity = function (velocity, slowdown) {
return Math.floor(Math.abs(velocity)) === 0 ? 0 // is velocity less than 1?
: velocity * slowdown; // reduce slowdown
};
var capVelocity = function (velocity, max) {
var newVelocity = velocity;
if (velocity > 0) {
if (velocity > max) {
newVelocity = max;
}
} else {
if (velocity < (0 - max)) {
newVelocity = (0 - max);
}
}
return newVelocity;
};
var setMoveClasses = function (settings, classes) {
this.removeClass(settings.movingClass.up)
.removeClass(settings.movingClass.down)
.removeClass(settings.movingClass.left)
.removeClass(settings.movingClass.right)
.removeClass(settings.deceleratingClass.up)
.removeClass(settings.deceleratingClass.down)
.removeClass(settings.deceleratingClass.left)
.removeClass(settings.deceleratingClass.right);
if (settings.velocity > 0) {
this.addClass(classes.right);
}
if (settings.velocity < 0) {
this.addClass(classes.left);
}
if (settings.velocityY > 0) {
this.addClass(classes.down);
}
if (settings.velocityY < 0) {
this.addClass(classes.up);
}
};
var stop = function ($scroller, settings) {
if (typeof settings.stopped === 'function') {
settings.stopped.call($scroller, settings);
}
};
/** do the actual kinetic movement */
var move = function ($scroller, settings) {
var scroller = $scroller[0];
// set scrollLeft
if (settings.x && scroller.scrollWidth > 0) {
scroller.scrollLeft = settings.scrollLeft = scroller.scrollLeft + settings.velocity;
if (Math.abs(settings.velocity) > 0) {
settings.velocity = settings.decelerate ?
decelerateVelocity(settings.velocity, settings.slowdown) : settings.velocity;
}
} else {
settings.velocity = 0;
}
// set scrollTop
if (settings.y && scroller.scrollHeight > 0) {
scroller.scrollTop = settings.scrollTop = scroller.scrollTop + settings.velocityY;
if (Math.abs(settings.velocityY) > 0) {
settings.velocityY = settings.decelerate ?
decelerateVelocity(settings.velocityY, settings.slowdown) : settings.velocityY;
}
} else {
settings.velocityY = 0;
}
setMoveClasses.call($scroller, settings, settings.deceleratingClass);
if (typeof settings.moved === 'function') {
settings.moved.call($scroller, settings);
}
if (Math.abs(settings.velocity) > 0 || Math.abs(settings.velocityY) > 0) {
// tick for next movement
window.requestAnimationFrame(function () { move($scroller, settings); });
} else {
stop($scroller, settings);
}
};
var callOption = function (method, options) {
var methodFn = $.kinetic.callMethods[method]
, args = Array.prototype.slice.call(arguments)
;
if (methodFn) {
this.each(function () {
var opts = args.slice(1), settings = $(this).data(SETTINGS_KEY);
opts.unshift(settings);
methodFn.apply(this, opts);
});
}
};
var attachListeners = function ($this, settings) {
var element = $this[0];
if ($.support.touch) {
element.addEventListener('touchstart', settings.events.touchStart, false);
element.addEventListener('touchend', settings.events.inputEnd, false);
element.addEventListener('touchmove', settings.events.touchMove, false);
} else {
$this
.mousedown(settings.events.inputDown)
.mouseup(settings.events.inputEnd)
.mousemove(settings.events.inputMove);
}
$this.click(settings.events.inputClick)
.bind("selectstart", selectStart); // prevent selection when dragging
$this.bind('dragstart', settings.events.dragStart);
};
var detachListeners = function ($this, settings) {
var element = $this[0];
if ($.support.touch) {
element.removeEventListener('touchstart', settings.events.touchStart, false);
element.removeEventListener('touchend', settings.events.inputEnd, false);
element.removeEventListener('touchmove', settings.events.touchMove, false);
} else {
$this
.unbind('mousedown', settings.events.inputDown)
.unbind('mouseup', settings.events.inputEnd)
.unbind('mousemove', settings.events.inputMove);
}
$this.unbind('click', settings.events.inputClick)
.unbind("selectstart", selectStart); // prevent selection when dragging
$this.unbind('dragstart', settings.events.dragStart);
};
var initElements = function (options) {
this
.addClass(ACTIVE_CLASS)
.each(function () {
var settings = $.extend({}, DEFAULT_SETTINGS, options);
var self = this
, $this = $(this)
, xpos
, prevXPos = false
, ypos
, prevYPos = false
, mouseDown = false
, scrollLeft
, scrollTop
, throttleTimeout = 1000 / settings.throttleFPS
, lastMove
, elementFocused
;
settings.velocity = 0;
settings.velocityY = 0;
// make sure we reset everything when mouse up
var resetMouse = function () {
xpos = false;
ypos = false;
mouseDown = false;
};
$(document).mouseup(resetMouse).click(resetMouse);
var calculateVelocities = function () {
settings.velocity = capVelocity(prevXPos - xpos, settings.maxvelocity);
settings.velocityY = capVelocity(prevYPos - ypos, settings.maxvelocity);
};
var useTarget = function (target) {
if ($.isFunction(settings.filterTarget)) {
return settings.filterTarget.call(self, target) !== false;
}
return true;
};
var start = function (clientX, clientY) {
mouseDown = true;
settings.velocity = prevXPos = 0;
settings.velocityY = prevYPos = 0;
xpos = clientX;
ypos = clientY;
};
var end = function () {
if (xpos && prevXPos && settings.decelerate === false) {
settings.decelerate = true;
calculateVelocities();
xpos = prevXPos = mouseDown = false;
move($this, settings);
}
};
var inputmove = function (clientX, clientY) {
if (!lastMove || new Date() > new Date(lastMove.getTime() + throttleTimeout)) {
lastMove = new Date();
if (mouseDown && (xpos || ypos)) {
if (elementFocused) {
$(elementFocused).blur();
elementFocused = null;
$this.focus();
}
settings.decelerate = false;
settings.velocity = settings.velocityY = 0;
$this[0].scrollLeft = settings.scrollLeft = settings.x ? $this[0].scrollLeft - (clientX - xpos) : $this[0].scrollLeft;
$this[0].scrollTop = settings.scrollTop = settings.y ? $this[0].scrollTop - (clientY - ypos) : $this[0].scrollTop;
prevXPos = xpos;
prevYPos = ypos;
xpos = clientX;
ypos = clientY;
calculateVelocities();
setMoveClasses.call($this, settings, settings.movingClass);
if (typeof settings.moved === 'function') {
settings.moved.call($this, settings);
}
}
}
};
// Events
settings.events = {
touchStart: function (e) {
if (useTarget(e.target)) {
start(e.touches[0].clientX, e.touches[0].clientY);
e.stopPropagation();
}
},
touchMove: function (e) {
if (mouseDown) {
inputmove(e.touches[0].clientX, e.touches[0].clientY);
if (e.preventDefault) { e.preventDefault(); }
}
},
inputDown: function (e) {
if (useTarget(e.target)) {
start(e.clientX, e.clientY);
elementFocused = e.target;
if (e.target.nodeName === 'IMG') {
e.preventDefault();
}
e.stopPropagation();
}
},
inputEnd: function (e) {
end();
elementFocused = null;
if (e.preventDefault) { e.preventDefault(); }
},
inputMove: function (e) {
if (mouseDown) {
inputmove(e.clientX, e.clientY);
if (e.preventDefault) { e.preventDefault(); }
}
},
inputClick: function (e) {
if (Math.abs(settings.velocity) > 0) {
e.preventDefault();
return false;
}
},
// prevent drag and drop images in ie
dragStart: function (e) {
if (elementFocused) {
return false;
}
}
};
attachListeners($this, settings);
$this.data(SETTINGS_KEY, settings).css("cursor", "move");
if (settings.triggerHardware) {
$this.css('-webkit-transform', 'translate3d(0,0,0)');
}
});
};
$.kinetic = {
settingsKey: SETTINGS_KEY,
callMethods: {
start: function (settings, options) {
var $this = $(this);
settings = $.extend(settings, options);
if (settings) {
settings.decelerate = false;
move($this, settings);
}
},
end: function (settings, options) {
var $this = $(this);
if (settings) {
settings.decelerate = true;
}
},
stop: function (settings, options) {
settings.velocity = 0;
settings.velocityY = 0;
settings.decelerate = true;
},
detach: function (settings, options) {
var $this = $(this);
detachListeners($this, settings);
$this
.removeClass(ACTIVE_CLASS)
.css("cursor", "");
},
attach: function (settings, options) {
var $this = $(this);
attachListeners($this, settings);
$this
.addClass(ACTIVE_CLASS)
.css("cursor", "move");
}
}
};
$.fn.kinetic = function (options) {
if (typeof options === 'string') {
callOption.apply(this, arguments);
} else {
initElements.call(this, options);
}
return this;
};
} (window.jQuery || window.Zepto));