jQuery.fn.extend({ create3DCarousel: function (/*options*/) { //config var options = { vanishingPoint: arguments[0].vanishingPoint, speed: arguments[0].speed } //3DCarousel class $3DCarousel = function (jEl) { var parent = this; //Extra Class /* NODE */ $Node = function (/*node, x, y, z*/) { var self = this; /*fields*/ this.node = arguments[0].node; //DOM node this.nodeIMG = { //CONST width: self.node.find("img:first").width(), height: self.node.find("img:first").height() } this.x = arguments[0].x; this.y = arguments[0].y; //CONST this.z = arguments[0].z; this.angle_TO_FRONT = arguments[0].angle_TO_FRONT; this.depth = 10000 - Math.round(Math.abs(this.z)); this.timer; /*methods*/ //calculate the item dimension relate to its position(z-depth) this.calculate = function () { /*caculation*/ //Positioning var sinX = Math.cos((-90-this.angle_TO_FRONT)*Math.PI/180); var cosX = Math.sin((-90-this.angle_TO_FRONT)*Math.PI/180); this.z = parent.Radius*(1+cosX); this.depth = 10000 - Math.round(Math.abs(this.z)); this.scalingRatio = 1 - this.z/parent.vanishingPoint; this.x = parent.Radius*sinX/this.scalingRatio; //Scaling this.surfaceScalingRatio = Math.pow(this.scalingRatio, 2); //LIs this.node.width( Math.round( this.surfaceScalingRatio*this.nodeIMG.width ) ); this.node.height( Math.round( this.surfaceScalingRatio*this.nodeIMG.height ) ); //IMG this.node.find("img:first").css("width", Math.round( this.surfaceScalingRatio*this.nodeIMG.width ) ); this.node.find("img:first").css("height", Math.round( this.surfaceScalingRatio*this.nodeIMG.height ) ); } //output to browser: coords of viewport is top:0, left:0 this.outputToScreen = function () { var opacity = Math.round(Math.pow(this.depth/10000, 40)*100); this.node.css({ left: parent.Radius + this.x - this.node.find("img:first").width()/2 + "px", top: this.y - this.node.find("img:first").height()/2 + "px", zIndex: this.depth }); this.node.find("img:first").css({ opacity: opacity/100 }); } /* /* choose CLOCWISE as the DEFAULT ROTATION in case /* rotate LEFT OR RIGHT are both possible */ //CORE function to move NODE : move node to any angle value this.moveNodeToAngle = function (angle) { var node = $(this.node); //angle_TO_FRONT + offset*i until = |rangeAngle| //with i = rangeAngle/|rangeAngle| this.timer = setInterval(function () { if ( self.angle_TO_FRONT >= 180 ) { self.angle_TO_FRONT -= 360; } if ( self.angle_TO_FRONT <= -180 ) { self.angle_TO_FRONT += 360; } var rangeAngle = Math.abs(angle - self.angle_TO_FRONT); if ( rangeAngle > 180 ) { rangeAngle = -angle + self.angle_TO_FRONT; } else if ( rangeAngle < 180 ) { rangeAngle = angle - self.angle_TO_FRONT; } else { rangeAngle = Math.abs(angle - self.angle_TO_FRONT) } var i = rangeAngle/Math.abs(rangeAngle); if ( self.angle_TO_FRONT == angle ) { parent.timerControl.collectGarbageTimer(); clearInterval(self.timer); } else { if ( Math.abs(angle-self.angle_TO_FRONT) < parent.speed ) { restAngle = Math.abs(angle-self.angle_TO_FRONT); self.angle_TO_FRONT += restAngle*i; } else { self.angle_TO_FRONT += parent.speed*i; } self.calculate(); self.outputToScreen(); } }, 1); parent.timerControl.groupTimer.push(this.timer); } //node event this.node.find("a:first").bind("click", function (evt) { if ( self.angle_TO_FRONT != 0 && self.angle_TO_FRONT != 360 ) { parent.queue.rotateToFront(self); } return false; }); } /* QUEUE */ $Queue = function (/*$NodeList:Array*/) { var self = this; /*field*/ this.queue = typeof(arguments[0]) != "undefined" ? arguments[0] : []; this.head = this.queue.length > 0 ? this.queue[0] : null; /*methods*/ //return the number of items in queue this.length = function (item/*type:$Node*/) { return this.queue.length; } //return the first item out of the queue this.serve = function () { var returnItem = this.queue[0]; this.queue.splice(0,1); return returnItem; } //append item to the end of a queue, no return this.append = function (item/*type:$Node*/) { this.queue.push(item); } //prepend item to the front of a queue, no return this.prepend = function (item/*type:$Node*/) { var swapArr = new Array(); swapArr.push(item); for ( var i = 0 ; i < this.queue ; i++ ) { swapArr.push(this.queue[i]); } this.queue = swapArr; } //refresh angle of node in queue and clear timer control this.refresh = function (callback) { if ( this.head != null ) { /*call external function*/ this.head.node.find("a:first").unbind("click", doExtraWork); /*end.call external function*/ } parent.timerControl.clear(); callback(); } //output the queue's node to browser this.show = function (item/*type:$Node*/) { for ( var i = 0 ; i < this.queue.length ; i++ ) { this.queue[i].outputToScreen(); } } //rotate queue by angle this.rotateToFront = function (item/*type:$Node*/) { if ( parent.timerControl.isClear() && parent.ready ) { this.refresh(function () { self.head = item; var offsetAngle = item.angle_TO_FRONT; for ( var i = 0 ; i < self.queue.length ; i++ ) { var angle = self.queue[i].angle_TO_FRONT - offsetAngle; if ( angle > 180 ) { angle -= 360; } else if ( angle <= -180 ) { angle += 360; } self.queue[i].moveNodeToAngle(angle); } }); } } //rotate queue CW this.rotateCW = function () { if ( parent.timerControl.isClear() && parent.ready ) { this.refresh(function () { var offsetAngle = parent.angleX; for ( var i = 0 ; i < self.queue.length ; i++ ) { var angle = self.queue[i].angle_TO_FRONT + offsetAngle; if ( angle > 180 ) { angle -= 360; } else if ( angle <= -180 ) { angle += 360; } if ( angle == 0 || angle == 360) { self.head = self.queue[i]; } self.queue[i].moveNodeToAngle(angle); } }); } } //rotate queue CCW this.rotateCCW = function () { if ( parent.timerControl.isClear() && parent.ready ) { this.refresh(function () { var offsetAngle = -parent.angleX; for ( var i = 0 ; i < self.queue.length ; i++ ) { var angle = self.queue[i].angle_TO_FRONT + offsetAngle; if ( angle > 180 ) { angle -= 360; } else if ( angle <= -180 ) { angle += 360; } if ( angle == 0 || angle == 360 ) { self.head = self.queue[i]; } self.queue[i].moveNodeToAngle(angle); } }); } } } /* TIMER CONTROL */ $TimerControl = function () { this.groupTimer = new Array(); this.garbageTimer = 0; /*methods*/ //Clear TimerControl this.clear = function () { for ( var i = 0 ; i < this.groupTimer.length ; i++ ) { clearInterval(this.groupTimer[i]); } this.groupTimer = new Array(); this.garbageTimer = 0; } //Collect finished timer this.collectGarbageTimer = function () { this.garbageTimer++; if ( this.isClear() ) { parent.callback(); } } //isClear: all timer have been collected this.isClear = function () { return this.groupTimer.length == this.garbageTimer; } } //Extends function jQuery.fn.extend({ traverse: function (i) { var startItem = this.next(); var endItem = typeof(i) == "undefined" ? null : i; var regExp; var items = new Array(); if (endItem != null) { do { items.push(startItem); startItem = startItem.next(); regExp = startItem.attr("class") != "" ? new RegExp(startItem.attr("class"), "g") : null } while ( regExp == null || !(regExp).test(endItem.attr("class")) ) } else { do { items.push(startItem); startItem = startItem.next() } while ( startItem.html() != null ) } return items; } }); //MAIN this.vanishingPoint = options.vanishingPoint; this.speed = options.speed; this.listItem = jEl; var classes = this.listItem.attr("class").split(" "); for ( var i = 0 ; i < classes.length ; i++ ) { if ( classes[i].match(/Theme_/) ) { var theme = "Carousel3D_" + classes[i]; } else { var theme = "Carousel3D_Default"; } } this.container = $("
"); this.listItem.before(this.container); this.listItem.appendTo(this.container); //Controler this.controller = $( "
\n" + " " + " " + "
" ); this.controller.appendTo(this.container); //Loading Container this.loadingContainer = $("

"); this.loadingContainer.appendTo(this.container); this.loadingContainer.css({ width: this.listItem.outerWidth(), height: this.listItem.outerHeight(), position: "absolute", top: 0, left: parent.listItem.get(0).offsetLeft, zIndex: 2000 }); this.ready = false; this.Radius = this.listItem.outerWidth()/2; this.Y_BaseLine = this.listItem.outerHeight()/2; this.onLoadComplete = function () { /*call external function*/ this.queue.head.node.find("a:first").bind("click", doExtraWork); //showBenefit(this.queue.head.node.find("a:first").attr("rel"));; setTitle(this.queue.head.node.find("img:first").attr("alt")); /*end.call external function*/ } this.callback = function () { /*call external function*/ this.queue.head.node.find("a:first").bind("click", doExtraWork); showBenefit(this.queue.head.node.find("a:first").attr("rel")); setTitle(this.queue.head.node.find("img:first").attr("alt")); /*end.call external function*/ } //Timer this.timerControl = new $TimerControl(); //set up list parent.listItem.children("li").css({ opacity: 0 }); //setup items in list this.listItem.children("li").each(function () { var item = $(this); item.css({ position: "absolute" }) }); //set up circle var items = this.listItem.children("li"); var images = new Array(); this.angleX = 360/items.length; //in DEGREE //Distribute item onto the circle, except FRONT_NODE (first item) this.queue = new $Queue(); for ( var i = 0 ; i < items.length ; i++ ) { var angle = this.angleX*i <= 180 ? this.angleX*i : this.angleX*i - 360; var sinX = +Math.cos((90+angle)*Math.PI/180); var cosX = -Math.sin((90+angle)*Math.PI/180); var NODE = new $Node({ node: $(items[i]), x: this.Radius*sinX, y: this.Y_BaseLine, z: this.Radius*(1+cosX), angle_TO_FRONT: angle }); //caculation NODE.calculate(); //append to queue this.queue.append(NODE); //events to FRONT_NODE if ( i == 0 ) { this.queue.head = NODE; } //images list images.push($(items[i]).find("img:first").attr("src")); } //output to screen new PreloadImages(images, function () { parent.loadingContainer.fadeOut("slow", function() { parent.listItem.children("li").animate({ opacity: 1 }); }); parent.ready = true; parent.queue.show(); parent.onLoadComplete(); } , this.loadingContainer.find("p:first").get(0)); //events: NEXT and PREV this.controller.find(".Next:first").bind("click", function () { parent.queue.rotateCW(); //CW = clockwise return false; }); this.controller.find(".Prev:first").bind("click", function () { parent.queue.rotateCCW(); //CCW = counter-clockwise return false; }); } //setup $(this).each(function () { new $3DCarousel($(this)); }) } }); function doExtraWork () { //onclick event on FRONT NODE showBenefit($(this).attr("rel")); } function setTitle (title/*String*/) { $("#cardnameHolder").html(title); } function hideBenefit () { $("#benefitContent li.Fake").hide(); if ( typeof(arguments[0]) != "undefined" && arguments[0] ) { //CLOSE $("#benefitContent li.Fake").remove(); $("#benefitContent li.FlyIn").animate({ left: "-2000px" }, "normal", "swing", function () { $("#benefitContent li:not(.FlyIn)").css({ left: "-2000px" }); }); $("#benefitContent li.FlyIn").removeClass("FlyIn"); } else { $("#benefitContent li.FlyIn").animate({ opacity: 0 }, "normal", "swing", function () { $("#benefitContent li.FlyIn").removeClass("FlyIn"); }); } } function showBenefit (str/*String*/) { var activeBenefitName = str; if ( $("li#" + activeBenefitName).hasClass("FlyIn") ) { //same programme $("#benefitContent li.Fake").css({ opacity: 100, display: "block" }); $("li#" + activeBenefitName).animate({ opacity: 0 }, "normal", "swing", function () { $("li#" + activeBenefitName).animate({ opacity: 100 }, "normal", "swing"); }); } else { if ( $("#benefitContent li.FlyIn").length > 0 ) { hideBenefit(); } $("li#" + activeBenefitName).animate({ left: 0, opacity: 100 }, "normal", "swing", function () { $("li#" + activeBenefitName).addClass("FlyIn"); $("#benefitContent li:not(.FlyIn)").css({ opacity: 0, left: 0 }); if ( $("#benefitContent li.Fake").length <= 0 ) { $("#benefitContent").append("
  • "); $("#benefitContent li.Fake").css({ opacity: 100, display: "none" }); } }); } }