Skip to content

Instantly share code, notes, and snippets.

@PhillipSenn
Created June 8, 2019 01:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PhillipSenn/4a0fae1c7f9cef28961ce2236799a0e5 to your computer and use it in GitHub Desktop.
Save PhillipSenn/4a0fae1c7f9cef28961ce2236799a0e5 to your computer and use it in GitHub Desktop.
Flip clock & countdown, Vue
<script>console.clear();</script>
Vue.filter('zerofill', function (value) {
//value = ( value < 0 ? 0 : value );
return ( value < 10 && value > -1 ? '0' : '' ) + value;
});
var Tracker = Vue.extend({
template: `
<span v-show="show" class="flip-clock__piece">
<span class="flip-clock__card flip-card">
<b class="flip-card__top">{{current | zerofill}}</b>
<b class="flip-card__bottom" data-value="{{current | zerofill}}"></b>
<b class="flip-card__back" data-value="{{previous | zerofill}}"></b>
<b class="flip-card__back-bottom" data-value="{{previous | zerofill}}"></b>
</span>
<span class="flip-clock__slot">{{property}}</span>
</span>`,
props: ['property','time'],
data: () => ({
current: 0,
previous: 0,
show: false
}),
events: {
time(newValue) {
if ( newValue[this.property] === undefined ) {
this.show = false;
return;
}
var val = newValue[this.property];
this.show = true;
val = ( val < 0 ? 0 : val );
if ( val !== this.current ) {
this.previous = this.current;
this.current = val;
this.$el.classList.remove('flip');
void this.$el.offsetWidth;
this.$el.classList.add('flip');
}
}
},
});
var el = document.createElement('div');
document.body.appendChild(el);
var Countdown = new Vue({
el: el,
template: `
<div class="flip-clock" data-date="2017-02-11" @click="update">
<tracker
v-for="tracker in trackers"
:property="tracker"
:time="time"
v-ref:trackers
></tracker>
</div>
`,
props: ['date','callback'],
data: () => ({
time: {},
i: 0,
trackers: ['Days', 'Hours','Minutes','Seconds'] //'Random',
}),
components: {
Tracker
},
beforeDestroy(){
if ( window['cancelAnimationFrame'] ) {
cancelAnimationFrame(this.frame);
}
},
watch: {
'date': function(newVal){
this.setCountdown(newVal);
}
},
ready() {
if ( window['requestAnimationFrame'] ) {
this.setCountdown(this.date);
this.callback = this.callback || function(){};
this.update();
}
},
methods: {
setCountdown(date){
if ( date ) {
this.countdown = moment(date, 'YYYY-MM-DD HH:mm:ss');
} else {
this.countdown = moment().endOf('day'); //this.$el.getAttribute('data-date');
}
},
update() {
this.frame = requestAnimationFrame(this.update.bind(this));
if ( this.i++ % 10 ) { return; }
var t = moment(new Date());
// Calculation adapted from https://www.sitepoint.com/build-javascript-countdown-timer-no-dependencies/
if ( this.countdown ) {
t = this.countdown.diff(t);
//t = this.countdown.diff(t);//.getTime();
//console.log(t);
this.time.Days = Math.floor(t / (1000 * 60 * 60 * 24));
this.time.Hours = Math.floor((t / (1000 * 60 * 60)) % 24);
this.time.Minutes = Math.floor((t / 1000 / 60) % 60);
this.time.Seconds = Math.floor((t / 1000) % 60);
} else {
this.time.Days = undefined;
this.time.Hours = t.hours() % 13;
this.time.Minutes = t.minutes();
this.time.Seconds = t.seconds();
}
this.time.Total = t;
this.$broadcast('time',this.time);
return this.time;
}
}
});
<script src="https://cdn.rawgit.com/vuejs/vue/v1.0.24/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.1/moment.min.js"></script>
html { height: 100%; }
body {
min-height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #EEE;
}
.flip-clock {
text-align: center;
perspective: 600px;
margin: 0 auto;
*,
*:before,
*:after { box-sizing: border-box; }
}
.flip-clock__piece {
display: inline-block;
margin: 0 0.2vw;
@media (min-width: 1000px) {
margin: 0 5px;
}
}
.flip-clock__slot {
font-size: 1rem;
line-height: 1.5;
display: block;
/*
//position: relative;
//top: -1.6em;
z-index: 10;
//color: #FFF;
*/
}
@halfHeight: 0.72em;
@borderRadius: 0.15em;
.flip-card {
display: block;
position: relative;
padding-bottom: @halfHeight;
font-size: 2.25rem;
line-height: 0.95;
}
@media (min-width: 1000px) {
.flip-clock__slot { font-size: 1.2rem; }
.flip-card { font-size: 3rem; }
}
/*////////////////////////////////////////*/
.flip-card__top,
.flip-card__bottom,
.flip-card__back-bottom,
.flip-card__back::before,
.flip-card__back::after {
display: block;
height: @halfHeight;
color: #ccc;
background: #222;
padding: 0.23em 0.25em 0.4em;
border-radius: @borderRadius @borderRadius 0 0;
backface-visiblity: hidden;
transform-style: preserve-3d;
width: 1.8em;
}
.flip-card__bottom,
.flip-card__back-bottom {
color: #FFF;
position: absolute;
top: 50%;
left: 0;
border-top: solid 1px #000;
background: #393939;
border-radius: 0 0 @borderRadius @borderRadius;
pointer-events: none;
overflow: hidden;
z-index: 2;
}
.flip-card__back-bottom {
z-index: 1;
}
.flip-card__bottom::after,
.flip-card__back-bottom::after {
display: block;
margin-top: -@halfHeight;
}
.flip-card__back::before,
.flip-card__bottom::after,
.flip-card__back-bottom::after {
content: attr(data-value);
}
.flip-card__back {
position: absolute;
top: 0;
height: 100%;
left: 0%;
pointer-events: none;
}
.flip-card__back::before {
position: relative;
overflow: hidden;
z-index: -1;
}
.flip .flip-card__back::before {
z-index: 1;
animation: flipTop 0.3s cubic-bezier(.37,.01,.94,.35);
animation-fill-mode: both;
transform-origin: center bottom;
}
.flip .flip-card__bottom {
transform-origin: center top;
animation-fill-mode: both;
animation: flipBottom 0.6s cubic-bezier(.15,.45,.28,1);// 0.3s;
}
@keyframes flipTop {
0% {
transform: rotateX(0deg);
z-index: 2;
}
0%, 99% {
opacity: 1;
}
100% {
transform: rotateX(-90deg);
opacity: 0;
}
}
@keyframes flipBottom {
0%, 50% {
z-index: -1;
transform: rotateX(90deg);
opacity: 0;
}
51% {
opacity: 1;
}
100% {
opacity: 1;
transform: rotateX(0deg);
z-index: 5;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment