For demonstration of the issue please see here:
https://gyazo.com/06e423d07afecfa2fbdb06a6da77f66a
I'm getting a jumping behavior on un-pausing the notification. This is also influenced by how long the mouse stays on the notification and how close the progress is to the end.
I've tried so many things, I'm not sure anymore if the problem is truly with setTimeout.
It is like as if since the calculation of this.timerFinishesAt to the first iteration of requestAnimationFrame the progress jumps due to waiting on cpu time? But then again, why would it be influenced by the hover time and progress.
How do I mitigate the jumping behavior?
I read/tried to implement the fix from the following resources amongst looking at other stackoverflow questions:
https://gist.github.com/tanepiper/4215634
How to create an accurate timer in javascript?
What is the reason JavaScript setTimeout is so inaccurate?
https://www.sitepoint.com/creating-accurate-timers-in-javascript/
https://codepen.io/sayes2x/embed/GYdLqL?default-tabs=js%2Cresult&height=600&host=https%3A%2F%2Fcodepen.io&referrer=https%3A%2F%2Fmedium.com%2Fmedia%2Fb90251c55fe9ac7717ae8451081f6366%3FpostId%3D255f3f5cf50c&slug-hash=GYdLqL
https://github.com/Falc/Tock.js/tree/master
https://github.com/philipyoungg/timer
https://github.com/Aaronik/accurate_timer
https://github.com/husa/timer.js
timerStart(){
// new future date = future date + elapsed time since pausing
this.timerFinishesAt = new Date( this.timerFinishesAt.getTime() + (Date.now() - this.timerPausedAt.getTime()) );
// set new timeout
this.timerId = window.setTimeout(this.toggleVisibility, (this.timerFinishesAt.getTime() - Date.now()));
// animation start
this.progressId = requestAnimationFrame(this.progressBar);
},
timerPause(){
// stop notification from closing
window.clearTimeout(this.timerId);
// set to null so animation won't stay in a loop
this.timerId = null;
// stop loader animation from progressing
cancelAnimationFrame(this.progressId);
this.progressId = null;
this.timerPausedAt = new Date();
},
progressBar(){
if (this.progress < 100) {
let elapsed = Date.now() - this.timerStarted.getTime();
let wholeTime = this.timerFinishesAt.getTime() - this.timerStarted.getTime();
this.progress = Math.ceil((elapsed / wholeTime) * 100);
if (this.timerId) {
this.progressId = requestAnimationFrame(this.progressBar);
}
} else {
this.progressId = cancelAnimationFrame(this.progressId);
}
}
When you do calculate the current progress of your timer, you are not taking the pause time into consideration. Hence the jumps: This part of your code is only aware of the startTime and currentTime, it won't be affected by the pauses.
To circumvent it, you can either accumulate all this pause times in the startTimer function
class Timer {
constructor() {
this.progress = 0;
this.totalPauseDuration = 0;
const d = this.timerFinishesAt = new Date(Date.now() + 10000);
this.timerStarted = new Date();
this.timerPausedAt = new Date();
}
timerStart() {
const pauseDuration = (Date.now() - this.timerPausedAt.getTime())
this.totalPauseDuration += pauseDuration;
// new future date = future date + elapsed time since pausing
this.timerFinishesAt = new Date(this.timerFinishesAt.getTime() + pauseDuration);
// set new timeout
this.timerId = window.setTimeout(this.toggleVisibility.bind(this), (this.timerFinishesAt.getTime() - Date.now()));
// animation start
this.progressId = requestAnimationFrame(this.progressBar.bind(this));
}
timerPause() {
// stop notification from closing
window.clearTimeout(this.timerId);
// set to null so animation won't stay in a loop
this.timerId = null;
// stop loader animation from progressing
cancelAnimationFrame(this.progressId);
this.progressId = null;
this.timerPausedAt = new Date();
}
progressBar() {
if (this.progress < 100) {
let elapsed = (Date.now() - this.timerStarted.getTime()) - this.totalPauseDuration;
let wholeTime = this.timerFinishesAt.getTime() - this.timerStarted.getTime();
this.progress = Math.ceil((elapsed / wholeTime) * 100);
log.textContent = this.progress;
if (this.timerId) {
this.progressId = requestAnimationFrame(this.progressBar.bind(this));
}
} else {
this.progressId = cancelAnimationFrame(this.progressId);
}
}
toggleVisibility() {
console.log("done");
}
};
const timer = new Timer();
btn.onclick = e => {
if (timer.timerId) timer.timerPause();
else timer.timerStart();
};
<pre id="log"></pre>
<button id="btn">toggle</button>
or update the startTime, which seems to be more reliable:
class Timer {
constructor() {
this.progress = 0;
const d = this.timerFinishesAt = new Date(Date.now() + 10000);
this.timerStarted = new Date();
this.timerPausedAt = new Date();
}
timerStart() {
const pauseDuration = (Date.now() - this.timerPausedAt.getTime())
// update timerStarted
this.timerStarted = new Date(this.timerStarted.getTime() + pauseDuration);
// new future date = future date + elapsed time since pausing
this.timerFinishesAt = new Date(this.timerFinishesAt.getTime() + pauseDuration);
// set new timeout
this.timerId = window.setTimeout(this.toggleVisibility.bind(this), (this.timerFinishesAt.getTime() - Date.now()));
// animation start
this.progressId = requestAnimationFrame(this.progressBar.bind(this));
}
timerPause() {
// stop notification from closing
window.clearTimeout(this.timerId);
// set to null so animation won't stay in a loop
this.timerId = null;
// stop loader animation from progressing
cancelAnimationFrame(this.progressId);
this.progressId = null;
this.timerPausedAt = new Date();
}
progressBar() {
if (this.progress < 100) {
let elapsed = Date.now() - this.timerStarted.getTime();
let wholeTime = this.timerFinishesAt.getTime() - this.timerStarted.getTime();
this.progress = Math.ceil((elapsed / wholeTime) * 100);
log.textContent = this.progress;
if (this.timerId) {
this.progressId = requestAnimationFrame(this.progressBar.bind(this));
}
} else {
this.progressId = cancelAnimationFrame(this.progressId);
}
}
toggleVisibility() {
console.log("done");
}
};
const timer = new Timer();
btn.onclick = e => {
if (timer.timerId) timer.timerPause();
else timer.timerStart();
};
<pre id="log"></pre>
<button id="btn">toggle</button>
As to the final gap, not seeing how this code is linked with your UI, it's hard to tell what happens.
I think simply using SetInterval is enough :
const progressBar = {
MsgBox : document.querySelector('#Message'),
Info : document.querySelector('#Message h1'),
barr : document.querySelector('#Message progress'),
interV : 0,
DTime : 0,
D_Max : 0,
Init() {
this.MsgBox.onmouseover=_=> { // pause
clearInterval( this.interV )
}
this.MsgBox.onmouseout=_=>{ // restart
this._run()
}
},
Start(t,txt)
{
this.DTime = this.D_Max = t * 1000
this.barr.value = 0
this.barr.max = this.D_Max
this.Info.textContent = txt
this._run()
},
_run()
{
let D_End = new Date(Date.now() + this.DTime )
this.interV = setInterval(_=>{
this.DTime = D_End - (new Date(Date.now()))
if (this.DTime > 0) { this.barr.value = this.D_Max - this.DTime }
else { clearInterval( this.interV ); console.clear(); console.log( "finish" ) }
}, 100);
}
}
progressBar.Init()
progressBar.Start(10, 'Hello!') // 10 seconds
#Message {
box-sizing: border-box;
display: block;
float: right;
width: 200px;
height: 80px;
background-color: darkslategrey;
padding: 0 1em;
color:#e4a8b4;
cursor: pointer;
margin-right:1.5em;
}
#Message h1 { margin: .3em 0 0 0}
#Message progress { height: .1em; margin: 0; width:100%; background-color:black; }
#Message progress::-moz-progress-bar,
#Message progress::-webkit-progress-value { background-color:greenyellow; }
<div id="Message">
<progress value="50" max="100" ></progress>
<h1> </h1>
</div>
Related
How do I pause and resume the setInterval() function using Javascript?
For example, maybe I have a stopwatch to tell you the number of seconds that you have been looking at the webpage. There is a 'Pause' and 'Resume' button. The reason why clearInterval() would not work here is because if the user clicks on the 'Pause' button at the 40th second and 800th millisecond, when he clicks on the 'Resume' button, the number of seconds elapsed must increase by 1 after 200 milliseconds. If I use the clearInterval() function on the timer variable (when the pause button is clicked) and then using the setInterval() function on the timer variable again (when the resume button is clicked), the number of seconds elapsed will increase by 1 only after 1000 milliseconds, which destroys the accuracy of the stopwatch.
So how do I do that?
You could use a flag to keep track of the status:
var output = $('h1');
var isPaused = false;
var time = 0;
var t = window.setInterval(function() {
if(!isPaused) {
time++;
output.text("Seconds: " + time);
}
}, 1000);
//with jquery
$('.pause').on('click', function(e) {
e.preventDefault();
isPaused = true;
});
$('.play').on('click', function(e) {
e.preventDefault();
isPaused = false;
});
h1 {
font-family: Helvetica, Verdana, sans-serif;
font-size: 12px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Seconds: 0</h1>
<button class="play">Play</button>
<button class="pause">Pause</button>
This is just what I would do, I'm not sure if you can actually pause the setInterval.
Note: This system is easy and works pretty well for applications that don't require a high level of precision, but it won't consider the time elapsed in between ticks: if you click pause after half a second and later click play your time will be off by half a second.
You shouldn't measure time in interval function. Instead just save time when timer was started and measure difference when timer was stopped/paused. Use setInterval only to update displayed value. So there is no need to pause timer and you will get best possible accuracy in this way.
While #Jonas Giuro is right when saying that:
You cannot PAUSE the setInterval function, you can either STOP it (clearInterval), or let it run
On the other hand this behavior can be simulated with approach #VitaliyG suggested:
You shouldn't measure time in interval function. Instead just save time when timer was started and measure difference when timer was stopped/paused. Use setInterval only to update displayed value.
var output = $('h1');
var isPaused = false;
var time = new Date();
var offset = 0;
var t = window.setInterval(function() {
if(!isPaused) {
var milisec = offset + (new Date()).getTime() - time.getTime();
output.text(parseInt(milisec / 1000) + "s " + (milisec % 1000));
}
}, 10);
//with jquery
$('.toggle').on('click', function(e) {
e.preventDefault();
isPaused = !isPaused;
if (isPaused) {
offset += (new Date()).getTime() - time.getTime();
} else {
time = new Date();
}
});
h1 {
font-family: Helvetica, Verdana, sans-serif;
font-size: 12px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Seconds: 0</h1>
<button class="toggle">Toggle</button>
Why not use a simpler approach? Add a class!
Simply add a class that tells the interval not to do anything. For example: on hover.
var i = 0;
this.setInterval(function() {
if(!$('#counter').hasClass('pauseInterval')) { //only run if it hasn't got this class 'pauseInterval'
console.log('Counting...');
$('#counter').html(i++); //just for explaining and showing
} else {
console.log('Stopped counting');
}
}, 500);
/* In this example, I'm adding a class on mouseover and remove it again on mouseleave. You can of course do pretty much whatever you like */
$('#counter').hover(function() { //mouse enter
$(this).addClass('pauseInterval');
},function() { //mouse leave
$(this).removeClass('pauseInterval');
}
);
/* Other example */
$('#pauseInterval').click(function() {
$('#counter').toggleClass('pauseInterval');
});
body {
background-color: #eee;
font-family: Calibri, Arial, sans-serif;
}
#counter {
width: 50%;
background: #ddd;
border: 2px solid #009afd;
border-radius: 5px;
padding: 5px;
text-align: center;
transition: .3s;
margin: 0 auto;
}
#counter.pauseInterval {
border-color: red;
}
<!-- you'll need jQuery for this. If you really want a vanilla version, ask -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="counter"> </p>
<button id="pauseInterval">Pause</button></p>
I've been looking for this fast and easy approach for ages, so I'm posting several versions to introduce as many people to it as possible.
i wrote a simple ES6 class that may come handy.
inspired by https://stackoverflow.com/a/58580918/4907364 answer
export class IntervalTimer {
callbackStartTime;
remaining = 0;
paused = false;
timerId = null;
_callback;
_delay;
constructor(callback, delay) {
this._callback = callback;
this._delay = delay;
}
pause() {
if (!this.paused) {
this.clear();
this.remaining = new Date().getTime() - this.callbackStartTime;
this.paused = true;
}
}
resume() {
if (this.paused) {
if (this.remaining) {
setTimeout(() => {
this.run();
this.paused = false;
this.start();
}, this.remaining);
} else {
this.paused = false;
this.start();
}
}
}
clear() {
clearInterval(this.timerId);
}
start() {
this.clear();
this.timerId = setInterval(() => {
this.run();
}, this._delay);
}
run() {
this.callbackStartTime = new Date().getTime();
this._callback();
}
}
usage is pretty straightforward,
const interval = new IntervalTimer(console.log('aaa'), 3000);
interval.start();
interval.pause();
interval.resume();
interval.clear();
My simple way:
function Timer (callback, delay) {
let callbackStartTime
let remaining = 0
this.timerId = null
this.paused = false
this.pause = () => {
this.clear()
remaining -= Date.now() - callbackStartTime
this.paused = true
}
this.resume = () => {
window.setTimeout(this.setTimeout.bind(this), remaining)
this.paused = false
}
this.setTimeout = () => {
this.clear()
this.timerId = window.setInterval(() => {
callbackStartTime = Date.now()
callback()
}, delay)
}
this.clear = () => {
window.clearInterval(this.timerId)
}
this.setTimeout()
}
How to use:
let seconds = 0
const timer = new Timer(() => {
seconds++
console.log('seconds', seconds)
if (seconds === 8) {
timer.clear()
alert('Game over!')
}
}, 1000)
timer.pause()
console.log('isPaused: ', timer.paused)
setTimeout(() => {
timer.resume()
console.log('isPaused: ', timer.paused)
}, 2500)
function Timer (callback, delay) {
let callbackStartTime
let remaining = 0
this.timerId = null
this.paused = false
this.pause = () => {
this.clear()
remaining -= Date.now() - callbackStartTime
this.paused = true
}
this.resume = () => {
window.setTimeout(this.setTimeout.bind(this), remaining)
this.paused = false
}
this.setTimeout = () => {
this.clear()
this.timerId = window.setInterval(() => {
callbackStartTime = Date.now()
callback()
}, delay)
}
this.clear = () => {
window.clearInterval(this.timerId)
}
this.setTimeout()
}
The code is written quickly and did not refactored, raise the rating of my answer if you want me to improve the code and give ES2015 version (classes).
I know this thread is old, but this could be another solution:
var do_this = null;
function y(){
// what you wanna do
}
do_this = setInterval(y, 1000);
function y_start(){
do_this = setInterval(y, 1000);
};
function y_stop(){
do_this = clearInterval(do_this);
};
The following code, provides a precision way to pause resume a timer.
How it works:
When the timer is resumed after a pause, it generates a correction cycle using a single timeout, that will consider the pause offset (exact time when the timer was paused between cycles). After the correction cycle finishes, it schedules the following cycles with a regular setInteval, and continues normally the cycle execution.
This allows to pause/resume the timer, without losing the sync.
Code :
function Timer(_fn_callback_ , _timer_freq_){
let RESUME_CORRECTION_RATE = 2;
let _timer_statusCode_;
let _timer_clockRef_;
let _time_ellapsed_; // will store the total time ellapsed
let _time_pause_; // stores the time when timer is paused
let _time_lastCycle_; // stores the time of the last cycle
let _isCorrectionCycle_;
/**
* execute in each clock cycle
*/
const nextCycle = function(){
// calculate deltaTime
let _time_delta_ = new Date() - _time_lastCycle_;
_time_lastCycle_ = new Date();
_time_ellapsed_ += _time_delta_;
// if its a correction cicle (caused by a pause,
// destroy the temporary timeout and generate a definitive interval
if( _isCorrectionCycle_ ){
clearTimeout( _timer_clockRef_ );
clearInterval( _timer_clockRef_ );
_timer_clockRef_ = setInterval( nextCycle , _timer_freq_ );
_isCorrectionCycle_ = false;
}
// execute callback
_fn_callback_.apply( timer, [ timer ] );
};
// initialize timer
_time_ellapsed_ = 0;
_time_lastCycle_ = new Date();
_timer_statusCode_ = 1;
_timer_clockRef_ = setInterval( nextCycle , _timer_freq_ );
// timer public API
const timer = {
get statusCode(){ return _timer_statusCode_ },
get timestamp(){
let abstime;
if( _timer_statusCode_=== 1 ) abstime = _time_ellapsed_ + ( new Date() - _time_lastCycle_ );
else if( _timer_statusCode_=== 2 ) abstime = _time_ellapsed_ + ( _time_pause_ - _time_lastCycle_ );
return abstime || 0;
},
pause : function(){
if( _timer_statusCode_ !== 1 ) return this;
// stop timers
clearTimeout( _timer_clockRef_ );
clearInterval( _timer_clockRef_ );
// set new status and store current time, it will be used on
// resume to calculate how much time is left for next cycle
// to be triggered
_timer_statusCode_ = 2;
_time_pause_ = new Date();
return this;
},
resume: function(){
if( _timer_statusCode_ !== 2 ) return this;
_timer_statusCode_ = 1;
_isCorrectionCycle_ = true;
const delayEllapsedTime = _time_pause_ - _time_lastCycle_;
_time_lastCycle_ = new Date( new Date() - (_time_pause_ - _time_lastCycle_) );
_timer_clockRef_ = setTimeout( nextCycle , _timer_freq_ - delayEllapsedTime - RESUME_CORRECTION_RATE);
return this;
}
};
return timer;
};
let myTimer = Timer( x=> console.log(x.timestamp), 1000);
<input type="button" onclick="myTimer.pause()" value="pause">
<input type="button" onclick="myTimer.resume()" value="resume">
Code source :
This Timer is a modified and simplified version of advanced-timer, a js library created by myself, with many more functionalities.
The full library and documentation is available in NPM and GITHUB
let time = document.getElementById("time");
let stopButton = document.getElementById("stop");
let timeCount = 0,
currentTimeout;
function play() {
stopButton.hidden = false;
clearInterval(currentTimeout);
currentTimeout = setInterval(() => {
timeCount++;
const min = String(Math.trunc(timeCount / 60)).padStart(2, 0);
const sec = String(Math.trunc(timeCount % 60)).padStart(2, 0);
time.innerHTML = `${min} : ${sec}`;
}, 1000);
}
function pause() {
clearInterval(currentTimeout);
}
function stop() {
stopButton.hidden = true;
pause();
timeCount = 0;
time.innerHTML = `00 : 00`;
}
<div>
<h1 id="time">00 : 00</h1>
<br />
<div>
<button onclick="play()">play</button>
<button onclick="pause()">pause</button>
<button onclick="stop()" id="stop" hidden>Reset</button>
</div>
</div>
I am trying to put together a stopwatch using JavaScript. I have got the date to populate with the correct information but my Stopwatch doesn't work. I click start and the numbers never move from 0, I would like to have it increment in MS no seconds. I have my code for the JS and the HTML also. HTML is functioning as it should but the JS is not. I am very green to the JavaScript world and i have looked and looked and was unable to come across a solution that would be a benefit to me. Thanks for your assistance.
"use strict";
var $ = function(id) { return document.getElementById(id); };
var stopwatchTimer;
var elapsedMinutes = 0;
var elapsedSeconds = 0;
var elapsedMilliseconds = 0;
var displayCurrentTime = function() {
var now = new Date();
var hours = now.getHours();
var ampm = "AM";
if (hours > 12) {
hours = hours - 12;
ampm = "PM";
} else {
switch (hours) {
case 12:
ampm = "PM";
break;
case 0:
hours = 12;
ampm = "AM";
}
}
$("hours").firstChild.nodeValue = hours;
$("minutes").firstChild.nodeValue = padSingleDigit(now.getMinutes());
$("seconds").firstChild.nodeValue = padSingleDigit(now.getSeconds());
$("ampm").firstChild.nodeValue = ampm;
};
var padSingleDigit = function(num) {
if (num < 10) { return "0" + num; }
else { return num; }
};
var tickStopwatch = function() {
// I also need to increment in 10 milliseconds increments but unsure if I //have this right
var ms=0;
var sec=0;
var min=0;
var frame= function() {
If(ms==1000)
ms=0;
sec++;
}
if(sec==60) {
sec=0;
min++;
document.getElementById("s_seconds").innerHTML = valueOf(sec);
document.getElementById("s_minutes").innerHTML = valueOf(min);
document.getElementById("s_ms").innerHTML = valueOf(ms);
}
};
var startStopwatch = function(evt) {
};
var stopStopwatch = function(evt) {
};
var resetStopwatch = function(evt) {
};
window.onload = function() {
displayCurrentTime();
setInterval(tickStopwatch, 1000);
};
"use strict"; //evt is in a separate file
var evt = {
attach: function(node, eventName, func) {
},
detach: function(node, eventName, func) {
},
preventDefault: function(e) {
}
};
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Clock</title>
<link rel="stylesheet" href="clock.css">
<script src="library_event.js"></script>
<script src="clock.js"></script>
</head>
<body>
<main>
<h1>Digital clock with stopwatch</h1>
<fieldset>
<legend>Clock</legend>
<span id="hours"> </span>:
<span id="minutes"> </span>:
<span id="seconds"> </span>
<span id="ampm"> </span>
</fieldset>
<fieldset>
<legend>Stop Watch</legend>
Start
Stop
Reset
<span id="s_minutes">00</span>:
<span id="s_seconds">00</span>:
<span id="s_ms">000</span>
</fieldset>
</main>
</body>
</html>
If you incrementally update the values ms, seconds, minutes you'll never be accurate. You'll lose time with every update. There simply is no interval that can operate at that speed and accuracy in JS.
Instead, compute them from the internal clock
let offset = 0,
paused = true;
render();
function startStopwatch(evt) {
if (paused) {
paused = false;
offset -= Date.now();
render();
}
}
function stopStopwatch(evt) {
if (!paused) {
paused = true;
offset += Date.now();
}
}
function resetStopwatch(evt) {
if (paused) {
offset = 0;
render();
} else {
offset = -Date.now();
}
}
function format(value, scale, modulo, padding) {
value = Math.floor(value / scale) % modulo;
return value.toString().padStart(padding, 0);
}
function render() {
var value = paused ? offset : Date.now() + offset;
document.querySelector('#s_ms').textContent = format(value, 1, 1000, 3);
document.querySelector('#s_seconds').textContent = format(value, 1000, 60, 2);
document.querySelector('#s_minutes').textContent = format(value, 60000, 60, 2);
if(!paused) {
requestAnimationFrame(render);
}
}
<fieldset>
<legend>Stop Watch</legend>
Start
Stop
Reset
<span id="s_minutes">00</span>:
<span id="s_seconds">00</span>:
<span id="s_ms">000</span>
</fieldset>
offset stores two values: when paused it stores the value at which you stopped, otherwise it stores the offset you have to add to Date.now() to compute the value.
The value is the time in ms, computing seconds and minutes out of it is basic arithmetic.
Your interval function is set to run once per 1000ms (or 1 sec), which won't give you 1ms resolution. In fact, the best you can do is 10ms resolution with setInterval. You also need to increment a counter, which you currently don't do.
Try:
setInterval(tickStopwatch, 10);
var ms = 0;
var tickStopwatch = function() {
var ms += 10;
// rest of your logic
}
Here is the CodePen of the animation. It flashes for the first cycle of the frames displayed. Is there a way to stop this from happening?
Any help would be very much appreciated!
let frames = [
"http://i.imgur.com/QhvQuaG.png",
"http://i.imgur.com/VjSpZfB.png",
"http://i.imgur.com/Ar1czX0.png",
"http://i.imgur.com/ROfhCv4.png",
"http://i.imgur.com/6B32vk7.png",
"http://i.imgur.com/2t5MWOL.png",
"http://i.imgur.com/a9wLBbc.png",
"http://i.imgur.com/OBKcW8f.png",
"http://i.imgur.com/RC6wLgw.png",
"http://i.imgur.com/2HyI8yS.png"];
let startframe = 0;
function arrow(){
let start = Date.now();
let timer = setInterval(function() {
let timePassed = Date.now() - start;
if (timePassed >= 20000) {
clearInterval(timer); // finish the animation after 2 seconds
return;
}
move();
}, 200);
}
function move(){
if (startframe==(frames.length-1)){
startframe=0;
} else {
startframe++;
}
// document.getElementById('continue').style.backgroundSize = "100%";
document.getElementById('continue').style.background = "url(" + frames[startframe] +")";
document.getElementById('continue').style.backgroundSize = "100%";
}
#continue {
width: 80px;
height:40px;
}
<div onclick = "arrow()">Start</div>
<div id="continue"></div>
If you look at the network tab of your browser dev tools, you'll see that the flashing happens when the browser is loading the images.
You should preload all the images before starting the animation, like so:
let frames = [
"http://i.imgur.com/QhvQuaG.png",
"http://i.imgur.com/VjSpZfB.png",
"http://i.imgur.com/Ar1czX0.png",
"http://i.imgur.com/ROfhCv4.png",
"http://i.imgur.com/6B32vk7.png",
"http://i.imgur.com/2t5MWOL.png",
"http://i.imgur.com/a9wLBbc.png",
"http://i.imgur.com/OBKcW8f.png",
"http://i.imgur.com/RC6wLgw.png",
"http://i.imgur.com/2HyI8yS.png"
]
var startframe = 0
var images = [] // This array will contain all the downloaded images
function preloadImages() {
var loaded = 0
for (i = 0; i < frames.length; i++) {
images[i] = new Image();
images[i].onload = function() {
loaded += 1
if (loaded >= frames.length) {
arrow()
}
}
images[i].src = frames[i]
}
}
function arrow(){
let start = Date.now();
let timer = setInterval(function() {
let timePassed = Date.now() - start;
if (timePassed >= 20000) {
clearInterval(timer) // finish the animation after 2 seconds
return;
}
move()
}, 200)
}
function move() {
var c = document.getElementById('continue')
c.innerHTML = '' // remove the content of #continue
// Insert the already exiting image from the images array
// into the container instead of downloading again with css
c.append(images[startframe])
if (startframe >= frames.length - 1) {
startframe = 0
}
else {
startframe++
}
}
#continue {
width: 80px;
height:40px;
}
#continue > img {
max-width: 100%;
}
<div onclick = "preloadImages()">Start</div>
<div id="continue"></div>
This is because the images need to be loaded when viewed for the first time. It is possible to pre-load images in different ways. Here are three ways to preload images.
Sample:
http://codepen.io/anon/pen/xgZMVd/
html file:
<div class="game-page">
</div>
css file:
.game-page {
width: 1024px;
height: 768px;
position: absolute;
background-color: grey;
}
.stick {
position: absolute;
left: 50%;
margin-left: -94px;
top: -60px;
width: 188px;
height: 50px;
background-color: black;
}
js file:
$(document).ready(function() {
init();
});
var CSS_CLASS = { STICK: "stick" },
changeSpeedTime = 1000, //in milliseconds
gameTime = 60, //in seconds
timer = 1, //in seconds
windowWidth = 1024,
windowHeight = 768,
stickTop = -60,
restStickTime = 0, //in milliseconds
initialStickInterval = 1000, //in milliseconds
stickInterval = null, //in milliseconds
initialStickDuration = 7, //in seconds
stickDuration = null, //in seconds
stickTweensArray = [],
changeSpeedInterval = null,
countdownTimerInterval = null,
generateSticksInterval = null,
generateSticksTimeout = null,
$gamePage = null;
function init() {
initVariables();
initGamePage();
}
function changingSpeedFunction(x){
var y = Math.pow(2, (x / 20));
return y;
}
function initVariables() {
$gamePage = $(".game-page");
stickDuration = initialStickDuration;
stickInterval = initialStickInterval;
}
function initGamePage() {
TweenMax.ticker.useRAF(false);
TweenMax.lagSmoothing(0);
initGamePageAnimation();
}
function initGamePageAnimation () {
generateSticks();
changeSpeedInterval = setInterval(function () {
changeSpeed();
}, changeSpeedTime);
countdownTimerInterval = setInterval(function () {
updateCountdown();
}, 1000);
}
function changeSpeed () {
var x = timer;
var y = changingSpeedFunction(x); //change speed function
stickDuration = initialStickDuration / y;
stickInterval = initialStickInterval / y;
changeCurrentSticksSpeed();
generateSticks();
}
function changeCurrentSticksSpeed () {
stickTweensArray.forEach(function(item, i, arr) {
var tween = item.tween;
var $stick = item.$stick;
var oldTime = tween._time;
var oldDuration = tween._duration;
var newDuration = stickDuration;
var oldPosition = stickTop;
var newPosition = $stick.position().top;
var oldStartTime = tween._startTime;
var distance = newPosition - oldPosition;
var oldSpeed = distance / oldTime;
var newSpeed = oldSpeed * oldDuration / newDuration;
var newTime = distance / newSpeed;
var currentTime = oldStartTime + oldTime;
var newStartTime = currentTime - newTime;
item.tween._duration = newDuration;
item.tween._startTime = newStartTime;
});
}
function generateSticks () {
if (restStickTime >= changeSpeedTime) {
restStickTime -= changeSpeedTime;
restStickTime = Math.abs(restStickTime);
} else {
generateSticksTimeout = setTimeout(function () {
generateSticksInterval = setInterval(function () {
generateStick();
restStickTime -= stickInterval;
if (restStickTime <= 0) {
clearInterval(generateSticksInterval);
restStickTime = Math.abs(restStickTime);
}
}, stickInterval);
generateStick();
restStickTime = changeSpeedTime - Math.abs(restStickTime) - stickInterval;
if (restStickTime <= 0) {
clearInterval(generateSticksInterval);
restStickTime = Math.abs(restStickTime);
}
}, restStickTime);
}
}
function generateStick () {
var $stick = $("<div class='" + CSS_CLASS.STICK + "'></div>").appendTo($gamePage);
animateStick($stick);
}
function animateStick ($stick) {
var translateYValue = windowHeight + -stickTop;
var tween = new TweenMax($stick, stickDuration, {
y: translateYValue, ease: Power0.easeNone, onComplete: function () {
$stick.remove();
stickTweensArray.shift();
}
});
stickTweensArray.push({tween:tween, $stick:$stick});
}
function updateCountdown () {
timer++;
if (timer >= gameTime) {
onGameEnd();
clearInterval(changeSpeedInterval);
clearInterval(countdownTimerInterval);
clearInterval(generateSticksInterval);
clearTimeout(generateSticksTimeout);
}
}
function onGameEnd () {
var $sticks = $gamePage.find(".stick");
TweenMax.killTweensOf($sticks);
}
So, as I researched, I have next situation:
TweenMax (as it uses requestAnimationFrame) freezes when tab is inactive.
setInterval keep going when tab is inactive (also it's delay may change when tab is inactive, depends on browser)
Is there any other javascript functionality that changes when tab is inactive?
Then I have 2 solutions:
Freeze whole game, when tab is inactive.
Keep going, when tab is inactive.
With first solution I have next problem: as TweenMax uses requestAnimationFrame, it works correct according to this solution (freezes animation), but how can I freeze intervals and timeouts when tab is inactive and then resume intervals and timeouts?
With second solution I can use TweenMax.lagSmoothing(0) and TweenMax.ticker.useRAF(false) for animation and it works, but anyway something goes wrong with intervals and/or timeouts. I expected that animation goes wrong because of change of interval delay to 1000+ ms when tab is inactive (according to http://stackoverflow...w-is-not-active), but I disabled acceleration and set delays to 2000ms and it didn't help.
Please help me with at least one solution. Better with both to have some variety.
Resolved problem with first solution: freeze whole game. Just used TweenMax.delayedCall() function instead of setTimeout and setInterval and whole animation synchronized pretty well.
I've been working on a very simple animation script. The images are to move when clicked, stop when clicked again, etc. I have this all working fine. The trouble is I have a stop everything button below the images which is suppose to not only stop the images but to return them to their original position. So far it stops, but it doesn't return.
I've searched and found a few different solutions, however this is a university assignment and I am NOT allowed to use Jquery.
This is my script:
var timer = null;
var cat = null;
var dog = null;
function moveOver1Pixel()
{
cat.style.left = parseInt(cat.style.left) + 1 + "px";
}
function moveOver10Pixels()
{
dog.style.left = parseInt(dog.style.left) + 10 + "px";
}
window.onload=function()
{
//MOVE CAT FUNCTION
cat = document.getElementById("cat");
cat.onclick=function(){
if(timer == null)
{
timer = setInterval("moveOver1Pixel();", 100);
}else{
clearInterval(timer);
timer = null;
}
}
// MOVE DOG FUNCTION
dog = document.getElementById("dog");
dog.onclick=function(){
if(timer == null)
{
timer = setInterval("moveOver10Pixels();", 30);
}else{
clearInterval(timer);
timer = null;
}
}
// STOP BUTTON FUNCTION
document.getElementById("stop").onclick=function()
{
window.clearInterval(timer);
timer = null;
reset = true;
}
}
and this is the html portion I am using:
<img src="cat.jpg" id="cat" style="height:100px;position:absolute;left:10px">
<img src="dog.jpg" id="dog" style="height:100px;position:absolute;left:10px;top:110px">
<input type="button" value="EVERYONE STOP" id="stop" style="position:absolute;left:10px;top:220px">
I feel like the solution to my problem should be simple... something I'm missing inside of the button's onclick function? Or am I going to have to create an entirely new function for the reset/return of the images to their original place?
I changed a lot:
var timer, cat, dog, ticksCat = 0, ticksDog = 0,
moveOver1Pixel = function(inc){
cat.style.left = parseInt(cat.style.left) + inc + "px";
// Don't change if inc is -1, ~-1 === 0 == false
if(~inc) ticksCat+=inc;
},
moveOver10Pixels = function(inc){
dog.style.left = parseInt(dog.style.left) + inc*10 + "px";
if(~inc) ticksDog+=inc;
};
window.onload=function(){
//MOVE CAT FUNCTION
cat = document.getElementById("cat");
cat.onclick=function(){
if(!timer){
timer = setInterval(function(){
moveOver1Pixel(+1);
}, 100);
}else{
clearInterval(timer);
timer = null;
}
};
// MOVE DOG FUNCTION
dog = document.getElementById("dog");
dog.onclick=function(){
if(!timer){
timer = setInterval(function(){
moveOver10Pixels(+1);
}, 30);
}else{
clearInterval(timer);
timer = null;
}
};
// STOP BUTTON FUNCTION
document.getElementById("stop").onclick=function(){
window.clearInterval(timer);
timer = null;
reset = true;
// to do immediately
while(ticksCat--){ moveOver1Pixel(-1); }
while(ticksDog--){ moveOver10Pixels(-1); }
/* animated
while(ticksCat--){ setTimeout(function(){moveOver1Pixel(-1)}, 100*ticksCat);}
while(ticksDog--){ setTimeout(function(){moveOver10Pixels(-1)}, 30*ticksDog);}
Multiplication on time is to synchronously set a queue of async moves
*/
};
};
How about this?
document.getElementById("stop").onclick=function()
{
window.clearInterval(timer);
timer = null;
reset = true;
document.getElementById("cat").style.left = "10px"
document.getElementById("dog").style.left = "10px"
}
var timer = null;
var cat = null;
var dog = null;
function moveOver1Pixel()
{
cat.style.left = parseInt(cat.style.left) + 1 + "px";
}
function moveOver10Pixels()
{
dog.style.left = parseInt(dog.style.left) + 10 + "px";
}
window.onload=function()
{
//MOVE CAT FUNCTION
cat = document.getElementById("cat");
cat.onclick=function(){
if(timer == null)
{
timer = setInterval("moveOver1Pixel();", 100);
cat.startLeft=cat.offsetLeft;
cat.startTop=cat.offsetTop;
}else{
clearInterval(timer);
timer = null;
}
}
// MOVE DOG FUNCTION
dog = document.getElementById("dog");
dog.onclick=function(){
if(timer == null)
{
timer = setInterval("moveOver10Pixels();", 30);
dog.startLeft=dog.offsetLeft;
dog.startTop=dog.offsetTop;
}else{
clearInterval(timer);
timer = null;
}
}
// STOP BUTTON FUNCTION
document.getElementById("stop").onclick=function()
{
window.clearInterval(timer);
timer = null;
reset = true;
if (typeOf cat.startLeft !=undefined)
{
cat.style.left=cat.startLeft+"px";
cat.style.top=cat.startTop+"px";
}
if (typeOf dog.startLeft !=undefined)
{
dog.style.left=dog.startLeft+"px";
dog.style.top=dog.startTop+"px";
}
}
}
A more modern and "proper" solution to would be to use css transitions to handle the animation while using JS to control the position:
var cat = document.getElementById("cat");
var dog = document.getElementById("dog");
cat.addEventListener('click', moveIt);
dog.addEventListener('click', moveIt);
function moveIt()
{
this.style.left = "400px";
}
// STOP BUTTON FUNCTION
document.getElementById("stop").addEventListener('click', function()
{
cat.style.removeProperty('left');
dog.style.removeProperty('left');
//Uncomment for instant-reset
/*
cat.style.transitionDuration = dog.style.transitionDuration = '0s';
window.setTimeout(function()
{
cat.style.removeProperty('transition-duration');
dog.style.removeProperty('transition-duration');
}, 250);
*/
});
With your css looking something like this:
#cat, #dog
{
position: absolute;
left: 10px;
transition: 10s left linear;
}
#cat
{
left: 25px
}
#dog
{
transition-duration: 1s;
}
See this JSFiddle for a demo: http://jsfiddle.net/SPHMM/3/. Of course, if the assignment is specifically to do all the animation in javascript, this probably doesn't count. But for real life applications, this should provide the best quality, lowest cpu usage animation.