Modify countdown script to allow for multiple countdowns per page

走远了吗. 提交于 2021-02-04 18:39:31

问题


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.


回答1:


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,




回答2:


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.




回答3:


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 -->



回答4:


// 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.



来源:https://stackoverflow.com/questions/58294674/modify-countdown-script-to-allow-for-multiple-countdowns-per-page

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!