(function($, window) { var methods = {}; var helper = {}; var behavior = {}; $.fn.spritespin = function(method) { if ( methods[method] ) { return methods[method].apply( this, array.prototype.slice.call( arguments, 1 )); } else if (typeof(method) === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error( 'method ' + method + ' does not exist on jquery.spritespin' ); } }; function spriteloader(images, callback){ if (typeof(images) === "string"){ images = [images]; } this.callback = callback; this.numloaded = 0; this.numerrors = 0; this.numaborts = 0; this.numprocessed = 0; this.numimages = images.length; this.images = []; var i = 0; for (i = 0; i < images.length; i++ ) { this.preload(images[i]); } } spriteloader.prototype.preload = function(imageurl){ // create new image object and add to array var image = new image(); this.images.push(image); // set up event handlers for the image object image.onload = spriteloader.prototype.onload; image.onerror = spriteloader.prototype.onerror; image.onabort = spriteloader.prototype.onabort; // assign pointer back to this. image.preloader = this; // assign the .src property of the image object to start loading image.src = imageurl; }; spriteloader.prototype.onprocessed = function(){ this.numprocessed++; if ( this.numprocessed === this.numimages ){ this.callback(this.images, this.numloaded); } }; spriteloader.prototype.onload = function(){ this.preloader.numloaded++; this.preloader.onprocessed(); }; spriteloader.prototype.onerror = function(){ this.preloader.numerrors++; this.preloader.onprocessed(); }; spriteloader.prototype.onabort = function(){ this.preloader.numaborts++; this.preloader.onprocessed(); }; methods.init = function(options){ // default settings var settings = { // dimensions width : undefined, // window width (or frame width) height : undefined, // window height (or frame height) offsetx : 0, // offset in x direction from the left image border to the first frames left border offsety : 0, // offset in y direction from the top image border to the first frames top border framestepx : undefined, // distance in x direction to the next frame if it differs from window width framestepy : undefined, // distance in y direction to the next frame if it differs from window height framestep : undefined, // width of a single frame or step to the next frame framesx : undefined, // number of frames in a single row frames : 36, // total number of frames frame : 0, // initial frame number resolutionx : undefined, // the spritesheet resolution in x direction resolutiony : undefined, // the spritesheet resolution in y direction // animation & update animate : true, // run animation when after initialize loop : false, // repeat animation in a loop loopframe : 0, // indicates the loop start frame frametime : 36, // time between updates reverse : false, // if true animation is played backward sense : 1, // interaction sensitivity used by behavior implementations // interaction slider : undefined, // jquery-ui slider instance behavior : "drag", // enables mouse interaction // appearance image : "images/spritespin.jpg",// stiched source image preloadhtml : " ", // html to appear when images are preloaded preloadbackground : undefined, // background image to display on load preloadcss : undefined, fadeframes : 0, // enables and disables smooth transitions between frames fadeintime : 0, // fadeouttime : 120, // // events onframe : undefined, // occurs whe frame has been updated onload : undefined, // occurs when images are loaded touchable : undefined, // tells spritespin that it is running on a touchable device panorama : false }; // extending options options = (options || {}); $.extend(settings, options); return this.each(function(){ var $this = $(this); var data = $this.data('spritespin'); if (!data){ // disable selection & hide overflow $this.attr("unselectable", "on").css({ overflow : "hidden" }).html(""); var imageelement, imageelements; if (!settings.panorama && settings.fadeframes > 0){ imageelement = $this.find("img"); if (imageelement.length === 0){ imageelement = $(""); $this.append(imageelement); } var i; for (i = 1; i < settings.fadeframes; i ++){ $this.append(""); } imageelements = $this.find("img"); imageelements.hide(); } // initialize the plugin if it hasn't been initialized yet $this.data('spritespin', { target : $this, settings : settings, animation : null, frametime : settings.frametime, imageelement : imageelement, imageelements: imageelements, imageindex : 0, touchable : (settings.touchable || (/iphone|ipod|ipad|android/i).test(window.navigator.useragent)) }); // run configuration data = $this.data('spritespin'); helper.reconfiger($this, data); } else { // reconfiger the plugin if it is already initialized $.extend(data.settings, options); data.frametime = data.settings.frametime; // override cached frametime if (options.image !== null && options.image !== undefined){ // when images are passed, need to reconfiger the plugin helper.reconfiger($this, data); } else { // otherwise just reanimate spritespin $this.spritespin("animate", data.settings.animate, data.settings.loop); } } }); }; methods.destroy = function(){ return this.each(function(){ var $this = $(this); $this.unbind('.spritespin'); $this.removedata('spritespin'); }); }; // updates a single frame to the specified frame number. if no value is // given this will increment the current frame counter. // triggers the onframe event methods.update = function(frame, reverse){ return this.each(function(){ var $this = $(this); var data = $this.data('spritespin'); var settings = data.settings; if (reverse !== undefined){ settings.reverse = reverse; } // update frame counter if (frame === undefined){ settings.frame = (settings.frame + (settings.reverse ? -1 : 1)); } else { settings.frame = frame; } settings.frame = helper.wrapvalue(settings.frame, 0, settings.frames); data.target.trigger("onframe", data); }); }; // starts or stops the animation depend on the animate paramter. // in case when animation is already running pass "false" to stop. // in case when animation is not running pass "true" to start. // to keep animation running forever pass "true" for the loop parameter. // to detect whether the animation is running or not, do not pass any // parameters. methods.animate = function(animate, loop){ if (animate === undefined){ return $(this).data('spritespin').animation !== null; } else { return this.each(function(){ var $this = $(this); var data = $this.data('spritespin'); var settings = data.settings; // check the loop variable and update settings if (typeof(loop) === "boolean"){ settings.loop = loop; } // toggle and update animation settings if (animate === "toggle"){ animate = !settings.animate; settings.animate = animate; } else { settings.animate = animate; } if (data.animation !== null){ window.clearinterval(data.animation); data.animation = null; } if (settings.animate){ // start animation data.animation = window.setinterval( function(){ try { $this.spritespin("update"); } catch(err){ // the try catch block is a hack for opera browser } }, data.frametime); } }); } }; // gets the current framenumber when no parameter is passed or // updates the spinner to the sepcified frame. methods.frame = function(frame){ if (frame === undefined){ return $(this).data('spritespin').settings.frame; } else { return this.each(function(){ $(this).spritespin("update", frame); }); } }; // gets or sets a value indicating whether the animation is looped or not. // starts the animation when settings.animate is set to true passed value // is defined methods.loop = function(value){ if (value === undefined){ return $(this).data('spritespin').settings.loop; } else { return this.each(function(){ var $this = $(this); var data = $this.data('spritespin'); $this.spritespin("animate", data.settings.animate, value); }); } }; helper.storepoints = function(e, data){ if (e.touches === undefined && e.originalevent !== undefined){ // jquery event normalization does not preserve the event.touches // we just try to restore it e.touches = e.originalevent.touches; } data.oldx = data.currentx; data.oldy = data.currenty; if (e.touches !== undefined && e.touches.length > 0){ data.currentx = e.touches[0].clientx; data.currenty = e.touches[0].clienty; } else { data.currentx = e.clientx; data.currenty = e.clienty; } if (data.startx === undefined || data.starty === undefined){ data.startx = data.currentx; data.starty = data.currenty; data.clickframe = data.settings.frame; } if (data.oldx === undefined || data.oldy === undefined){ data.oldx = data.currentx; data.oldy = data.currenty; } data.dx = data.currentx - data.startx; data.dy = data.currenty - data.starty; data.ddx = data.currentx - data.oldx; data.ddy = data.currenty - data.oldy; return false; }; helper.resetpoints = function(e, data){ data.startx = undefined; data.starty = undefined; data.currentx = undefined; data.currenty = undefined; data.oldx = undefined; data.oldy = undefined; data.dx = 0; data.dy = 0; data.ddx = 0; data.ddy = 0; }; helper.clamp = function(value, min, max){ return (value > max ? max : (value < min ? min : value)); }; helper.wrapvalue = function(value, min, max){ while (value >= max){ value -= max; } while (value < min){ value += max; } return value; }; helper.reconfiger = function(instance, data){ helper.blankbackground(instance, data); helper.preloadimages(instance, data, function(){ helper.updatebackground(instance, data); helper.hookslider(instance, data); helper.rebindevents(instance, data); if (data.settings.animate){ methods.animate.apply(instance, [data.settings.animate, data.settings.loop]); } instance.trigger("onload", data); }); }; helper.blankbackground = function(instance, data){ var image = "none"; if (typeof(data.settings.preloadbackground) === "string"){ image = ["url('", data.settings.preloadbackground, "')"].join(""); } instance.css({ width : [data.settings.width, "px"].join(""), height : [data.settings.height, "px"].join(""), "background-image" : image, "background-repeat" : "repeat-x", "background-position" : "0px 0px" }); $(data.imageelement).hide(); }; helper.updatebackground = function(instance){ var data = instance.data("spritespin"); var image = data.settings.image; var x = data.settings.offsetx; var y = -data.settings.offsety; if (typeof(data.settings.image) === "string"){ var stepx = (data.settings.framestepx || data.settings.width); var stepy = (data.settings.framestepy || data.settings.height); var numframesx = (data.settings.framesx || data.settings.frames); var framex = (data.settings.frame % numframesx); var framey = (data.settings.frame / numframesx)|0; x -= (framex * stepx); y -= (framey * stepy); } else { // we expect an array in this case image = data.settings.image[data.settings.frame]; } var css = {}; if (data.imageelement){ css = { position : "absolute", top : "0px", left : "0px" }; if (data.settings.resolutionx && data.settings.resolutiony){ css.width = data.settings.resolutionx; css.height = data.settings.resolutiony; } instance.css({ position : "relative", top : 0, left : 0, width : data.settings.width, height : data.settings.height }); if (data.imageelements.length === 1){ data.imageelement.attr("src", image).css(css).show(); } else { var max = data.imageelements.length - 1; var index = helper.wrapvalue(data.imageindex, 0, max); var previndex = helper.wrapvalue(data.imageindex + 1, 0, max); data.imageindex = helper.wrapvalue(data.imageindex - 1, 0, max); if (data.settings.fadeouttime > 0){ $(data.imageelements[previndex]).fadeout(data.settings.fadeouttime); } else { $(data.imageelements[previndex]).hide(); } if (data.settings.fadeintime > 0){ $(data.imageelements[index]).attr("src", image).css(css).fadein(data.settings.fadeintime); } else { $(data.imageelements[index]).attr("src", image).css(css).show(); } } } else { css = { width : [data.settings.width, "px"].join(""), height : [data.settings.height, "px"].join(""), "background-image" : ["url('", image, "')"].join(""), "background-repeat" : "repeat-x", "background-position" : [x, "px ", y, "px"].join("") }; // spritesheets may easily exceed the maximum image size for iphones. // in this case the browser will scale down the image automaticly and // this will break the logic how spritespin works. // here we set the webkit css attribute to display the background in its // original dimension even if it has been scaled down. if (data.settings.resolutionx && data.settings.resolutiony) { css["-webkit-background-size"] = [data.settings.resolutionx, "px ", data.settings.resolutiony, "px"].join(""); } instance.css(css); } }; helper.hookslider = function(instance, data){ if (data.settings.slider !== undefined){ data.settings.slider.slider({ value : data.settings.frame, min : 0, max : (data.settings.frames) - 1, step : 1, slide : function(event, ui) { methods.animate.apply(instance, [false]); // stop animation methods.frame.apply(instance, [ui.value]); // update to frame } }); } }; helper.rebindevents = function(instance, data){ // unbind all events instance.unbind('.spritespin'); // use custom or build in behavior var currentbehavior = data.settings.behavior; if (typeof(data.settings.behavior) === "string"){ currentbehavior = behavior[data.settings.behavior]; } var prevent = function(e){ if (e.cancelable){ e.preventdefault(); } return false; }; // rebind interaction events instance.bind('mousedown.spritespin', currentbehavior.mousedown); instance.bind('mousemove.spritespin', currentbehavior.mousemove); instance.bind('mouseup.spritespin', currentbehavior.mouseup); instance.bind('mouseenter.spritespin', currentbehavior.mouseenter); instance.bind('mouseover.spritespin', currentbehavior.mouseover); instance.bind('mouseleave.spritespin', currentbehavior.mouseleave); instance.bind('dblclick.spritespin', currentbehavior.dblclick); instance.bind('onframe.spritespin', currentbehavior.onframe); if (data.touchable){ instance.bind('touchstart.spritespin', currentbehavior.mousedown); instance.bind('touchmove.spritespin', currentbehavior.mousemove); instance.bind('touchend.spritespin', currentbehavior.mouseup); instance.bind('touchcancel.spritespin', currentbehavior.mouseleave); instance.bind('click.spritespin', prevent); instance.bind('gesturestart.spritespin', prevent); instance.bind('gesturechange.spritespin', prevent); instance.bind('gestureend.spritespin', prevent); } // disable selection instance.bind("mousedown.spritespin selectstart.spritespin", prevent); instance.bind("onframe.spritespin", function(event, data){ helper.updatebackground(data.target, data); // stop animation if we are back at looframe if (data.settings.frame === data.settings.loopframe && !data.settings.loop){ methods.animate.apply(data.target, [false]); } // update the jquery-ui slider if (data.settings.slider){ data.settings.slider.slider("value", data.settings.frame); } }); // bind custom events if (typeof(data.settings.onframe) === "function"){ instance.bind("onframe.spritespin", data.settings.onframe); } if (typeof(data.settings.onload) === "function"){ instance.bind("onload.spritespin", data.settings.onload); } }; helper.preloadimages = function(instance, data, callback) { var preload = $('
'); if (instance.find(".preload").length === 0){ instance.append(preload); } var css = (data.settings.preloadcss || {}); preload.css( $.extend({ width : data.settings.width, height: data.settings.height}, css)) .hide() .html(data.settings.preloadhtml) .fadein(250, function(){ new spriteloader(data.settings.image, function(){ instance.find(".preload").fadeout(250, function(){ $(this).detach(); }); callback.apply(instance, [instance, data]); }); }); }; behavior.none = { mousedown : function(e){ return false; }, mousemove : function(e){ return false; }, mouseup : function(e){ return false; }, mouseenter : function(e){ return false; }, mouseover : function(e){ return false; }, mouseleave : function(e){ return false; }, dblclick : function(e){ return false; }, onframe : function(e, frame){ return false; } }; behavior.spin = { mousedown : function(e){ var $this = $(this), data = $this.data('spritespin'); helper.storepoints(e, data); data.ondrag = true; return false; }, mousemove : function(e){ var $this = $(this), data = $this.data('spritespin'); if (data.ondrag){ // perform default drag behavior helper.storepoints(e, data); var d = data.dx / data.settings.width; var dframe = d * data.settings.frames * data.settings.sense; var frame = math.round(data.clickframe + dframe); methods.update.apply($this, [frame]); // update to frame methods.animate.apply($this, [false]); // stop animation // calculate framtetime for spinwheel if (data.ddx !== 0){ d = data.ddx / data.settings.width; dframe = d * data.settings.frames * data.settings.sense; data.frametime = (data.settings.frametime / dframe); data.settings.reverse = (data.ddx < 0); } } return false; }, mouseup : function(e){ var $this = $(this), data = $this.data('spritespin'); if (data.ondrag){ data.ondrag = false; $this.spritespin("animate", true); } return false; }, mouseenter : function(e){ return false; }, mouseover : function(e){ return false; }, mouseleave : function(e){ var $this = $(this), data = $this.data('spritespin'); if (data.ondrag){ data.ondrag = false; $this.spritespin("animate", $this.spritespin("animate")); } return false; }, dblclick : function(e){ $(this).spritespin("animate", "toggle"); return false; }, onframe : function(e, data){ if (data.ddx !== 0){ data.frametime = data.frametime + 1; $(this).spritespin("animate", false); if (data.frametime < 62){ $(this).spritespin("animate", true); } } else { $(this).spritespin("animate", false); } return false; } }; behavior.drag = { mousedown : function(e){ var $this = $(this), data = $this.data('spritespin'); helper.storepoints(e, data); data.ondrag = true; return false; }, mousemove : function(e){ var $this = $(this), data = $this.data('spritespin'); if (data.ondrag){ helper.storepoints(e, data); var d = data.dx / data.settings.width; var dframe = d * data.settings.frames * data.settings.sense; var frame = math.round(data.clickframe + dframe); methods.update.apply($this, [frame]); // update to frame methods.animate.apply($this, [false]); // stop animation } return false; }, mouseup : function(e){ var $this = $(this), data = $this.data('spritespin'); helper.resetpoints(e, data); data.ondrag = false; return false; }, mouseenter : function(e){ return false; }, mouseover : function(e){ return false; }, mouseleave : function(e){ var $this = $(this), data = $this.data('spritespin'); helper.resetpoints(e, data); data.ondrag = false; return false; }, dblclick : function(e){ var $this = $(this), data = $this.data('spritespin'); $this.spritespin("animate", "toggle"); return false; }, onframe : function(e, frame){ return false; } }; }(jquery, window));