обратный отсчет от n до 0 в данное время, отрицательное конечное значение - PullRequest
0 голосов
/ 09 мая 2018

Я работаю над простым сценарием, который должен анимировать заданное значение (например, 6345.23) до 0 путем обратного отсчета, оно также должно закончиться на 0, если прошло указанное количество времени (например, 2 seconds.

Я начал с простой логики:

  • заданная конфигурация: initial value, time in sec, interval
  • время дается в секундах, поэтому его необходимо преобразовать в миллисекунды
  • рассчитать количество тиков путем деления времени в мс на интервал
  • рассчитать сумму уменьшенного значения на тик путем деления начального значения на количество тиков

как только вы узнали, что мы можем просто сделать: (простая модель, а не реальный код)

intId = setInterval(function() {
    if(ticks_made === amount_of_ticks) {
        clearInterval(intId);
    } else {
        value -= amount_per_tick;
        // update view
    }
}, interval);

фактический код:

var value = 212.45,
    time = 2, // in seconds
    interval = 20; // in milliseconds

var time_to_ms = time * 1000,
    amount_of_ticks = time_to_ms / interval,
    amount_per_tick = (value / amount_of_ticks).toFixed(5);

var start_time = new Date();

var ticks_made = 0;

var intId = setInterval(function() {

    if(ticks_made === amount_of_ticks) {
        console.log('start time', start_time);
        console.log('end time', new Date());
        console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
        clearInterval(intId);
    } else {
        value = (value - amount_per_tick).toFixed(5);
        console.log('running', ticks_made, value);
    }

    ticks_made++;

}, interval);

Link do fiddle (в консоли вы можете наблюдать, как это работает)

Если вы установите время на 2 (2 seconds), то это нормально, но если вы установите время, например, 2.55 (2.55 seconds), оно вообще не остановится на 0, оно будет проходить и идти бесконечно в отрицательных значениях.

Как я могу это исправить, независимо от того, что установлено в секундах, оно всегда идет точно по одному, пока не достигнет идеального значения 0?

var value = 212.45,
	time = 2, // in seconds
	interval = 20; // in milliseconds

var time_to_ms = time * 1000,
	amount_of_ticks = time_to_ms / interval,
	amount_per_tick = (value / amount_of_ticks).toFixed(5);

var start_time = new Date();

var ticks_made = 0;

var intId = setInterval(function() {

	if(ticks_made === amount_of_ticks) {
		console.log('start time', start_time);
		console.log('end time', new Date());
		console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
		clearInterval(intId);
	} else {
		value = (value - amount_per_tick).toFixed(5);
		console.log('running', ticks_made, value);
	}
	
	ticks_made++;

}, interval);

Ответы [ 3 ]

0 голосов
/ 09 мая 2018

Имеет смысл сразу преобразовать результат .toFixed() в число:

let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);

(обратите внимание на знаки +)

Тогда вам никогда не придется беспокоиться о принуждении типов или о чем-то еще, вместо этого просто сосредоточьтесь на математике.

0 голосов
/ 09 мая 2018

ответ Кшетлина правильно объясняет, почему вы получаете отрицательные значения. При работе с дробными двоичными числами двойной точности IEEE-754 (в нормальном диапазоне или даже целыми числами в очень высоких диапазонах), == и === могут быть проблематичными (например, 0.1 + 0.2 == 0.3 является ложным). При работе с такими малыми значениями, как дробные значения, накопленная неточность также является фактором. Это неизбежно, чтобы придумать последний шаг.

Но есть большая проблема: вы не можете полагаться на таймеры, работающие по точному графику. Многие, многие вещи могут помешать им сделать это & ​​mdash; другие операции рендеринга пользовательского интерфейса, другие сценарии, загрузка процессора, неактивная вкладка и т. д.

Вместо этого основной метод анимации в браузерах:

  • Обновите, когда сможете
  • Обновление в зависимости от того, где вы должны быть в анимации в зависимости от времени , а не от того, сколько раз вы анимировали
  • Используйте requestAnimationFrame, чтобы ваше обновление синхронизировалось с обновлением браузера

Вот ваш код, обновленный для этого, см. Комментарии:

// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});

var value = 212.45,
  time = 2.55, // in seconds
  time_in_ms = time * 1000,
  amount_per_ms = value / time_in_ms,
  interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
  ticks_made = 0;
  
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
          ? performance.now.bind(performance)
          : Date.now.bind(Date);

// Remember when we started
var started = now();

// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;

// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
  requestAnimationFrame(render);
}, interval);

// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
  console.log("************DELAY************");
  var stop = now() + 200;
  while (now() < stop) {
    // Busy-loop, preventing anything else from happening
  }
}, time_in_ms / 2);

// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
  if (done) {
    return;
  }
  ++ticks_made;
  var elapsed = now() - started;
  if (elapsed >= time_in_ms) {
    console.log(ticks_made, "done");
    done = true;
    clearInterval(intId);
  } else {
    var current_value = value - (amount_per_ms * elapsed);
    console.log(ticks_made, current_value);
  }
}
/* Maximize in-snippet console */
.as-console-wrapper {
  max-height: 100% !important;
}

Если вы запустите это, а затем прокрутите вверх до строки "************DELAY************", вы увидите, что, хотя рендеринг был задержан «другим процессом», мы продолжим с соответствующим следующим значением рендеринга.

0 голосов
/ 09 мая 2018

Вы полагаетесь на ticks_made === amount_of_ticks, который является точным соответствием. Скорее всего, из-за округления вы не получите точного соответствия, поэтому вам лучше сделать:

if(ticks_made >= amount_of_ticks) {
...