How to keep the arrow position unchanged when restart it using requestAnimationFrame()? - javascript

I tried to understand an example on MDN.
The effect of the current code is a bit confusing to me - after a pause if you wait a while and start again, the position of the arrow is not where it originally stopped.
How to make sure to restart where I left off? I thought of a way as below, but I am not very satisfied with it.
const spinner = document.querySelector('div')
let rotateCount = 0
let startTime = null
let rAF
let spinning = false
let previousRotateCount = 0
let hasStopped = false
let rotateInterval
function draw(timestamp) {
if (hasStopped) {
// The angle of each rotation is constant
rotateCount += rotateInterval
rotateCount %= 360
} else {
if (!startTime) {
startTime = timestamp
}
rotateCount = (timestamp - startTime) / 3
rotateCount %= 360
rotateInterval = rotateCount - previousRotateCount
previousRotateCount = rotateCount
}
console.log(rotateCount)
spinner.style.transform = `rotate(${rotateCount}deg)`
rAF = requestAnimationFrame(draw)
}
document.body.addEventListener('click', () => {
if (spinning) {
hasStopped = true
cancelAnimationFrame(rAF)
} else {
draw()
}
spinning = !spinning
})
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: #f00;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
}
<div>↻</div>
I hope there is a better way. Thank you very much.

This example code is a bit weird, they define a global rotateCount that's never actually used and their use of the timestamp is ... yes, confusing.
Their let rotateCount = (timestamp - startTime) / 3; makes it that the animation will take 1080ms to perform a full revolution (360deg x 3 = 1080ms).
But this is based on the difference between the current time and the start time. So indeed, even if you pause the animation, when restarting it will just like it never did pause.
To make this, you'd need to actually use the global rotateCount (by not redefining a new variable inside draw), and increment it every time by the amount of rotation that was needed since last draw, and not since the overall beginning.
Then you just need to ensure that the last-drawn timestamp gets updated when you resume the animation and you get your animation to actually pause and resume.
const spinner = document.querySelector('div');
const duration = 1080; // ms to perform a full revolution
const speed = 360 / duration;
let rotateCount = 0;
let rAF;
// we'll set this in the starting code
// (in the click event listener)
let lastTime;
let spinning = false;
// Create a draw() function
function draw(timestamp) {
// get the elapsed time since the last time we did fire
// we directly get the remainder from "duration"
// because we're in a looping animation
const elapsed = (timestamp - lastTime) % duration;
// for next time, lastTime is now
lastTime = timestamp;
// add to the previous rotation how much we did rotate
rotateCount += elapsed * speed;
// Set the rotation of the div to be equal to rotateCount degrees
spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
// Call the next frame in the animation
rAF = requestAnimationFrame(draw);
}
// event listener to start and stop spinner when page is clicked
document.body.addEventListener('click', () => {
if(spinning) {
cancelAnimationFrame(rAF);
spinning = false;
} else {
// reset lastTime with either the current frame time
// or JS time if unavailable
// so that in the next draw
// we see only the time that did elapse
// from now to then
lastTime = document.timeline?.currentTime || performance.now();
// schedule the next draw
requestAnimationFrame( draw )
spinning = true;
}
});
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: red;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
user-select: none;
}
<div>↻</div>

Related

How to change an element's animate keyframe while animation is running?

I have a little mouse speed detector (which is far from perfect) which gives me the current mouse speed every 100ms in the variable window.mouseSpeed.t.
I only implemented it because I want to have a funny animation on the bottom edge of the screen with a bar that grows with higher speeds and shrinks with lower speeds.
I want it to be animated with Element.animate().
The only problem is: How can I change the Animation's end keyframe (I only give an end frame so the browser assumes the current status as the first frame) while the animation is running?
I want to achieve that the bar smoothly changes its length.
// The code I want to have animated is below this function.
// Mouse speed tracker (I hope this isn't too horrible code):
document.addEventListener('DOMContentLoaded', mausgeschwindigkeitVerfolgen, {once:true});
function mausgeschwindigkeitVerfolgen() { // "Mausgeschwindigkeit verfolgen" means "track mouse speed" in German
var speedX = NaN;
var speedY = NaN;
var posX = NaN;
var posY = NaN;
var speed = NaN;
document.addEventListener("mousemove", function(ev){
speedX += Math.abs(ev.movementX);
speedY += Math.abs(ev.movementY);
speed = 10*Math.sqrt(ev.movementX**2+ev.movementY**2);
window.mousePosition = {x:posX = ev.clientX,y:posY = ev.clientY};
}, false);
setInterval(function(){
[window.mouseSpeed, window.mousePosition] = [{x:speedX,y:speedY,t:speed}, {x:posX,y:posY}]; // Werte in window.mouseSpeed und window.mouseDistance speichern
speed = totalX = totalY = 0;
}, 100);
window.mausgeschwindigkeitVerfolgen = () => {return {speed:window.mouseSpeed, pos:window.mousePosition};};
return {speed:window.mouseSpeed, pos:window.mousePosition};
}
// --- This is the code I want to have animated: ---
setInterval(() => {
document.querySelector('div#mouseSpeedIndicator').style.width = window.mouseSpeed.t+'px';
//document.querySelector('div#mouseSpeedIndicator').animate({width:'0px'}, {duration:1000,iterations:1}); // This just keeps the bar at width 0, I want it to slowly change to any newly set width
}, 100);
div#mouseSpeedIndicator {
position: fixed;
bottom: 0px;
left: 0px;
height: 33px;
background-color: green;
max-width: 100vh;
border: 0px solid green;
border-top-right-radius: 10px;
}
<!-- What I currently have -->
<div id="mouseSpeedIndicator"></div>
First, something as simple as one additional line of the transition CSS property like e.g. ...
transition: width 1s ease-out;
... already does the job; no need for more JavaScript based computation and DOM manipulation.
But of cause the OP's script could be dramatically simplified with or even without the support of an external helper method like throttle (lodash _.throttle or underscorejs _.throttle) where the latter would create a delayed executed version of the passed function which for the OP's example-script is the 'mousemove'-handler.
This handler before being throttled (or even not throttled) could be created as a bound version of the function which actually computes the speed value and updates the indicator-node's appearance.
function handleMouseSpeedIndicatorUpdateFromBoundData(evt) {
const { movementX, movementY } = evt;
const { rootNode, timeoutId } = this;
// prevent indicator nullification at time.
clearTimeout(timeoutId);
// compute `speed`.
const speed = 10 * Math.sqrt(movementX**2 + movementY**2);
// update indicator appearance.
rootNode.style.width = `${ speed }px`;
// trigger delayed indicator nullification.
this.timeoutId = setTimeout(() => rootNode.style.width = 0, 110);
}
function initialzeMouseSpeedIndicator() {
document
.addEventListener(
'mousemove',
// create throttled version of the just created bound handler.
_.throttle(
// create handler function with bound contextual data.
handleMouseSpeedIndicatorUpdateFromBoundData.bind({
rootNode: document.querySelector('#mouseSpeedIndicator'),
timeoutId: null,
}), 100
),
false
);
}
// - no need for `'DOMContentLoaded'`
// in order to initialize the indicator.
initialzeMouseSpeedIndicator();
div#mouseSpeedIndicator {
position: fixed;
top: 0px;
left: 0px;
height: 33px;
background-color: green;
max-width: 100vh;
border: 0px solid green;
border-bottom-right-radius: 10px;
/* proposed change(s) */
transition: width 1s ease-out;
/* transition: width .5s ease-in; */
/* transition: width .5s ease-in-out; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<div id="mouseSpeedIndicator"></div>

requestAnimationFrame and setInterval animating at a different pace

I am translating two divs using setInterval and requestAnimationFrame. Animated using interval, the div translates at a rate of 3px per (1000/60)ms, which equates to 180px per 1000ms. At the same time, the div animated using requestAnimationFrame translates at a rate of 0.18px per 1ms, which equates to 180px per 1000ms.
However, they curiously aren't translating at the speed I want. Look at the example below:
let interval = document.querySelector('.interval')
let raq = document.querySelector('.raq')
function startAnimation() {
let translateValue = 0
setInterval(() => {
translateValue = (translateValue + 3) % 300
interval.style.transform = `translateX(${translateValue}px)`
}, 1000 / 60)
let raqAnimation = (timeElapsed) => {
let translateValue = (timeElapsed * 0.18) % 300
raq.style.transform = `translateX(${translateValue}px)`
window.requestAnimationFrame(raqAnimation)
}
window.requestAnimationFrame(raqAnimation)
}
window.setTimeout(startAnimation, 1000)
.interval,
.raq {
width: 50px;
height: 50px;
border-radius: 5px;
background-color: #121212;
margin: 1rem;
}
<div class="interval"></div>
<div class="raq"></div>
Did I use setInterval or requestAnimationFrame wrong or did I fail at the maths calculation?
There is a absolutely no guarantee that your iterval will run at the requested rate so just adding some constant every callback like the code does for the setInterval case isn't going to match.
you could use performance.now or Date.now as your clock in the setInterval case
let interval = document.querySelector('.interval')
let raq = document.querySelector('.raq')
function startAnimation() {
setInterval(() => {
const translateValue = (performance.now() * 0.18) % 300
interval.style.transform = `translateX(${translateValue}px)`
}, 1000 / 60)
let raqAnimation = (timeElapsed) => {
let translateValue = (timeElapsed * 0.18) % 300
raq.style.transform = `translateX(${translateValue}px)`
window.requestAnimationFrame(raqAnimation)
}
window.requestAnimationFrame(raqAnimation)
}
window.setTimeout(startAnimation, 1000)
.interval,
.raq {
width: 50px;
height: 50px;
border-radius: 5px;
background-color: #121212;
margin: 1rem;
}
<div class="interval"></div>
<div class="raq"></div>
they still may not perfectly align though as (a) they are actually running at different times and so get different time values and (b) the run at different rates. They will be close though since it's effectively the same clock

Progress bar between given time and current time

I calculate the time between a given time and current time to get the seconds remaining for the progress bar.
It's working, but not when i refresh the page, it starts again from 0%.
I want I like this:
Current time: 15:30:00
Time to given time: 16:00:00
Progress bar is at 50%.
JS:
var start = (“01-02-2020 15:00:00”)
var end = (“01-02-2020 16:00:00”
var cur = new Date();
var diff = end.getTime() - cur.getTime();
var duration = (diff);
$outer = $("#pbar_outerdiv");
$outer.click(function() {
$('#pbar_innerdiv')
.stop()
.css({ width: 0 })
.animate({ width: "100%" }, duration, "linear", function() { window.location.reload(1); });
})
$outer.trigger("click");
});
}
});
HTML:
<div id="pbar_outerdiv">
<div id="pbar_innerdiv"></div>
</div>
CSS:
#pbar_outerdiv {
margin-top: 50px;
width: 100%;
height: 20px;
background: #ccc;
}
#pbar_innerdiv {
height: 100%;
width: 0;
background: #f00;
}
Consider the following example. You need to get the starting point, the ending point and the current time. Find the delta between the end time and the start time to find out the duration of the progress, we'll keep that in duration. Then find how much time has passed since the start time and divide that by duration, we'll keep that in t. Then use that as the value of a <progress/>.
For the following example, we will set the start time to 15 minutes ago and the end time to 30 minutes from now.
const curr = new Date();
const start = new Date();
const end = new Date();
start.setMinutes(curr.getMinutes() - 15);
end.setMinutes(curr.getMinutes() + 30);
const duration = end.getMinutes() - start.getMinutes();
const t = (curr.getMinutes() - start.getMinutes()) / duration;
console.log(t);
const progress = document.getElementById("progress");
progress.value = t;
<progress id="progress" value="0"></progress>

I want to be able to pause a block in place when I click on it

Here is the website with the game/source code and want to try and see if i can pause a block as it falls when i left click it with my mouse but not sure the proper function for it. ( https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/By_example/Raining_rectangles )
You must clearTimeout() to make it paused, I have implemented a toggle on click of box i.e play/pause.
(function() {
"use strict"
window.addEventListener("load", setupAnimation, false);
var gl,
timer,
rainingRect,
scoreDisplay,
missesDisplay,
status,
paused = false;
function setupAnimation(evt) {
window.removeEventListener(evt.type, setupAnimation, false);
if (!(gl = getRenderingContext()))
return;
gl.enable(gl.SCISSOR_TEST);
rainingRect = new Rectangle();
timer = setTimeout(drawAnimation, 17);
document.querySelector("canvas")
.addEventListener("click", playerClick, false);
var displays = document.querySelectorAll("strong");
scoreDisplay = displays[0];
missesDisplay = displays[1];
status = displays[2];
}
var score = 0,
misses = 0;
function drawAnimation() {
gl.scissor(rainingRect.position[0], rainingRect.position[1],
rainingRect.size[0], rainingRect.size[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
rainingRect.position[1] -= rainingRect.velocity;
if (rainingRect.position[1] < 0) {
misses += 1;
missesDisplay.innerHTML = misses;
rainingRect = new Rectangle();
}
// We are using setTimeout for animation. So we reschedule
// the timeout to call drawAnimation again in 17ms.
// Otherwise we won't get any animation.
timer = setTimeout(drawAnimation, 17);
}
function playerClick(evt) {
// We need to transform the position of the click event from
// window coordinates to relative position inside the canvas.
// In addition we need to remember that vertical position in
// WebGL increases from bottom to top, unlike in the browser
// window.
var position = [
evt.pageX - evt.target.offsetLeft,
gl.drawingBufferHeight - (evt.pageY - evt.target.offsetTop),
];
// if the click falls inside the rectangle, we caught it.
// Increment score and create a new rectangle.
var diffPos = [position[0] - rainingRect.position[0],
position[1] - rainingRect.position[1]
];
if (diffPos[0] >= 0 && diffPos[0] < rainingRect.size[0] &&
diffPos[1] >= 0 && diffPos[1] < rainingRect.size[1]) {
score += 1;
scoreDisplay.innerHTML = score;
// rainingRect = new Rectangle();
if (!paused) {
clearTimeout(timer)
paused = true;
status.innerHTML = 'Paused';
} else {
timer = setTimeout(drawAnimation, 17);
paused = false;
status.innerHTML = 'Playing';
}
}
}
function Rectangle() {
// Keeping a reference to the new Rectangle object, rather
// than using the confusing this keyword.
var rect = this;
// We get three random numbers and use them for new rectangle
// size and position. For each we use a different number,
// because we want horizontal size, vertical size and
// position to be determined independently.
var randNums = getRandomVector();
rect.size = [
5 + 120 * randNums[0],
5 + 120 * randNums[1]
];
rect.position = [
randNums[2] * (gl.drawingBufferWidth - rect.size[0]),
gl.drawingBufferHeight
];
rect.velocity = 1.0 + 6.0 * Math.random();
rect.color = getRandomVector();
gl.clearColor(rect.color[0], rect.color[1], rect.color[2], 1.0);
function getRandomVector() {
return [Math.random(), Math.random(), Math.random()];
}
}
function getRenderingContext() {
var canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var gl = canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl");
if (!gl) {
var paragraph = document.querySelector("p");
paragraph.innerHTML = "Failed to get WebGL context." +
"Your browser or device may not support WebGL.";
return null;
}
gl.viewport(0, 0,
gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
return gl;
}
})();
<style>
body {
text-align: center;
}
canvas {
display: block;
width: 280px;
height: 210px;
margin: auto;
padding: 0;
border: none;
background-color: black;
}
button {
display: block;
font-size: inherit;
margin: auto;
padding: 0.6em;
}
</style>
<p>You caught
<strong>0</strong>.
You missed
<strong>0</strong>
Status
<strong>Playing</strong>.</p>
<canvas>Your browser does not seem to support
HTML5 canvas.</canvas>
As you can see in the code the function drawAnimation() is calling itself every 17ms using setTimeout() JavaScript function (and this is what creates steady animation).
function drawAnimation() {
.
.
.
timer = setTimeout(drawAnimation, 17);
}
In order to pause/stop the animation you would need to use JavaScript function clearTimeout(timer). Since you want to stop/pause the animation on click event you could just reuse the function playerClick (evt) { ... } from the code you already have and put the function clearTimeout(timer) there.
function playerClick (evt) {
.
.
.
clearTimeout(timer);
}
If you want to be able to continue with animation after you have paused it you'll need to implement some switch-logic (pause if it is already playing, play if it is already paused) inside your function playerClick (evt) or to use timers to continue the animation after some time, for example.

Transition a circle from one point to another given x, y position with a pause at every point

I have code JSFiddle to transition a circle from point A to point B. But, how do I modify code to make it work if the 'move' method is called like below and also circle has to pause for a second at every point. Please help.
myBall.move(50,300).move(150,400).move(100,200).move(130,230);
After a bit of playing around I think I've managed to come up with what you were looking for, this creates a queue of move events which is pushed to each time move is called, this sequence of events is then run in order by the run function (note that I've added a .move(0,0) at the beginning, something strange happens running this in an SO snippet, this also adds a brief delay on SO, but it seems to work fine on JSFiddle without it):
function ball() {
const elem = document.getElementById("ball");
var q = [];
var running = false;
var me = this;
this.move = function(x, y) {
q.push([x, y]);
if (!running) {
running = true;
me.run();
}
return this;
}
this.run = function() {
if (q.length == 0) {
running = false;
} else {
var pos = q.shift();
elem.style.transform = `translate(${pos[0]}px, ${pos[1]}px)`;
setTimeout(me.run, 2000);
}
}
}
let myBall = new ball();
myBall.move(0, 0).move(50, 300).move(150, 400).move(100, 200).move(130, 230);
#ball {
width: 100px;
height: 100px;
border: 1px solid black;
border-radius: 50%;
transition: transform 2s;
}
<div id="ball">
</div>

Categories