/** * jquery roundabout - v1.1 * http://fredhq.com/projects/roundabout/ * * moves list-items of enabled ordered and unordered lists long * a chosen path. includes the default "lazysusan" path, that * moves items long a spinning turntable. * * terms of use // jquery roundabout * * open source under the bsd license * * copyright (c) 2010, fred leblanc * all rights reserved. * * redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * - neither the name of the author nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * this software is provided by the copyright holders and contributors "as is" * and any express or implied warranties, including, but not limited to, the * implied warranties of merchantability and fitness for a particular purpose * are disclaimed. in no event shall the copyright holder or contributors be * liable for any direct, indirect, incidental, special, exemplary, or * consequential damages (including, but not limited to, procurement of * substitute goods or services; loss of use, data, or profits; or business * interruption) however caused and on any theory of liability, whether in * contract, strict liability, or tort (including negligence or otherwise) * arising in any way out of the use of this software, even if advised of the * possibility of such damage. */ // creates a default shape to be used for pathing jquery.extend({ roundabout_shape: { def: 'lazysusan', lazysusan: function(r, a, t) { return { x: math.sin(r + a), y: (math.sin(r + 3*math.pi/2 + a) / 8) * t, z: (math.cos(r + a) + 1) / 2, scale: (math.sin(r + math.pi/2 + a) / 2) + 0.5 }; } } }); jquery.fn.roundabout = function() { var options = (typeof arguments[0] != 'object') ? {} : arguments[0]; // set options and fill in defaults options = { bearing: (typeof options.bearing == 'undefined') ? 0.0 : jquery.roundabout_tofloat(options.bearing % 360.0), tilt: (typeof options.tilt == 'undefined') ? 0.0 : jquery.roundabout_tofloat(options.tilt), minz: (typeof options.minz == 'undefined') ? 100 : parseint(options.minz, 10), maxz: (typeof options.maxz == 'undefined') ? 400 : parseint(options.maxz, 10), minopacity: (typeof options.minopacity == 'undefined') ? 0.40 : jquery.roundabout_tofloat(options.minopacity), maxopacity: (typeof options.maxopacity == 'undefined') ? 1.00 : jquery.roundabout_tofloat(options.maxopacity), minscale: (typeof options.minscale == 'undefined') ? 0.40 : jquery.roundabout_tofloat(options.minscale), maxscale: (typeof options.maxscale == 'undefined') ? 1.00 : jquery.roundabout_tofloat(options.maxscale), duration: (typeof options.duration == 'undefined') ? 600 : parseint(options.duration, 10), btnnext: options.btnnext || null, btnprev: options.btnprev || null, easing: options.easing || 'swing', clicktofocus: (options.clicktofocus !== false), focusbearing: (typeof options.focusbearing == 'undefined') ? 0.0 : jquery.roundabout_tofloat(options.focusbearing % 360.0), shape: options.shape || 'lazysusan', debug: options.debug || false, childselector: options.childselector || 'li', startingchild: (typeof options.startingchild == 'undefined') ? null : parseint(options.startingchild, 10), reflect: (typeof options.reflect == 'undefined' || options.reflect === false) ? false : true }; // assign things this.each(function(i) { var ref = jquery(this); var period = jquery.roundabout_tofloat(360.0 / ref.children(options.childselector).length); var startingbearing = (options.startingchild === null) ? options.bearing : options.startingchild * period; // set starting styles ref .addclass('roundabout-holder') .css('padding', 0) .css('position', 'relative') .css('z-index', options.minz); // set starting options ref.data('roundabout', { 'bearing': startingbearing, 'tilt': options.tilt, 'minz': options.minz, 'maxz': options.maxz, 'minopacity': options.minopacity, 'maxopacity': options.maxopacity, 'minscale': options.minscale, 'maxscale': options.maxscale, 'duration': options.duration, 'easing': options.easing, 'clicktofocus': options.clicktofocus, 'focusbearing': options.focusbearing, 'animating': 0, 'childinfocus': -1, 'shape': options.shape, 'period': period, 'debug': options.debug, 'childselector': options.childselector, 'reflect': options.reflect }); // bind click events if (options.clicktofocus === true) { ref.children(options.childselector).each(function(i) { jquery(this).click(function(e) { var degrees = (options.reflect === true) ? 360.0 - (period * i) : period * i; degrees = jquery.roundabout_tofloat(degrees); if (!jquery.roundabout_isinfocus(ref, degrees)) { e.preventdefault(); if (ref.data('roundabout').animating === 0) { ref.roundabout_animateangletofocus(degrees); } return false; } }); }); } // bind next buttons if (options.btnnext) { jquery(options.btnnext).bind('click.roundabout', function(e) { e.preventdefault(); if (ref.data('roundabout').animating === 0) { ref.roundabout_animatetonextchild(); } return false; }); } // bind previous buttons if (options.btnprev) { jquery(options.btnprev).bind('click.roundabout', function(e) { e.preventdefault(); if (ref.data('roundabout').animating === 0) { ref.roundabout_animatetopreviouschild(); } return false; }); } }); // start children this.roundabout_startchildren(); // callback once ready if (typeof arguments[1] === 'function') { var callback = arguments[1], ref = this; settimeout(function() { callback(ref); }, 0); } return this; }; jquery.fn.roundabout_startchildren = function() { this.each(function(i) { var ref = jquery(this); var data = ref.data('roundabout'); var children = ref.children(data.childselector); children.each(function(i) { var degrees = (data.reflect === true) ? 360.0 - (data.period * i) : data.period * i; // apply classes and css first jquery(this) .addclass('roundabout-moveable-item') .css('position', 'absolute'); // then measure jquery(this).data('roundabout', { 'startwidth': jquery(this).width(), 'startheight': jquery(this).height(), 'startfontsize': parseint(jquery(this).css('font-size'), 10), 'degrees': degrees }); }); ref.roundabout_updatechildpositions(); }); return this; }; jquery.fn.roundabout_settilt = function(newtilt) { this.each(function(i) { jquery(this).data('roundabout').tilt = newtilt; jquery(this).roundabout_updatechildpositions(); }); if (typeof arguments[1] === 'function') { var callback = arguments[1], ref = this; settimeout(function() { callback(ref); }, 0); } return this; }; jquery.fn.roundabout_setbearing = function(newbearing) { this.each(function(i) { jquery(this).data('roundabout').bearing = jquery.roundabout_tofloat(newbearing % 360, 2); jquery(this).roundabout_updatechildpositions(); }); if (typeof arguments[1] === 'function') { var callback = arguments[1], ref = this; settimeout(function() { callback(ref); }, 0); } return this; }; jquery.fn.roundabout_adjustbearing = function(delta) { delta = jquery.roundabout_tofloat(delta); if (delta !== 0) { this.each(function(i) { jquery(this).data('roundabout').bearing = jquery.roundabout_getbearing(jquery(this)) + delta; jquery(this).roundabout_updatechildpositions(); }); } if (typeof arguments[1] === 'function') { var callback = arguments[1], ref = this; settimeout(function() { callback(ref); }, 0); } return this; }; jquery.fn.roundabout_adjusttilt = function(delta) { delta = jquery.roundabout_tofloat(delta); if (delta !== 0) { this.each(function(i) { jquery(this).data('roundabout').tilt = jquery.roundabout_tofloat(jquery(this).roundabout_get('tilt') + delta); jquery(this).roundabout_updatechildpositions(); }); } if (typeof arguments[1] === 'function') { var callback = arguments[1], ref = this; settimeout(function() { callback(ref); }, 0); } return this; }; jquery.fn.roundabout_animatetobearing = function(bearing) { bearing = jquery.roundabout_tofloat(bearing); var currenttime = new date(); var duration = (typeof arguments[1] == 'undefined') ? null : arguments[1]; var easingtype = (typeof arguments[2] == 'undefined') ? null : arguments[2]; var passeddata = (typeof arguments[3] !== 'object') ? null : arguments[3]; this.each(function(i) { var ref = jquery(this), data = ref.data('roundabout'), timer, easingfn, newbearing; var thisduration = (duration === null) ? data.duration : duration; var thiseasingtype = (easingtype !== null) ? easingtype : data.easing || 'swing'; if (passeddata === null) { passeddata = { timerstart: currenttime, start: jquery.roundabout_getbearing(ref), totaltime: thisduration }; } timer = currenttime - passeddata.timerstart; if (timer < thisduration) { data.animating = 1; if (typeof jquery.easing.def == 'string') { easingfn = jquery.easing[thiseasingtype] || jquery.easing[jquery.easing.def]; newbearing = easingfn(null, timer, passeddata.start, bearing - passeddata.start, passeddata.totaltime); } else { newbearing = jquery.easing[thiseasingtype]((timer / passeddata.totaltime), timer, passeddata.start, bearing - passeddata.start, passeddata.totaltime); } ref.roundabout_setbearing(newbearing, function() { ref.roundabout_animatetobearing(bearing, thisduration, thiseasingtype, passeddata); }); } else { bearing = (bearing < 0) ? bearing + 360 : bearing % 360; data.animating = 0; ref.roundabout_setbearing(bearing); } }); return this; }; jquery.fn.roundabout_animatetodelta = function(delta) { var duration = arguments[1], easing = arguments[2]; this.each(function(i) { delta = jquery.roundabout_getbearing(jquery(this)) + jquery.roundabout_tofloat(delta); jquery(this).roundabout_animatetobearing(delta, duration, easing); }); return this; }; jquery.fn.roundabout_animatetochild = function(childpos) { var duration = arguments[1], easing = arguments[2]; this.each(function(i) { var ref = jquery(this), data = ref.data('roundabout'); if (data.childinfocus !== childpos && data.animating === 0) { var child = jquery(ref.children(data.childselector)[childpos]); ref.roundabout_animateangletofocus(child.data('roundabout').degrees, duration, easing); } }); return this; }; jquery.fn.roundabout_animatetonearbychild = function(passedargs, which) { var duration = passedargs[0], easing = passedargs[1]; this.each(function(i) { var data = jquery(this).data('roundabout'); var bearing = jquery.roundabout_tofloat(360.0 - jquery.roundabout_getbearing(jquery(this))); var period = data.period, j = 0, range; var reflect = data.reflect; var length = jquery(this).children(data.childselector).length; bearing = (reflect === true) ? bearing % 360.0 : bearing; if (data.animating === 0) { // if we're not reflecting and we're moving to next or // we are reflecting and we're moving previous if ((reflect === false && which === 'next') || (reflect === true && which !== 'next')) { bearing = (bearing === 0) ? 360 : bearing; // counterclockwise while (true && j < length) { range = { lower: jquery.roundabout_tofloat(period * j), upper: jquery.roundabout_tofloat(period * (j + 1)) }; range.upper = (j == length - 1) ? 360.0 : range.upper; // adjust for javascript being bad at floats if (bearing <= range.upper && bearing > range.lower) { jquery(this).roundabout_animatetodelta(bearing - range.lower, duration, easing); break; } j++; } } else { // clockwise while (true) { range = { lower: jquery.roundabout_tofloat(period * j), upper: jquery.roundabout_tofloat(period * (j + 1)) }; range.upper = (j == length - 1) ? 360.0 : range.upper; // adjust for javascript being bad at floats if (bearing >= range.lower && bearing < range.upper) { jquery(this).roundabout_animatetodelta(bearing - range.upper, duration, easing); break; } j++; } } } }); return this; }; jquery.fn.roundabout_animatetonextchild = function() { return this.roundabout_animatetonearbychild(arguments, 'next'); }; jquery.fn.roundabout_animatetopreviouschild = function() { return this.roundabout_animatetonearbychild(arguments, 'previous'); }; // moves a given angle to the focus by the shortest means possible jquery.fn.roundabout_animateangletofocus = function(target) { var duration = arguments[1], easing = arguments[2]; this.each(function(i) { var delta = jquery.roundabout_getbearing(jquery(this)) - target; delta = (math.abs(360.0 - delta) < math.abs(0.0 - delta)) ? 360.0 - delta : 0.0 - delta; delta = (delta > 180) ? -(360.0 - delta) : delta; if (delta !== 0) { jquery(this).roundabout_animatetodelta(delta, duration, easing); } }); return this; }; jquery.fn.roundabout_updatechildpositions = function() { this.each(function(i) { var ref = jquery(this), data = ref.data('roundabout'); var infocus = -1; var info = { bearing: jquery.roundabout_getbearing(ref), tilt: data.tilt, stage: { width: math.floor(ref.width() * 0.9), height: math.floor(ref.height() * 0.9) }, animating: data.animating, infocus: data.childinfocus, focusbearingrad: jquery.roundabout_degtorad(data.focusbearing), shape: jquery.roundabout_shape[data.shape] || jquery.roundabout_shape[jquery.roundabout_shape.def] }; info.midstage = { width: info.stage.width / 2, height: info.stage.height / 2 }; info.nudge = { width: info.midstage.width + info.stage.width * 0.05, height: info.midstage.height + info.stage.height * 0.05 }; info.zvalues = { min: data.minz, max: data.maxz, diff: data.maxz - data.minz }; info.opacity = { min: data.minopacity, max: data.maxopacity, diff: data.maxopacity - data.minopacity }; info.scale = { min: data.minscale, max: data.maxscale, diff: data.maxscale - data.minscale }; // update child positions ref.children(data.childselector).each(function(i) { if (jquery.roundabout_updatechildposition(jquery(this), ref, info, i) && info.animating === 0) { infocus = i; jquery(this).addclass('roundabout-in-focus'); } else { jquery(this).removeclass('roundabout-in-focus'); } }); // update status of who is in focus if (infocus !== info.infocus) { jquery.roundabout_triggerevent(ref, info.infocus, 'blur'); if (infocus !== -1) { jquery.roundabout_triggerevent(ref, infocus, 'focus'); } data.childinfocus = infocus; } }); return this; }; //---------------- jquery.roundabout_getbearing = function(el) { return jquery.roundabout_tofloat(el.data('roundabout').bearing) % 360; }; jquery.roundabout_degtorad = function(degrees) { return (degrees % 360.0) * math.pi / 180.0; }; jquery.roundabout_isinfocus = function(el, target) { return (jquery.roundabout_getbearing(el) % 360 === (target % 360)); }; jquery.roundabout_triggerevent = function(el, child, eventtype) { return (child < 0) ? this : jquery(el.children(el.data('roundabout').childselector)[child]).trigger(eventtype); }; jquery.roundabout_tofloat = function(number) { number = math.round(parsefloat(number) * 1000) / 1000; return parsefloat(number.tofixed(2)); }; jquery.roundabout_updatechildposition = function(child, container, info, childpos) { var ref = jquery(child), data = ref.data('roundabout'), out = []; var rad = jquery.roundabout_degtorad((360.0 - ref.data('roundabout').degrees) + info.bearing); // adjust radians to be between 0 and math.pi * 2 while (rad < 0) { rad = rad + math.pi * 2; } while (rad > math.pi * 2) { rad = rad - math.pi * 2; } var factors = info.shape(rad, info.focusbearingrad, info.tilt); // obj with x, y, z, and scale values // correct factors.scale = (factors.scale > 1) ? 1 : factors.scale; factors.adjustedscale = (info.scale.min + (info.scale.diff * factors.scale)).tofixed(4); factors.width = (factors.adjustedscale * data.startwidth).tofixed(4); factors.height = (factors.adjustedscale * data.startheight).tofixed(4); // alter item ref .css('left', ((factors.x * info.midstage.width + info.nudge.width) - factors.width / 2.0).tofixed(1) + 'px') .css('top', ((factors.y * info.midstage.height + info.nudge.height) - factors.height / 2.0).tofixed(1) + 'px') .css('width', factors.width + 'px') .css('height', factors.height + 'px') .css('opacity', (info.opacity.min + (info.opacity.diff * factors.scale)).tofixed(2)) .css('z-index', math.round(info.zvalues.min + (info.zvalues.diff * factors.z))) .css('font-size', (factors.adjustedscale * data.startfontsize).tofixed(2) + 'px') .attr('current-scale', factors.adjustedscale); if (container.data('roundabout').debug === true) { out.push('
'); out.push('child ' + childpos + '
'); out.push('left: ' + ref.css('left') + '
top: ' + ref.css('top') + '
'); out.push('width: ' + ref.css('width') + '
opacity: ' + ref.css('opacity') + '
'); out.push('z-index: ' + ref.css('z-index') + '
font-size: ' + ref.css('font-size') + '
'); out.push('scale: ' + ref.attr('current-scale')); out.push('
'); ref.html(out.join('')); } return jquery.roundabout_isinfocus(container, ref.data('roundabout').degrees); };