A 3d carousel with jQuery, how difficult can that be? Not difficult at all, if you know your maths...

So, go back to your school days and remember how the position of a point on a circle with radius of 1 is defined.
And there we are, the beloved sine (sin) and cosine (cos) functions will do this for us, luckily implemented in the JS Math object.

Math.sin( angle );
Math.cos( angle );

You get the angle by dividing the circle into equal segments, depending on the amount of elements in the carousel div. The formula is based on the famous number PI (seldomly known as Ludolph's number).

var genAngle = Math.PI * 2 / defaults.nrEle;

Then multiply the sine of the angle with the x-radius, and the cosine with the y-radius to get a position on the ellipse.
Add x and y to position the whole thing within the surrounding div.

angle = objectCounter * genAngle + count * genSpeed;
var left =  cX + rX * Math.sin( angle );
var top =  cY + rY * Math.cos( angle );

A continuous counter multiplied by a speed factor, in combination with the JS setInterval() function et voilà, your circular movement. Of course the duration of the interval and the speed defines the granularity of the movement, also called "Zuckeln" by insiders :^ -- jerking, for all those non German speakers ...).
If you don't want to annoy the visitors with your furious circle, keep the interval above 20 msec.

setInterval('moveCarousel()',defaults.interval);

So where is the 3D effect, you ask. It comes with the style and a little formula, that defines the width of the surrounding div plotted against the inverse of the y-position on the circle.
Zooming is, by this formula, defined from 50%(top-position) to 100%(bottom-position). What adds to the 3d-impression is the zIndex, which is also defined by the y-position.

zIndex = Math.round(top) * zIndexFactor;
width = imgWidth * ( 3 +  ( top - cY ) / rY ) / 4; // width from 100% to 50%

I let the image divs overlap the central "tooltip" div, so you can see that the top-positioned divs lie behind the tooltip div and the bottom-positioned lie in front of it.

Another problem was the divs moving on a cicular path with their top-left corner, which resulted in a leaning impression. So I subtracted half of the div's width from the left and top position so that it's now centered.

top -= width * .5;
left -= width * .5;

This finally leads us to the moveCarousel function:

moveCarousel = function (){
	var count = defaults.count;
	var rX = defaults.rX;
	var rY = defaults.rY;
	var cX = defaults.cX;
	var cY = defaults.cY;
	var direction = defaults.direction < 0 ? -1 : 1; 
	var imgWidth = defaults.imgWidth;
	var zIndexFactor = defaults.zIndexFactor;
	var angle = 0, left = 0, top = 0, zIndex = 0, width = 0;
	var genSpeed = defaults.speed;
	var genAngle	= Math.PI * 2 / defaults.nrEle;
	var maxHeight = defaults.carousel;
	var objectCounter = 0;
	jQuery.each(defaults.imageDivs, function()
	{
		angle = objectCounter * genAngle + count * genSpeed;
		left =  cX + direction * rX * Math.sin( angle );
		top =  cY + rY * Math.cos( angle );
		zIndex = Math.round(top) * zIndexFactor;
		width = imgWidth * ( 3 +  ( top - cY ) / rY ) / 4; // width from 100% to 50%
		top -= width * .5;
		left -= width * .5;
		jQuery(this).css('top', top + 'px')
			.css('left', left + 'px')
			.css('width', width + 'px')
			.css('zIndex', zIndex);
		objectCounter++;
	});
	defaults.count++;
	if(defaults.count>1000000000000) defaults.count = 0;
}

Integrated mouseover functionality for all the buttons (change image-src by regexp),  and a "tooltip" div that shows in the middle of the circle. You can also see that I used chaining (see highlighting) a lot.

defaults.imageDivs.bind('mouseenter', function(evt) {
		hoverIn(this);
		var pHeader = '

' + jQuery('input',this)[0].value; + '

'; var pText = '

' + jQuery('input',this)[1].value; + '

'; jQuery('.jq3d_display', this.parent).append(pHeader).append(pText).show(); if(defaults.buttonPP === 0) { clearInterval(defaults.carousel_timer); } }) .bind('mouseout', function(evt) { hoverOut(this); jQuery('.jq3d_display', this.parent).hide().empty(); if(defaults.buttonPP === 0) { clearInterval(defaults.carousel_timer); defaults.carousel_timer = setInterval('moveCarousel()',defaults.interval); } });

Then I added a play-/pause-Button which clears or sets an interval for the moveCarousel function. By the way, I'd recommend storing the interval, respectively its reference, so it can be deleted. Also, if you have stored the interval, always remove it before setting it again, so that it can never be set twice, which results in a strange behavior. This happens because sometimes the clear interval is not called, if the "mouseenter" event doesn't fire (if too fast).

jQuery('.jq3d_buttonPP', this).bind('click', function(evt) {
		if(defaults.buttonPP === 0) {
			clearInterval(defaults.carousel_timer);
			defaults.buttonPP = 1;
			replaceImg( this, '^(.*)pause_hover\.png$' , '$1play_hover.png' );
		} else {
			clearInterval(defaults.carousel_timer);
			defaults.carousel_timer = setInterval('moveCarousel()',defaults.interval);
			defaults.buttonPP = 0;
			replaceImg( this, '^(.*)play_hover\.png$' , '$1pause_hover.png' );
		}
	})
	.bind('mouseenter', function(evt) { hoverIn(this); })
	.bind('mouseout', function(evt) { hoverOut(this); });

Next I added another button for direction change, which sets a variable either to -1 or 1. This variable is included in the formula for the x position.

moveCarousel = function (){
...
left =  cX + direction * rX * Math.sin( angle );
...
};

...

jQuery('.jq3d_buttonCD', this).bind('click', function(evt) {
		if(defaults.direction < 0) {
			defaults.direction = 1;
			replaceImg( this, '^(.*)clock_hover\.png$' , '$1counterclock_hover.png' );
		} else {
			defaults.direction = -1;
			replaceImg( this, '^(.*)counterclock_hover\.png$' , '$1clock_hover.png' );
		}
	})
	.bind('mouseenter', function(evt) { hoverIn(this); })
	.bind('mouseout', function(evt) { hoverOut(this); });

I wrapped up the whole code in an anonymous closure and put all the binding instructions and init functions in a "document ready" function.

Start the carousel by passing to the now built-in function the initialization object.

(function($) { //create closure
	$.fn.jq3dcarousel = function (defaults) {
		...
		jQuery(document).ready(function() {
			defaults.carousel = jQuery('.jq3d_carousel', this);
			defaults.imageDivs = jQuery('.jq3d_carousel div', this);
			defaults.nrEle = defaults.imageDivs.length;

			if(defaults.buttonPP === 0) {  // runOnLoad
				replaceImg( jQuery('.jq3d_buttonPP', this), '^(.*)play\.png$' , '$1pause.png' );
				clearInterval(defaults.carousel_timer);
				defaults.carousel_timer = setInterval('moveCarousel()',defaults.interval);
			} else {
				moveCarousel(); // set first Position
			}
			defaults.imageDivs.show();
			...
		});
	} // end jQ3dcarousel fct
})(jQuery) // end closure

jQuery('#jq3d_carousel_frame').jq3dcarousel({ // call the carousel
	count:					0, // continuing counter for sin/cos
	speed:					0.02, // defaultSpeed
	rX:						160, // ellipse radius x-axis
	rY:						80, // ellipse radius y-axis
	cX:						280, // ellipse center x-axis
	cY:						170, // ellipse center y-axis
	direction:				-1, // negative == clockwise, positive counter-clockwise
	imgWidth:			43, // constant factor for image width (size)
	zIndexFactor:		10, // multiplying factor for z-axis (multiplied by position top)
	imageDivs:			'', // placeholder for the divs in the carousel
	nrEle:					0, // number of elements
	carousel:				'', // the carousel div
	interval:				20, // time in ms for setInterval()
	carousel_timer:	'', // placeholder for the timer
	buttonPP:			1 // Play/Pause-Button 
});

So, what do you think? Feel free to ask questions or leave comments in the section below.