Problem – Fast, browser compatible, easily maintainable
Solution – CSS3 Animation Wrapper with Fallback
The Carousel feature of Zoosk animates in a candidate that may be appealing to the user. The user can either click Yes, Maybe, or No to express interest, and on press of the button, the candidate animates out and a new one comes in, similar to a spinning carousel.
The CSS that we used includes basic CSS3 animation, translate and scale. Ease-out is used to create smooth effect and forwards is used to keep the computed style once the animation is finished.
//Animating a user in | |
.carousel-animate-in { | |
.round-pic-frame { /* Bubble displaying the users photo */ | |
-webkit-animation: carousel-user-photo-in .6s ease-out forwards; | |
-moz-animation: carousel-user-photo-in .6s ease-out forwards; | |
-o-animation: carousel-user-photo-in .6s ease-out forwards; | |
animation: carousel-user-photo-in .6s ease-out forwards; | |
} | |
//... more animations | |
} | |
@-webkit-keyframes carousel-user-photo-in { | |
0% { | |
-webkit-transform: translate(-150%, 25%) scale(.1); | |
} | |
100% { | |
-webkit-transform: translate(0, 0) scale(1); | |
} | |
} | |
//Animating a user out | |
.carousel-animate-out { | |
.round-pic-frame { /* Bubble displaying the users photo */ | |
-webkit-animation: carousel-user-photo-out .2s ease-in .05s forwards; | |
-moz-animation: carousel-user-photo-out .2s ease-in .05s forwards; | |
-o-animation: carousel-user-photo-out .2s ease-in .05s forwards; | |
animation: carousel-user-photo-out .2s ease-in .05s forwards; | |
} | |
} |
For browsers that support CSS3, we just add the .carousel-animate-in class property to the Carousel bubble element in Javascript when the Carousel page loads and this will cause the bubble to grow and move to the center of the page from the left.
bubbleEl.className = 'carousel-animate-in'; |
Once this class is added, the browser starts the animation and the animation ends when the user bubble is focused on the center of the page.
Animating out a user bubble is done in much the same way:
bubbleEl.className = 'carousel-animate-out' |
When a user clicks on a button, we need to sequentially animate the current bubble out and a new bubble in. This is done by listening to the animation end event:
var animationEndEventString = userAgent == WEBKIT ? 'webkitAnimationEnd' : | |
(goog.userAgent.OPERA ? 'oAnimationEnd' : 'animationend'); | |
bubbleEl.addEventListener(animationEndEventString, handleAnimationEnd); | |
function handleAnimationEnd() { | |
//Update model | |
//Animate in next bubble | |
nextBubbleEl.className = 'carousel-animate-in' | |
} | |
While this works, the code relies on knowing the animation name and also setting the class name to perform the animation. To simplify this, we can create a Javascript class called CssAnimationClass. The code has been simplified for demonstration purposes and you can use your preferred method of creating Javascript classes.
function CssAnimationClass(element, cssClassName, callback) { | |
this.animationEndEventString = userAgent == WEBKIT ? 'webkitAnimationEnd' : | |
(goog.userAgent.OPERA ? 'oAnimationEnd' : 'animationend'); | |
this.element = element; | |
this.cssClassName = cssClassName; | |
this.callback = callback; | |
} | |
CssAnimationClass.prototype.play = function() { | |
this.element.addEventListener(this.animationEndEventString, this.handleAnimationEnd.bind(this)) //Function.prototype.bind() is supported from Javascript 1.8.5 | |
this.element.className = this.cssClassName; | |
} | |
CssAnimationClass.prototype.handleAnimationEnd = function() { | |
this.callback(); | |
} |
Then animating an element out simplifies to:
var carouselOutAnimation = new CssAnimationClass(bubbleEl, 'carousel-animate-out', handleCarouselAnimateOutEndEvent); | |
carouselOutAnimation.play(); | |
function handleCarouselAnimateOutEndEvent() { | |
//Handle carousel animate out end event | |
} |
The function handleCarouselAnimateOutEndEvent will run when the animation ends and we handle animating a Carousel candidate in.
There’s still another problem, supporting non-CSS3 browsers. This is where we have a fallback to use Javascript animations and cannot add the CSS animation class property.
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; | |
if (isAnimationSupported()) { | |
//Create CssAnimationClass and play | |
} else { | |
//Fallback to js animations | |
requestAnimationFrame(animateCarouselBubbleIn) | |
} | |
var start = null; | |
function animateCarouselBubbleIn(timestamp) { | |
var progress; | |
if (start === null) { | |
start = timestamp; | |
} | |
progress = Math.min((timestamp - start) / 600, 1); //Percent progress | |
bubbleEl.style.left = (1 - progress) * -150 + "%"; | |
bubbleEl.style.top = (1 - progress) * 25 + "%"; | |
bubbleEl.style.height = .9 * (progress * origHeight) + .1 * origHeight + "%" //Add .1 since the CSS3 animation starts from scale .1 | |
bubbleEl.style.width = .9 * (progress * origWidth) + .1 * origHeight + "%" | |
if (progress < 1) { | |
requestAnimationFrame(animateCarouselBubbleIn); | |
} | |
} |
The animateCarouselBubbleIn method is used as a callback to the browser window’s requestAnimationFrame method. This method updates the bubbleEl element style at regular intervals and finishes after .6 seconds, like the CSS3 animation.