/*! 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));