/ Published in: JavaScript
Expand |
Embed | Plain Text
Copy this code and paste it in your HTML
/* Copyright (c) 2009 Robert Jan Bast Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Slider.js Simple class for sliding elements within a container. Sample HTML, CSS and JS HTML <div id="parent"> <div class="a"></div> <div class="b"></div> <div class="c"></div> <div class="d"></div> </div> CSS div{width:100px;height:100px} #parent{overflow:hidden;} .a{background-color:blue} .b{background-color:red} .c{background-color:green} .d{background-color:yellow} JS var slider = new Slider('parent'); // default behavior or var slider = new Slider('parent', {mode:'vertical', endless:true}); // up & down, endlessly or var slider = new Slider('parent', {endless:true, onStart:function(){this.previous()}}); // reversed, endlessly */ var Slider = new Class({ Implements: [Options, Events], options: { childrenSelector: 'div', endless: false, mode: 'horizontal', // horizontal | vertical autoSlide: true, autoDelay: 2000, fxOptions: { duration: 2000 }, onStart: function(){ this.next(); }, onFirst: function(){ this.next(); }, onLast: function(){ this.previous(); }, onNext: function(){}, onPrevious: function(){}, onStop: function(){}, onPause: function(){}, onResume: function(){} }, container: null, children: [], stack: { next: [], previous: [] }, fx: null, timeoutId: null, initialize: function(container, options){ // Grab & extend our container element this.container = $(container); // Wtfux? if (!this.container){ alert('You failed the internet, do not pass start and do not collect awesome fx effects'); } // Apply options passed this.setOptions(options); // Grab all (current) children of our container, based on selector given this.children = this.container.getElements(this.options.childrenSelector).setStyle('float', 'left'); // Stoopid, durrr if (this.children.length < 2){ alert('Failure is imminent, it takes two to tango'); } // Grab all children other than the currently visible one and loop through them this.children.slice(1,this.children.length).each( function(el){ // Remove each element from the DOM and push them onto the 'next' stack this.stack.next.push(el.dispose()); // I bind 'this', because otherwise this.stack is not accessible }.bind(this) ); // If autoslide option is true if (this.options.autoSlide){ // Start! durrr this.start(); } }, start: function(){ this.fireEvent('start'); }, stop: function(){ if (this.timeoutId){ $clear(this.timeoutId); } this.fireEvent('stop'); }, next: function(){ // Check if we still have elements in the next stack OR if endless mode is set if (this.stack.next.length || this.options.endless){ // If we have no more elements on the 'next' stack, we assume endless mode is true if (!this.stack.next.length){ // Grab the last item from the 'previous' stack and push it onto the 'next' stack this.stack.next.push(this.stack.previous.pop()); } // Get the current child, use selector given just to be sure we get the right one var currChild = this.container.getFirst(this.options.childrenSelector); // Get the next child we want var nextChild = this.stack.next.shift(); // Generate the options object var opts = {'0': {}, '1': {}}; // Figure out which mode we are running in switch (this.options.mode){ // Vertical is ^ v case 'vertical' : // So we mess with top margin of current child opts['0']['margin-top'] = [0, -currChild.getStyle('height').toInt()], // And with bottom margin of the next child opts['1']['margin-bottom'] = [-currChild.getStyle('height').toInt(), 0]; // Apply the bottom margin to the to be inserted child and inject it into the dom nextChild.setStyle('margin-bottom', -currChild.getStyle('height').toInt()).inject(currChild, 'after'); break; // Horizontal is < > case 'horizontal' : default : // So we mess with left margin of current child opts['0']['margin-left'] = [0, -currChild.getStyle('width').toInt()], // And with right margin of the next child opts['1']['margin-right'] = [-currChild.getStyle('width').toInt(), 0]; // Apply the right margin to the to be inserted child and inject it into the dom nextChild.setStyle('margin-right', -currChild.getStyle('width').toInt()).inject(currChild, 'after'); break; } // Are we in autoslide mode? if (this.options.autoSlide){ // Is there a timer active? if (this.timeoutId){ // Clear it $clear(this.timeoutId); } // Generate the fxOptions object by merging default with some extra options var fxOptions = $merge(this.options.fxOptions, { // Add a onComplete event onComplete: function(e){ // When we're done, set a timer again, cause this is like, autoslide mode, mkay this.timeoutId = this.next.delay(this.options.autoDelay, this); // Binding 'this' cause otherwise this.next and such are not accessible }.bind(this) }); // No autoslide mode } else { // Default options are sufficient var fxOptions = this.options.fxOptions; } // Create the fx instance with the custom options, and add a chained function this.fx = new Fx.Elements($$(currChild, nextChild), fxOptions).start(opts).chain( function(){ // When we are done, dispose of the current child and put it on the 'previous' stack this.stack.previous.unshift(currChild.dispose().setStyles({'margin-top':0,'margin-bottom':0,'margin-left':0,'margin-right':0})); // Again, binding 'this' for accessibility }.bind(this) ); // We've moved to the next element, fire event! this.fireEvent('next', nextChild); // Ohnoez, no more elements and no endless mode :( } else { // Since there are no more elements, fire the last event this.fireEvent('last'); } }, previous: function(){ // Check if we still have elements in the previous stack OR if endless mode is set if (this.stack.previous.length || this.options.endless){ // If we have no more elements on the 'previous' stack, we assume endless mode is true if (!this.stack.previous.length){ // Grab the last item from the 'next' stack and push it onto the 'previous' stack this.stack.previous.push(this.stack.next.pop()); } // Get the current child, use selector given just to be sure we get the right one var currChild = this.container.getFirst(this.options.childrenSelector); // Get the previous child we want var prevChild = this.stack.previous.shift(); // Generate the options object var opts = {'0': {}, '1': {}}; // Figure out which mode we are running in switch (this.options.mode){ // Vertical is ^ v case 'vertical' : // So we mess with bottom margin of current child opts['0']['margin-bottom'] = [0, -currChild.getStyle('height').toInt()], // And with top margin of the previous child opts['1']['margin-top'] = [-currChild.getStyle('height').toInt(), 0]; // Apply the top margin to the to be inserted child and inject it into the dom prevChild.setStyle('margin-top', -currChild.getStyle('height').toInt()).inject(currChild, 'before'); break; // Horizontal is < > case 'horizontal' : default : // So we mess with right margin of current child opts['0']['margin-right'] = [0, -currChild.getStyle('width').toInt()], // And with left margin of the next child opts['1']['margin-left'] = [-currChild.getStyle('width').toInt(), 0]; // Apply the left margin to the to be inserted child and inject it into the dom prevChild.setStyle('margin-left', -currChild.getStyle('width').toInt()).inject(currChild, 'before'); break; } // Are we in autoslide mode? if (this.options.autoSlide){ // Is there a timer active? if (this.timeoutId){ // Clear it $clear(this.timeoutId); } // Generate the fxOptions object by merging default with some extra options var fxOptions = $merge(this.options.fxOptions, { // Add a onComplete event onComplete: function(e){ // When we're done, set a timer again, cause this is like, autoslide mode, mkay this.timeoutId = this.previous.delay(this.options.autoDelay, this); // Binding 'this' cause otherwise this.previous and such are not accessible }.bind(this) }); // No autoslide mode } else { // Default options are sufficient var fxOptions = this.options.fxOptions; } // Create the fx instance with the custom options, and add a chained function this.fx = new Fx.Elements($$(currChild, prevChild), fxOptions).start(opts).chain( function(){ // When we are done, dispose of the current child and put it on the 'next' stack this.stack.next.unshift(currChild.dispose().setStyles({'margin-top':0,'margin-bottom':0,'margin-left':0,'margin-right':0})); // Again, binding 'this' for accessibility }.bind(this) ); // We've moved to the previous element, fire event! this.fireEvent('previous', prevChild); // Ohnoez, no more elements and no endless mode :( } else { // Since there are no more elements, fire the first event this.fireEvent('first'); } }, pause: function(){ this.fx.pause(); this.fireEvent('pause'); }, resume: function(){ this.fx.resume(); this.fireEvent('resume'); } });