(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 = $('