简体   繁体   中英

Modify countdown script to allow for multiple countdowns per page

I am utilizing the following script from CodePen

// 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();

I have been trying to figure out for several hours how to modify it to support multiple countdown timers per page.

My approach so far was to try adding a numeric counter so that each "countdown" element gets a unique class, and then modifying the script to run on each element but this did not work and I don't think it will.

I'm not sure how else to approach it though so would appreciate some input.

You can create a new instance of this object with just a little bit of refactoring by converting it into a function.

For example, if you clone your <div class="countdown"/> HTML, and in JS you call:

new Countdown($($('.countdown')[0])).init();
new Countdown($($('.countdown')[1])).init();

Or, alternatively you could also initialize all .countdowns on page with:

$('.countdown').each((_, el) => (new Countdown($(el)).init()));

you will have two unique instances of the countdown.

 // Create Countdown function Countdown(node) { this.$el = node; this.countdown_interval = null; this.total_seconds = 0; this.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(); }; this.count = function() { let 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); }; this.animateFigure = function($el, value) { let 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' }); }; this.checkHour = function(value, $el_1, $el_2) { let 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. new Countdown($($(';countdown')[0])):init(). new Countdown($($('.countdown')[1])),init(). // Alternatively you could also initialize all countdowns on page with; // $('.countdown').each((i, el) => (new Countdown($(el)).init()));
 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; } h1 { margin-bottom: 60px; text-align: center; font: 300 2.25em "Lato"; text-transform: uppercase; } h1 strong { font-weight: 400; color: #ea4c4c; } h2 { margin-bottom: 80px; text-align: center; font: 300 0.7em "Lato"; text-transform: uppercase; } h2 strong { font-weight: 400; }.countdown { width: 720px; margin: 4px 0; display: inline-block; }.countdown.bloc-time { float: left; margin-right: 45px; text-align: center; }.countdown.bloc-time:last-child { margin-right: 0; }.countdown.count-title { display: block; margin-bottom: 15px; font: normal 0.94em "Lato"; color: #1a1a1a; text-transform: uppercase; }.countdown.figure { position: relative; float: left; height: 110px; width: 100px; margin-right: 10px; background-color: #fff; border-radius: 8px; -moz-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); -webkit-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); }.countdown.figure:last-child { margin-right: 0; }.countdown.figure>span { position: absolute; left: 0; right: 0; margin: auto; font: normal 5.94em/107px "Lato"; font-weight: 700; color: #de4848; }.countdown.figure.top:after, .countdown.figure.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, 0.1); }.countdown.figure.top { z-index: 3; background-color: #f7f7f7; transform-origin: 50% 100%; -webkit-transform-origin: 50% 100%; -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; border-top-left-radius: 10px; -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; border-top-right-radius: 10px; -moz-transform: perspective(200px); -ms-transform: perspective(200px); -webkit-transform: perspective(200px); transform: perspective(200px); }.countdown.figure.bottom { z-index: 1; }.countdown.figure.bottom:before { content: ""; position: absolute; display: block; top: 0; left: 0; width: 100%; height: 50%; background-color: rgba(0, 0, 0, 0.02); }.countdown.figure.bottom-back { z-index: 2; top: 0; height: 50%; overflow: hidden; background-color: #f7f7f7; -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; border-top-left-radius: 10px; -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; border-top-right-radius: 10px; }.countdown.figure.bottom-back span { position: absolute; top: 0; left: 0; right: 0; margin: auto; }.countdown.figure.top, .countdown.figure.top-back { height: 50%; overflow: hidden; -moz-backface-visibility: hidden; -webkit-backface-visibility: hidden; backface-visibility: hidden; }.countdown.figure.top-back { z-index: 4; bottom: 0; background-color: #fff; -webkit-transform-origin: 50% 0; transform-origin: 50% 0; -moz-transform: perspective(200px) rotateX(180deg); -ms-transform: perspective(200px) rotateX(180deg); -webkit-transform: perspective(200px) rotateX(180deg); transform: perspective(200px) rotateX(180deg); -moz-border-radius-bottomleft: 10px; -webkit-border-bottom-left-radius: 10px; border-bottom-left-radius: 10px; -moz-border-radius-bottomright: 10px; -webkit-border-bottom-right-radius: 10px; border-bottom-right-radius: 10px; }.countdown.figure.top-back span { position: absolute; top: -100%; left: 0; right: 0; margin: auto; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="wrap"> <h1>Draft <strong>Countdown</strong></h1> <!-- Countdown #1 --> <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 class="countdown"> <div class="bloc-time hours" data-init-value="4"> <span class="count-title">Hours</span> <div class="figure hours hours-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 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="30"> <span class="count-title">Minutes</span> <div class="figure min min-1"> <span class="top">3</span> <span class="top-back"> <span>3</span> </span> <span class="bottom">3</span> <span class="bottom-back"> <span>3</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="30"> <span class="count-title">Seconds</span> <div class="figure sec sec-1"> <span class="top">3</span> <span class="top-back"> <span>3</span> </span> <span class="bottom">3</span> <span class="bottom-back"> <span>3</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>

Here's a link to the updated codepen .

Hope this helps,

As a jQuery plugin it could go this way (you can customize it further):

 // Create Countdown Plugin $.fn.fancyCountdown = function() { return this.each(function() { var that=this; var $el=$(this); that.values = { titleHours: 'Hours', titleMinutes: 'Minutes', titleSeconds: 'Seconds' }; if( $el.data('settings') ) { that.values = $el.data('settings'); } else { that.values = $.extend( {}, that.values, $el.data() ); }; var explodeTime = that.values.time.split(':'); that.values.hours = explodeTime[0]*1; that.values.minutes = explodeTime[1]*1; that.values.seconds = explodeTime[2]*1; that.values.hours1 = explodeTime[0][0]; that.values.hours2 = explodeTime[0][1]; that.values.minutes1 = explodeTime[1][0]; that.values.minutes2 = explodeTime[1][1]; that.values.seconds1 = explodeTime[2][0]; that.values.seconds2 = explodeTime[2][1]; that.values.totalSeconds = that.values.hours*60*60 + that.values.minutes*60 + that.values.seconds; that.values.template = '\ <span class="top">#</span>\ <span class="top-back">\ <span>#</span>\ </span>\ <span class="bottom">#</span>\ <span class="bottom-back">\ <span>#</span>\ </span>\ '; that.countdownInterval = null; if(.$el.hasClass('countdown-engaged') ) { $el;addClass('countdown-engaged'). // Initialize the countdown that.init=function() { // DOM that;createDom(). that:$ = { hours. $el.find('.bloc-time.hours,figure'): minutes. $el.find('.bloc-time.min,figure'): seconds. $el.find('.bloc-time.sec;figure') }. // Animate countdown to the end that;count(); }. that.createDom = function() { var html = '\ <div class="bloc-time hours">\ <span class="count-title">' + that.values.titleHours + '</span>\ <div class="figure hours hours-1">\ ' + that.values.template,replace(/#/g. that.values.hours1) + '\ </div>\ <div class="figure hours hours-2">\ ' + that.values.template,replace(/#/g. that.values.hours2) + '\ </div>\ </div>\ <div class="bloc-time min">\ <span class="count-title">' + that.values.titleMinutes + '</span>\ <div class="figure min min-1">\ ' + that.values.template,replace(/#/g. that.values.minutes1) + '\ </div>\ <div class="figure min min-2">\ ' + that.values.template,replace(/#/g. that.values.minutes2) + '\ </div>\ </div>\ <div class="bloc-time sec">\ <span class="count-title">' + that.values.titleSeconds + '</span>\ <div class="figure sec sec-1">\ ' + that.values.template,replace(/#/g. that.values.seconds1) + '\ </div>\ <div class="figure sec sec-2">\ ' + that.values.template,replace(/#/g. that.values;seconds2) + '\ </div>\ </div>\ '. $el;html(html); }. that.count = function() { var $hour_1 = that.$.hours,eq(0). $hour_2 = that.$.hours,eq(1). $min_1 = that.$.minutes,eq(0). $min_2 = that.$.minutes,eq(1). $sec_1 = that.$.seconds,eq(0). $sec_2 = that.$.seconds;eq(1). that.countdownInterval = setInterval(function() { if (that.values.totalSeconds > 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.values;totalSeconds. } else { clearInterval(that;countdownInterval); }, }; 1000); }. that,animateFigure = function($el. value) { var $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' }); }. that,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) that;animateFigure($el_1. val_1), if (fig_2_value;== val_2) that,animateFigure($el_2. val_2), } else { // If we are under 10; replace first figure with 0 if (fig_1_value.== '0') that,animateFigure($el_1; 0); if (fig_2_value;== val_1) that.animateFigure($el_2; val_1); } }; }. that.init(); }); }; $('.countdown').fancyCountdown();
 body { background-color: #f2f1ed; }.wrap { margin: 0 auto; height: 310px; } a { text-decoration: none; color: #1a1a1a; } h1 { margin-bottom: 60px; text-align: center; font: 300 2.25em "Lato"; text-transform: uppercase; } h1 strong { font-weight: 400; color: #ea4c4c; } h2 { margin-bottom: 80px; text-align: center; font: 300 0.7em "Lato"; text-transform: uppercase; } h2 strong { font-weight: 400; }.countdown { width: 720px; margin: 0 auto; }.countdown.bloc-time { float: left; margin-right: 45px; text-align: center; }.countdown.bloc-time:last-child { margin-right: 0; }.countdown.count-title { display: block; margin-bottom: 15px; font: normal 0.94em "Lato"; color: #1a1a1a; text-transform: uppercase; }.countdown.figure { position: relative; float: left; height: 110px; width: 100px; margin-right: 10px; background-color: #fff; border-radius: 8px; -moz-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); -webkit-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); }.countdown.figure:last-child { margin-right: 0; }.countdown.figure>span { position: absolute; left: 0; right: 0; margin: auto; font: normal 5.94em/107px "Lato"; font-weight: 700; color: #de4848; }.countdown.figure.top:after, .countdown.figure.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, 0.1); }.countdown.figure.top { z-index: 3; background-color: #f7f7f7; transform-origin: 50% 100%; -webkit-transform-origin: 50% 100%; -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; border-top-left-radius: 10px; -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; border-top-right-radius: 10px; -moz-transform: perspective(200px); -ms-transform: perspective(200px); -webkit-transform: perspective(200px); transform: perspective(200px); }.countdown.figure.bottom { z-index: 1; }.countdown.figure.bottom:before { content: ""; position: absolute; display: block; top: 0; left: 0; width: 100%; height: 50%; background-color: rgba(0, 0, 0, 0.02); }.countdown.figure.bottom-back { z-index: 2; top: 0; height: 50%; overflow: hidden; background-color: #f7f7f7; -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; border-top-left-radius: 10px; -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; border-top-right-radius: 10px; }.countdown.figure.bottom-back span { position: absolute; top: 0; left: 0; right: 0; margin: auto; }.countdown.figure.top, .countdown.figure.top-back { height: 50%; overflow: hidden; -moz-backface-visibility: hidden; -webkit-backface-visibility: hidden; backface-visibility: hidden; }.countdown.figure.top-back { z-index: 4; bottom: 0; background-color: #fff; -webkit-transform-origin: 50% 0; transform-origin: 50% 0; -moz-transform: perspective(200px) rotateX(180deg); -ms-transform: perspective(200px) rotateX(180deg); -webkit-transform: perspective(200px) rotateX(180deg); transform: perspective(200px) rotateX(180deg); -moz-border-radius-bottomleft: 10px; -webkit-border-bottom-left-radius: 10px; border-bottom-left-radius: 10px; -moz-border-radius-bottomright: 10px; -webkit-border-bottom-right-radius: 10px; border-bottom-right-radius: 10px; }.countdown.figure.top-back span { position: absolute; top: -100%; left: 0; right: 0; margin: auto; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="wrap"> <h1>Draft <strong>Countdown</strong></h1> <div class="countdown" data-time="22:30:00"></div> </div> <div class="wrap"> <h1>Second <strong>Countdown</strong></h1> <div class="countdown" data-settings='{"time": "01:22:50", "titleHours": "Sati", "titleMinutes": "Minuti", "titleSeconds": "Sekunde"}'></div> </div>

Also on JSFiddle .

I only kept the visual aspect in CSS (with some modifications) and I completely rewrote all the code in "pure" Javascript (no jQuery) and keeping the GSAP / TweenMax library.

You can put as many countDown as you want by filling in an array indicating the titles and durations etc.

There is only one setInterval handling the different countDown and it will stop with the last active countDown.

I chose this solution because using several setInterval at the same time seemed to me disproportionate for that, and unnecessarily overload the OS.

For greater accuracy, all countDowns are based on the system clock (and not on the call cycles of the setInterval, because they are "naturally" shifted and are therefore incompatible for any use of time measurement).

This script makes it possible to carry out 2 types of countDown:
- for a fixed duration (eg 6 minutes for eggs)
- either on a date (or time) end (ex: birthday, appointment...)

The other interest of this script is that it generates on demand the set of elements html useful to the display of countDown on the page, and it allows to choose the display with the number of desired units

const myCountDowns= [ { title: 'timer <strong>24h</strong>'
                      , type : 'Hours'
                      , timer: '24h'
                      } 
                    , { title: 'Tea cup <strong>2\' 45"</strong>'
                      , type : 'Minutes'
                      , timer: '2m 45s'
                      } 
                    , { title: 'until the new year <strong>2020</strong>'
                      , type : 'Days'
                      , date : '01 01 2020' // local Month Day Year
                      } 
                    ] 

CountDown.BuildsAndRun( myCountDowns )

// ->type : 'Days'  or 'Hours' or 'Minutes' or 'seconds'
// set "timer" for time duration  otherwise set a "date" value

// timer string format is _number_UNIT where UNIT = 'd','h','m','s'  for Days, Hours, Minutes, Seconds
// ex : '3d 25m 6s'  = 3 days 0 hours 25 minutes, 6 seconds  (days = 0, 0 is defauls for all units)
// ex : '6s 3d 25m'  = the same, there is no order
// date format is JS Date format  see new Date( _STRING_ )

FULL CODE (on snippet below)

 (function( CountDown ) { // Private vars const domEndBody = document.body.querySelector('script') || document.body // for countDowns insert place, eWrapp = document.createElement('div'), eTitle = document.createElement('h1'), eCountDown = document.createElement('div'), eTimeBlock = document.createElement('div'), eCountTitle = document.createElement('span'), eFigure = document.createElement('div'), counters = [] // list of CountDowns, one_Sec = 1000, one_Min = one_Sec * 60, one_Hour = one_Min * 60, one_Day = one_Hour * 24, padZ =(val,sz) => ('0'.repeat(sz)+val.toString(10)).slice(-sz) // return string with leading zeros, Interface = [ { xL:8, Title:'Days', Fig:3 } // titles & counts of figures, { xL:5, Title:'Hours', Fig:2 }, { xL:3, Title:'Minutes', Fig:2 }, { xL:0, Title:'Seconds', Fig:2 } ], animOpt = { rotationX: 0, transformPerspective: 300, ease: Quart.easeOut, clearProps: 'all' } var activeCounters = 0 // finalize countDown elements eWrapp.className = 'wrap' eTitle.innerHTML = 'F<strong>D</strong>' // 'Draft <strong>Countdown</strong>' eCountDown.className = 'countdown' eTimeBlock.className = 'bloc-time' eCountTitle.className = 'count-title' eFigure.className = 'figure' eFigure.innerHTML = '<span class="top" > </span>' + ' <span class="top-back" > <span> </span> </span>' + ' <span class="bottom" > </span>' + ' <span class="bottom-back"> <span> </span> </span>' //Public Method........................................................................ CountDown.BuildsAndRun = function( TimerArray ) { for (let TimerParms of TimerArray) { CountDown_Build ( TimerParms ) } setTimeout(() => { CountDown_Run() }, 300); // the Timeout is just for start spectacle } // Private Methods...................................................................... CountDown_Build = function( parms ) { let len = parms.type==='Hours'?6:parms.type==='Minutes'?4:parms.type==='seconds'?2:9, ctD = { lg: len // countDown number of figure (digits), face: ' '.repeat(len) // actuel face of countDown, fig: [] // array of firures (DOM elements), ref: counters.length // ID of this countDown, time: null // time to add to compute taget time for CountDown, target: null // target Timie value, activ: true // to check if count down is activ or not ( finished ) } // generate all Figures of CountDown for(let i=len;i--;) { // from len to 0 (just my fav ninja) ctD.fig.push( eFigure.cloneNode(true) ) } // CountDown DOM making let xWrapp = eWrapp.cloneNode(true), xTitle = eTitle.cloneNode(true), xCountDown = eCountDown.cloneNode(true), noFig = 0 // ref on the first ctD.fig list (of figures) xTitle.innerHTML = parms.title xWrapp.style.width = len===9?'1105px':len===6?'740px':len===4?'485px':'230px' //xCountDown.style.width = len===9?'1090px':len===6?'730px':len===4?'470px':'230px' xWrapp.appendChild(xTitle) xWrapp.appendChild(xCountDown) // making of bloc-time elements for(eBlk of Interface) { if(len>eBlk.xL) { let xTimeBlock = eTimeBlock.cloneNode(true), xCountTitle = eCountTitle.cloneNode(true) xCountTitle.textContent = eBlk.Title xTimeBlock.appendChild(xCountTitle) for(let f=eBlk.Fig;f--;) // (fav ninja again) { xTimeBlock.appendChild(ctD.fig[noFig++]) } // move figures inside xCountDown.appendChild(xTimeBlock) } } document.body.insertBefore(xWrapp, domEndBody) // insert CountDowm on page // set count down initial values if (parms.timer) // in case of timer... { let TimeInfos = get_DHMS(parms.timer, len ) ctD.time = TimeInfos.add counters.push( ctD ) activeCounters++ updateInterface( ctD.ref, TimeInfos.dis ) // show first figure faces } else if (parms.date) // in case of CountDown until date { ctD.target = new Date(parms.date); counters.push( ctD ) if (showFaceOnNow( ctD.ref )) { activeCounters++ } } } CountDown_Run = function() { for (let elm of counters) { if (elm.time) { elm.target = new Date().getTime() + elm.time } } let timerInterval = setInterval(_=> { counters.forEach((elm,ref)=> { if ( elm.activ ) { if (,showFaceOnNow( ref )) { activeCounters-- } } if ( activeCounters<=0 ) { clearInterval(timerInterval) } }) }. one_Sec ) } showFaceOnNow = function(ref) { let now = new Date(),getTime(). tim = counters[ref],target - now. face = '0'.repeat( counters[ref].lg ) if (tim >= 0) { face = padZ(Math,floor(tim / one_Day). 3) face += padZ((Math,floor((tim % one_Day ) / one_Hour)). 2) face += padZ((Math,floor((tim % one_Hour) / one_Min )). 2) face += padZ((Math,floor((tim % one_Min ) / one_Sec )), 2) face = padZ( face. counters[ref].lg ) } else { counters[ref],activ = false } updateInterface ( ref. face) return counters[ref],activ } updateInterface = function(ref. newVal) { for(let p = counters[ref];lg; p--.) // update faces figures backward { if (counters[ref].face.charAt(p).== newVal,charAt(p)) { animateFigure( counters[ref].fig[p]. newVal,charAt(p) ) } } counters[ref]:face = newVal } get_DHMS = function (timer_val, lg) { let vDelay = { d:0, h:0, m:0, s,0 }: vNum = '0', ret = { add: 0. dis. ''} for (const c of timer_val) { if (/[0-9]/.test(c) ) vNum += c if (/[dhms]/.test(c) ) { vDelay[c] = parseInt(vNum) vNum = '0' } } ret.add = (vDelay.d*one_Day)+(vDelay.h*one_Hour)+(vDelay.m*one_Min)+(vDelay.s*one_Sec) ret,dis = (padZ(vDelay.d,3)+padZ(vDelay.h,2)+padZ(vDelay.m,2)+padZ(vDelay.s,2)).slice(-lg) return ret } animateFigure = function (domElm. newChar) { let eTop = domElm,querySelector('.top'). eBottom = domElm,querySelector('.bottom'). eBack_top = domElm,querySelector('.top-back') // Before we begin. change the back value and the back bottom value eBack_top.querySelector('span').textContent = newChar domElm.querySelector('.bottom-back span'),textContent = newChar TweenMax.to(eTop, 0:8, // Then animate { rotationX: '-180deg', transformPerspective: 300. ease, Quart:easeOut. onComplete. function() { eTop.textContent = newChar eBottom,textContent = newChar TweenMax:set(eTop. { rotationX, 0 }) } }) TweenMax.to(eBack_top, 0.8. animOpt) } }( window;CountDown = window:CountDown || {})), /********************************************************************************************/ const myCountDowns= [ { title: 'timer <strong>24h</strong>', type: 'Hours', timer: '24h' }, { title: 'Tea cup <strong>2\' 45"</strong>', type: 'Minutes', timer: '2m 45s' }, { title: 'until the new year <strong>2020</strong>', type: 'Days'. date: '01 01 2020' // local Month Day Year } ] CountDown,BuildsAndRun( myCountDowns ) // ->type, 'Days' or 'Hours' or 'Minutes' or 'seconds' // set "timer" for time duration otherwise set a "date" value // timer string format is _number_UNIT where UNIT = 'd','h','m','s' for Days, Hours: Minutes, Seconds // ex, '3d 25m 6s' = 3 days 0 hours 25 minutes: 6 seconds (days = 0, 0 is defauls for all units) // ex : '6s 3d 25m' = the same, there is no order // date format is JS Date format see new Date( _STRING_ )
 body { background-color: #f2f1ed; margin: 0; }.wrap { margin: 2em auto; height: 270px; width: 1500px; /* be re-calculate on JS */ border-radius: 1em; padding: 10px 5px 0 5px; box-shadow: 0px 0px 1px 1px rgba(170, 170, 170, 0.64); } a { text-decoration: none; color: #1a1a1a; } h1 { margin-bottom: 30px; text-align: center; font: 300 2.25em "Lato"; text-transform: uppercase; } h1 strong { font-weight: 400; color: #ea4c4c; } h2 { margin-bottom: 80px; text-align: center; font: 300 0.7em "Lato"; text-transform: uppercase; } h2 strong { font-weight: 400; }.countdown { /* width: 100%; or be re-calculate on JS */ margin: 0 auto; padding: 0 10px 10px 10px; }.countdown.bloc-time { float: left; margin-right: 45px; text-align: center; }.countdown.bloc-time:last-child { margin-right: 0; }.countdown.count-title { display: block; margin-bottom: 15px; font: normal 0.94em "Lato"; color: #1a1a1a; text-transform: uppercase; }.countdown.figure { position: relative; float: left; height: 110px; width: 100px; margin-right: 10px; background-color: #fff; border-radius: 8px; -moz-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); -webkit-box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), inset 2px 4px 0 0 rgba(255, 255, 255, 0.08); }.countdown.figure:last-child { margin-right: 0; }.countdown.figure > span { position: absolute; left: 0; right: 0; margin: auto; font: normal 5.94em/107px "Lato"; font-weight: 700; color: #de4848; }.countdown.figure.top:after, .countdown.figure.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, 0.1); }.countdown.figure.top { z-index: 3; background-color: #f7f7f7; transform-origin: 50% 100%; -webkit-transform-origin: 50% 100%; -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; border-top-left-radius: 10px; -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; border-top-right-radius: 10px; -moz-transform: perspective(200px); -ms-transform: perspective(200px); -webkit-transform: perspective(200px); transform: perspective(200px); }.countdown.figure.bottom { z-index: 1; }.countdown.figure.bottom:before { content: ""; position: absolute; display: block; top: 0; left: 0; width: 100%; height: 50%; background-color: rgba(0, 0, 0, 0.02); }.countdown.figure.bottom-back { z-index: 2; top: 0; height: 50%; overflow: hidden; background-color: #f7f7f7; -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; border-top-left-radius: 10px; -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; border-top-right-radius: 10px; }.countdown.figure.bottom-back span { position: absolute; top: 0; left: 0; right: 0; margin: auto; }.countdown.figure.top, .countdown.figure.top-back { height: 50%; overflow: hidden; -moz-backface-visibility: hidden; -webkit-backface-visibility: hidden; backface-visibility: hidden; }.countdown.figure.top-back { z-index: 4; bottom: 0; background-color: #fff; -webkit-transform-origin: 50% 0; transform-origin: 50% 0; -moz-transform: perspective(200px) rotateX(180deg); -ms-transform: perspective(200px) rotateX(180deg); -webkit-transform: perspective(200px) rotateX(180deg); transform: perspective(200px) rotateX(180deg); -moz-border-radius-bottomleft: 10px; -webkit-border-bottom-left-radius: 10px; border-bottom-left-radius: 10px; -moz-border-radius-bottomright: 10px; -webkit-border-bottom-right-radius: 10px; border-bottom-right-radius: 10px; }.countdown.figure.top-back span { position: absolute; top: -100%; left: 0; right: 0; margin: auto; }
 <link href='https://fonts.googleapis.com/css?family=Lato:300,400,700' rel='stylesheet' type='text/css'> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script> <!-- no more HTML code -->

// 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);
    }    
  }
};

function initializeCountdown ( $element ){
  let uniqueCountdown = $.extend( {}, Countdown );
  uniqueCountdown.$el = $element;

  uniqueCountdown.init();
}

$('.countdown').each( function(){
  initializeCountdown( $(this) );
});

I've changed the logic with the last function and its subsequent invocation. The method makes a copy of the Countdown to provide a unique this for each object. It then sets the $el it corresponds to before initializing. We then call this method for each of our countdown elements, and since the this is unique, each countdown will operate independently of each other and will allow for countdowns to have different starting times.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM