Измените скрипт обратного отсчета, чтобы разрешить несколько отсчетов на странице - PullRequest
4 голосов
/ 09 октября 2019

Я использую следующий скрипт из 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();

В течение нескольких часов я пытался выяснить, как его изменить, чтобы поддерживать несколько таймеров обратного отсчета на странице.

Мой подход до сих пор заключался в том, чтобы попытаться добавить числовой счетчик, чтобы каждый элемент «обратного отсчета» получал уникальный класс, а затем изменить сценарий для запуска на каждом элементе, но это не сработало, и я не думаю, чтоэто будет.

Я не уверен, как еще приблизиться к этому, хотя так был бы признателен за некоторый вклад.

Ответы [ 4 ]

6 голосов
/ 15 октября 2019

Вы можете создать новый экземпляр этого объекта с помощью небольшого рефакторинга, превратив его в функцию.

Например, если вы клонируете свой <div class="countdown"/> HTML и в JS вы вызываете:

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

Или, в качестве альтернативы, вы также можете инициализировать все .countdowns на странице с помощью:

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

у вас будет два уникальных экземпляра обратного отсчета.

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

Вот ссылка на обновленный кодовый блок .

Надеюсь, это поможет,

3 голосов
/ 16 октября 2019

Как плагин jQuery, он может пойти по этому пути (вы можете настроить его дальше):

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

Также на JSFiddle .

2 голосов
/ 21 октября 2019

Я сохранил только визуальный аспект в CSS (с некоторыми изменениями) и полностью переписал весь код в «чистый» Javascript (без jQuery) и сохранил библиотеку GSAP / TweenMax.

Вы можете поставить какмного countDown, как вы хотите, заполнив массив с указанием заголовков и продолжительности и т. д.

Существует только один setInterval для обработки другого countDown, и он остановится с последним активным countDown. это решение, потому что использование нескольких setInterval в то же время показалось мне непропорциональным для этого и излишне перегружало ОС.

Для большей точности все countDowns основаны на системных часах (а не на циклах вызоваsetInterval, потому что они «естественным образом» смещены и поэтому несовместимы для любого использования измерения времени).

Этот скрипт позволяет выполнять 2 типа countDown:
- в течение фиксированной продолжительности (например,6 минут для яиц)
- либо в конце даты (или времени) (например: день рождения, околоintment ...)

Другой интерес этого скрипта заключается в том, что он генерирует по запросу набор элементов html, полезный для отображения 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_ )

ПОЛНЫЙ КОД (на фрагменте ниже)

(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 -->
1 голос
/ 15 октября 2019
// 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) );
});

Я изменил логику с последней функцией и ее последующим вызовом. Метод создает копию Countdown, чтобы обеспечить уникальный this для каждого объекта. Затем он устанавливает $el, которому он соответствует, перед инициализацией. Затем мы вызываем этот метод для каждого из наших элементов обратного отсчета, и поскольку this уникален, каждый обратный отсчет будет работать независимо друг от друга и будет иметь разное время начала отсчета.

...