Draft-like animated countdown with TweenMax.
A Pen by Dorian Camilleri on CodePen.
Draft-like animated countdown with TweenMax.
A Pen by Dorian Camilleri on CodePen.
<div class="wrap"> | |
<h1>Draft <strong>Countdown</strong></h1> | |
<div class="countdown"> | |
<div class="bloc-time hours" data-init-value="24"> | |
<span class="count-title">Hours</span> | |
<div class="figure hours hours-1"> | |
<span class="top">2</span> | |
<span class="top-back"> | |
<span>2</span> | |
</span> | |
<span class="bottom">2</span> | |
<span class="bottom-back"> | |
<span>2</span> | |
</span> | |
</div> | |
<div class="figure hours hours-2"> | |
<span class="top">4</span> | |
<span class="top-back"> | |
<span>4</span> | |
</span> | |
<span class="bottom">4</span> | |
<span class="bottom-back"> | |
<span>4</span> | |
</span> | |
</div> | |
</div> | |
<div class="bloc-time min" data-init-value="0"> | |
<span class="count-title">Minutes</span> | |
<div class="figure min min-1"> | |
<span class="top">0</span> | |
<span class="top-back"> | |
<span>0</span> | |
</span> | |
<span class="bottom">0</span> | |
<span class="bottom-back"> | |
<span>0</span> | |
</span> | |
</div> | |
<div class="figure min min-2"> | |
<span class="top">0</span> | |
<span class="top-back"> | |
<span>0</span> | |
</span> | |
<span class="bottom">0</span> | |
<span class="bottom-back"> | |
<span>0</span> | |
</span> | |
</div> | |
</div> | |
<div class="bloc-time sec" data-init-value="0"> | |
<span class="count-title">Seconds</span> | |
<div class="figure sec sec-1"> | |
<span class="top">0</span> | |
<span class="top-back"> | |
<span>0</span> | |
</span> | |
<span class="bottom">0</span> | |
<span class="bottom-back"> | |
<span>0</span> | |
</span> | |
</div> | |
<div class="figure sec sec-2"> | |
<span class="top">0</span> | |
<span class="top-back"> | |
<span>0</span> | |
</span> | |
<span class="bottom">0</span> | |
<span class="bottom-back"> | |
<span>0</span> | |
</span> | |
</div> | |
</div> | |
</div> | |
</div> |
// Create Countdown | |
var Countdown = { | |
// Backbone-like structure | |
$el: $('.countdown'), | |
// Params | |
countdown_interval: null, | |
total_seconds : 0, | |
// Initialize the countdown | |
init: function() { | |
// DOM | |
this.$ = { | |
hours : this.$el.find('.bloc-time.hours .figure'), | |
minutes: this.$el.find('.bloc-time.min .figure'), | |
seconds: this.$el.find('.bloc-time.sec .figure') | |
}; | |
// Init countdown values | |
this.values = { | |
hours : this.$.hours.parent().attr('data-init-value'), | |
minutes: this.$.minutes.parent().attr('data-init-value'), | |
seconds: this.$.seconds.parent().attr('data-init-value'), | |
}; | |
// Initialize total seconds | |
this.total_seconds = this.values.hours * 60 * 60 + (this.values.minutes * 60) + this.values.seconds; | |
// Animate countdown to the end | |
this.count(); | |
}, | |
count: function() { | |
var that = this, | |
$hour_1 = this.$.hours.eq(0), | |
$hour_2 = this.$.hours.eq(1), | |
$min_1 = this.$.minutes.eq(0), | |
$min_2 = this.$.minutes.eq(1), | |
$sec_1 = this.$.seconds.eq(0), | |
$sec_2 = this.$.seconds.eq(1); | |
this.countdown_interval = setInterval(function() { | |
if(that.total_seconds > 0) { | |
--that.values.seconds; | |
if(that.values.minutes >= 0 && that.values.seconds < 0) { | |
that.values.seconds = 59; | |
--that.values.minutes; | |
} | |
if(that.values.hours >= 0 && that.values.minutes < 0) { | |
that.values.minutes = 59; | |
--that.values.hours; | |
} | |
// Update DOM values | |
// Hours | |
that.checkHour(that.values.hours, $hour_1, $hour_2); | |
// Minutes | |
that.checkHour(that.values.minutes, $min_1, $min_2); | |
// Seconds | |
that.checkHour(that.values.seconds, $sec_1, $sec_2); | |
--that.total_seconds; | |
} | |
else { | |
clearInterval(that.countdown_interval); | |
} | |
}, 1000); | |
}, | |
animateFigure: function($el, value) { | |
var that = this, | |
$top = $el.find('.top'), | |
$bottom = $el.find('.bottom'), | |
$back_top = $el.find('.top-back'), | |
$back_bottom = $el.find('.bottom-back'); | |
// Before we begin, change the back value | |
$back_top.find('span').html(value); | |
// Also change the back bottom value | |
$back_bottom.find('span').html(value); | |
// Then animate | |
TweenMax.to($top, 0.8, { | |
rotationX : '-180deg', | |
transformPerspective: 300, | |
ease : Quart.easeOut, | |
onComplete : function() { | |
$top.html(value); | |
$bottom.html(value); | |
TweenMax.set($top, { rotationX: 0 }); | |
} | |
}); | |
TweenMax.to($back_top, 0.8, { | |
rotationX : 0, | |
transformPerspective: 300, | |
ease : Quart.easeOut, | |
clearProps : 'all' | |
}); | |
}, | |
checkHour: function(value, $el_1, $el_2) { | |
var val_1 = value.toString().charAt(0), | |
val_2 = value.toString().charAt(1), | |
fig_1_value = $el_1.find('.top').html(), | |
fig_2_value = $el_2.find('.top').html(); | |
if(value >= 10) { | |
// Animate only if the figure has changed | |
if(fig_1_value !== val_1) this.animateFigure($el_1, val_1); | |
if(fig_2_value !== val_2) this.animateFigure($el_2, val_2); | |
} | |
else { | |
// If we are under 10, replace first figure with 0 | |
if(fig_1_value !== '0') this.animateFigure($el_1, 0); | |
if(fig_2_value !== val_1) this.animateFigure($el_2, val_1); | |
} | |
} | |
}; | |
// Let's go ! | |
Countdown.init(); |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script> |
@import "compass/css3"; | |
// Global | |
body { | |
background-color: #f2f1ed; | |
} | |
.wrap { | |
position: absolute; | |
bottom: 0; | |
top: 0; | |
left: 0; | |
right: 0; | |
margin: auto; | |
height: 310px; | |
} | |
a { | |
text-decoration: none; | |
color: #1a1a1a; | |
} | |
// Variables | |
$lato: 'Lato'; | |
// Title | |
h1 { | |
margin-bottom: 60px; | |
text-align: center; | |
font: 300 2.25em $lato; | |
text-transform: uppercase; | |
strong { | |
font-weight: 400; | |
color: #ea4c4c; | |
} | |
} | |
h2 { | |
margin-bottom: 80px; | |
text-align: center; | |
font: 300 0.7em $lato; | |
text-transform: uppercase; | |
strong { | |
font-weight: 400; | |
} | |
} | |
// Countdown | |
.countdown { | |
width: 720px; | |
margin: 0 auto; | |
.bloc-time { | |
float: left; | |
margin-right: 45px; | |
text-align: center; | |
&:last-child { | |
margin-right: 0; | |
} | |
} | |
.count-title { | |
display: block; | |
margin-bottom: 15px; | |
font: normal 0.94em $lato; | |
color: #1a1a1a; | |
text-transform: uppercase; | |
} | |
.figure { | |
position: relative; | |
float: left; | |
height: 110px; | |
width: 100px; | |
margin-right: 10px; | |
background-color: #fff; | |
border-radius: 8px; | |
@include box-shadow(0 3px 4px 0 rgba(0, 0, 0, .2),inset 2px 4px 0 0 rgba(255, 255, 255, .08)); | |
&:last-child { | |
margin-right: 0; | |
} | |
>span { | |
position: absolute; | |
left: 0; | |
right: 0; | |
margin: auto; | |
font: normal 5.94em/107px $lato; | |
font-weight: 700; | |
color: #de4848; | |
} | |
.top, .bottom-back { | |
&:after { | |
content: ""; | |
position: absolute; | |
z-index: -1; | |
left: 0; | |
bottom: 0; | |
width: 100%; | |
height: 100%; | |
border-bottom: 1px solid rgba(0, 0, 0, .1); | |
} | |
} | |
.top { | |
z-index: 3; | |
background-color: #f7f7f7; | |
transform-origin: 50% 100%; | |
-webkit-transform-origin: 50% 100%; | |
@include border-top-radius(10px); | |
@include transform(perspective(200px)); | |
} | |
.bottom { | |
z-index: 1; | |
&:before { | |
content: ""; | |
position: absolute; | |
display: block; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 50%; | |
background-color: rgba(0, 0, 0, .02); | |
} | |
} | |
.bottom-back { | |
z-index: 2; | |
top: 0; | |
height: 50%; | |
overflow: hidden; | |
background-color: #f7f7f7; | |
@include border-top-radius(10px); | |
span { | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
margin: auto; | |
} | |
} | |
.top, .top-back { | |
height: 50%; | |
overflow: hidden; | |
@include backface-visibility(hidden); | |
} | |
.top-back { | |
z-index: 4; | |
bottom: 0; | |
background-color: #fff; | |
-webkit-transform-origin: 50% 0; | |
transform-origin: 50% 0; | |
@include transform(perspective(200px) rotateX(180deg)); | |
@include border-bottom-radius(10px); | |
span { | |
position: absolute; | |
top: -100%; | |
left: 0; | |
right: 0; | |
margin: auto; | |
} | |
} | |
} | |
} |