I need to divide screen to 20 parts (horizontally) and set the value of mouse position from 1 to 20 to update sprite background-image position (for a smooth rotation animation). The code below is working, but there is a problem, when I move mouse very fast - than it can skip a few points, and I need to always change the number by one step. How can I achieve that?
https://codepen.io/kgalka/pen/vbpoqe
var frames = 20;
var frameWidth = Math.round(window.innerWidth / frames);
var xIndex;
function updatePosition(x) {
if (xIndex != x) {
xIndex = x;
document.getElementById('val').innerText = xIndex;
}
}
document.onmousemove = function(e) {
updatePosition(Math.round(e.clientX / frameWidth));
}
Ok i saw th example and i think that i understand the problem and here is how i would fix this.
Have a look and let me know if it work.
var frames = 20;
var frameWidth = Math.round(window.innerWidth / frames);
var xIndex;
var timeout;
function updatePosition(x) {
if (xIndex != x) {
xIndex = x;
document.getElementById('val').innerText = xIndex;
}
}
document.onmousemove = function(e) {
// clear the prev call if the mouse was to fast.
clearTimeout(timeout);
// Now ini new call to updatePosition
timeout= setTimeout(()=> updatePosition(Math.round(e.clientX / frameWidth)), 1 )
// you could play with the timeout and increase it from 1 to 100ms. and see what you prefere
}
<p id="val"></p>
According to several developers (link1, link2) the proper way to have a constant frame rate with requestAnimationFrame is to adjust the "last rendered" time within the game loop as follows:
function gameLoop() {
requestAnimationFrame(gameLoop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval); // This weird stuff
doGameUpdate(delta);
doGameRender();
}
}
Where interval is 1000/fps (i.e. 16.667ms).
The following line makes no sense to me:
then = now - (delta % interval);
Indeed if I try it I don't get smooth graphics at all but fast then slow depending on the CPU:
https://jsfiddle.net/6u82gpdn/
If I just let then = now (which makes sense) everything works smoothly:
https://jsfiddle.net/4v302mt3/
Which way is "correct"? Or what are the tradeoffs I am missing?
Delta time is bad animation.
It seems just about anyone will post a blog about the right way to do this and that, and be totally wrong.
Both articles are flawed as they do not understand how requestAnimationFrame is called and how it should be used in relation to frame rate and time.
When you use delta time to correct animation positions via requestAnimationFrame you have already presented the frame, it's too late to correct it.
requestAnimationFrame's callback function is passed an argument that holds the high precision time in ms (1/1000th) accurate to microseconds (1/1,000,000th) second. You should use that time not the Date objects time.
The callback is called as soon as possible after the last frame was presented to the display, there is no consistency in the interval between calls to the callback.
Methods that use delta time need to predict when the next frame is presented so that object can be render at the correct position for the upcoming frame. If your frame rendering load is high and variable, you can not predict at the start of frame, when the next frame will be presented.
The rendered frame is always presented during the vertical display refresh and is always on a 1/60th second time. The time between frames will always be integers multiples of 1/60th giving only frame rates of 1/60, 1/30, 1/20, 1/15 and so on
When you exit the callback function the rendered content is held in the backbuffer until the next vertical display refresh. Only then is it moved to display RAM.
The frame rate (vertical refresh) is tied to the device hardware and is perfect.
If you exit the callback late, so that the browser does not have time to move the canvas content to the display, the back buffer is held until the next vertical refresh. Your next frame will not be called until after the buffer has been presented.
Slow renders do not reduce frame rate, they cause frame rate oscillations between 60/30 frames per second. See example snippet using mouse button to add render load and see dropped frames.
Use the time supplied to the callback.
There is only one time value you should use and that is the time passed by the browser to the requestAnimationFrame callback function
eg
function mainLoop(time){ // time in ms accurate to 1 micro second 1/1,000,000th second
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Post frame correction error.
Don't use delta time based animation unless you must. Just let the frames drop or you will introduce animation noise in the attempt to reduce it.
I call this post frame correction error (PFCE). You are attempting to correct a position in time for an upcoming and uncertain time, based on the past frame time, which may have been in error.
Each frame you are rendering will appear some time from now (hopefully in the next 1/60th second). If you base the position on the previouse rendered frame time and you dropped a frame and this frame is on time you will render the next frame ahead of time by one frame, and the same applies to the previous frame which would have been rendered a frame behind as a frame was skipped. Thus with only a frame dropped you render 2 frames out of time. A total of 3 bad frames rather than 1.
If you want better delta time, count frames via the following method.
var frameRate = 1000/60;
var lastFrame = 0;
var startTime;
function mainLoop(time){ // time in ms accurate to 1 micro second 1/1,000,000th second
var deltaTime = 0
if(startTime === undefined){
startTime = time;
}else{
const currentFrame = Math.round((time - startTime) / frameRate);
deltaTime = (currentFrame - lastFrame) * frameRate;
}
lastFrame = currentFrame;
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
This does not eliminate PFCE but is better than irregular interval time if you use delta time as timeNow - lastTime.
Frames are always presented at a constant rate, requestAnimationFrame will drop frames if it can not keep up, but it will never present mid frame. The frame rates will be at fixed intervals of 1/60, 1/30, 1/20, or 1/15 and so on. Using a delta time that does not match these rates will incorrectly position your animation.
A snapshot of animation request frames
This is a timeline of requestAnimationframe for a simple animation function. I have annotated the results to show when the callback is called. During this time the frame rate was constant at a perfect 60fps no frames where dropped.
Yet the times between callbacks is all over the place.
Frame render timing
The example shows frame timing. Running in SO sandbox is not the ideal solution and to get good results you should run this in a dedicated page.
What it shows (though hard to see for small pixels) is the various time error from ideal times.
Red is frame time error from the callback argument. It will be stable near 0ms from 1/60th second ideal frame time.
Yellow is the frame time error calculated using performance.now(). It varies about 2 ms in total with the occasional peek outside the range.
Cyan is the frame time error calculated using Date.now(). You can clearly see the aliasing due to the poor resolution of the date's ms accuracy
Green dots are the difference in time between the callback time argument and the time reported by performance.now() and on my systems is about 1-2ms out.
Magenta is the last frame's render time calculated using performance now. If you hold the mouse button you can add a load and see this value climb.
Green vertical lines indicate that a frame has been dropped / skipped
The dark blue and black background marks seconds.
The primary purpose of this demo is to show how frames are dropped as render load increase. Hold the mouse button down and the render load will start to increase.
When the frame time gets close to 16 ms you will start to see frames dropped. Until the render load reaches about 32ms you will get frames between 1/60 and 1/30, first more at 1/60th for every one at 1/30th.
This is very problematic if you use delta time and post frame correction as you will constantly be over and under correcting the animation position.
const ctx = canvas.getContext("2d");
canvas.width = 512;
canvas.height = 380;
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
var lastTime; // callback time
var lastPTime; // performance time
var lastDTime; // date time
var lastFrameRenderTime = 0; // Last frames render time
var renderLoadMs = 0; // When mouse button down this slowly adds a load to the render
var pTimeErrorTotal = 0;
var totalFrameTime = 0;
var totalFrameCount = 0;
var startTime;
var clearToY = 0;
const frameRate = 1000/60;
ctx.font = "14px arial";
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime; // global to this
ctx.clearRect(0,0,w,h);
const graph = (()=>{
var posx = 0;
const legendW = 30;
const posy = canvas.height - 266;
const w = canvas.width - legendW;
const range = 6;
const gridAt = 1;
const subGridAt = 0.2;
const graph = ctx.getImageData(0,0,1,256);
const graph32 = new Uint32Array(graph.data.buffer);
const graphClearA = new Uint32Array(ctx.getImageData(0,0,1,256).data.buffer);
const graphClearB = new Uint32Array(ctx.getImageData(0,0,1,256).data.buffer);
const graphClearGrid = new Uint32Array(ctx.getImageData(0,0,1,256).data.buffer);
const graphFrameDropped = ctx.getImageData(0,0,1,256);
const graphFrameDropped32 = new Uint32Array(graphFrameDropped.data.buffer);
graphClearA.fill(0xFF000000);
graphClearB.fill(0xFF440000);
graphClearGrid.fill(0xFF888888);
graphFrameDropped32.fill(0xFF008800);
const gridYCol = 0xFF444444; // ms marks
const gridYColMaj = 0xFF888888; // 4 ms marks
const centerCol = 0xFF00AAAA;
ctx.save();
ctx.fillStyle = "black";
ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.font = "10px arial";
for(var i = -range; i < range; i += subGridAt){
var p = (i / range) * 128 + 128 | 0;
i = Number(i.toFixed(1));
graphFrameDropped32[p] = graphClearB[p] = graphClearA[p] = graphClearGrid[p] = i === 0 ? centerCol : (i % gridAt === 0) ? gridYColMaj : gridYCol;
if(i % gridAt === 0){
ctx.fillText(i + "ms",legendW - 2, p + posy);
ctx.fillText(i + "ms",legendW - 2, p + posy);
}
}
ctx.restore();
var lastFrame;
return {
step(frame){
if(lastFrame === undefined){
lastFrame = frame;
}else{
while(frame - lastFrame > 1){
if(frame - lastFrame > w){ lastFrame = frame - w - 1 }
lastFrame ++;
ctx.putImageData(graphFrameDropped,legendW + (posx++) % w, posy);
}
lastFrame = frame;
ctx.putImageData(graph,legendW + (posx++) % w, posy);
ctx.fillStyle = "red";
ctx.fillRect(legendW + posx % w,posy,1,256);
if((frame / 60 | 0) % 2){
graph32.set(graphClearA)
}else{
graph32.set(graphClearB)
}
}
},
mark(ms,col){
const p = (ms / range) * 128 + 128 | 0;
graph32[p] = col;
graph32[p+1] = col;
graph32[p-1] = col;
}
}
})();
function loop(time){
var pTime = performance.now();
var dTime = Date.now();
var frameTime = 0;
var framePTime = 0;
var frameDTime = 0;
if(lastTime !== undefined){
frameTime = time - lastTime;
framePTime = pTime - lastPTime;
frameDTime = dTime - lastDTime;
graph.mark(frameRate - framePTime,0xFF00FFFF);
graph.mark(frameRate - frameDTime,0xFFFFFF00);
graph.mark(frameRate - frameTime,0xFF0000FF);
graph.mark(time-pTime,0xFF00FF00);
graph.mark(lastFrameRenderTime,0xFFFF00FF);
pTimeErrorTotal += Math.abs(frameTime - framePTime);
totalFrameTime += frameTime;
totalFrameCount ++;
}else{
startTime = time;
}
lastPTime = pTime;
lastDTime = dTime;
lastTime = globalTime = time;
var atFrame = Math.round((time -startTime) / frameRate);
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,w,clearToY);
ctx.fillStyle = "black";
var y = 0;
var step = 16;
ctx.fillText("Frame time : " + frameTime.toFixed(3)+"ms",10,y += step);
ctx.fillText("Rendered frames : " + totalFrameCount,10,y += step);
ctx.fillText("Mean frame time : " + (totalFrameTime / totalFrameCount).toFixed(3)+"ms",10,y += step);
ctx.fillText("Frames dropped : " + Math.round(((time -startTime)- (totalFrameCount * frameRate)) / frameRate),10,y += step);
ctx.fillText("RenderLoad : " + lastFrameRenderTime.toFixed(3)+"ms Hold mouse into increase",10,y += step);
clearToY = y;
graph.step(atFrame);
requestAnimationFrame(loop);
if(mouse.button ){
renderLoadMs += 0.1;
var pt = performance.now();
while(performance.now() - pt < renderLoadMs);
}else{
renderLoadMs = 0;
}
lastFrameRenderTime = performance.now() - pTime;
}
requestAnimationFrame(loop);
canvas { border : 2px solid black; }
body { font-family : arial; font-size : 12px;}
<canvas id="canvas"></canvas>
<ul>
<li><span style="color:red">Red</span> is frame time error from the callback argument.</li>
<li><span style="color:yellow">Yellow</span> is the frame time error calculated using performance.now().</li>
<li><span style="color:cyan">Cyan</span> is the frame time error calculated using Date.now().</li>
<li><span style="color:#0F0">Green</span> dots are the difference in time between the callback time argument and the time reported by performance.now()</li>
<li><span style="color:magenta">Magenta</span> is the last frame's render time calculated using performance.now().</li>
<li><span style="color:green">Green</span> vertical lines indicate that a frame has been dropped / skipped</li>
<li>The dark blue and black background marks seconds.</li>
</ul>
For me I never use delta time for animation, and I accept that some frames will be lost. But overall you get a smoother animation using a fixed interval than attempting to correct the time post render.
The best way to get smooth animation is to reduce the render time to under 16ms, if you can't get that then use deltat time not to set animation frame but to selectively drop frames and maintain a rate of 30 frames per second.
The point of a delta time is to keep the frame-rate stable by compensating for time taken by computations.
Think of this code:
var framerate = 1000 / 60;
var exampleOne = function () {
/* computation that takes 10 ms */
setTimeout(exampleOne, framerate);
}
var exampleTwo = function () {
setTimeout(exampleTwo, framerate);
/* computation that takes 30 ms */
}
In example one the function would calculate for 10 ms and then wait the frame-rate before painting the next frame. This will inevitably lead to a frame-rate lower than the expected.
In example two the function would start the timer for the next iteration immediately and then calculate for 30 ms. This will lead to the next frame being painted before the previous is done calculating, bottle necking your application.
With delta-time you get the best of both worlds:
var framerate = 1000 / 60;
var exampleThree = function () {
var delta = Date.now();
/* computation that takes 10 to 30 ms */
var deltaTime = Date.now() - delta;
if (deltaTime >= framerate) {
requestAnimationFrame(exampleThree);
}
else {
setTimeout(function () { requestAnimationFrame(exampleThree); }, framerate - deltaTime);
}
};
With delta-time, which represents the calculation time, we know how much time we have left before the next frame needs to be painting.
We don't have the sliding performance from example one and we don't have a bunch of frames trying to draw at the same time as in example two.
I'm set up a simple animation with set Interval.
But I wanted to do an animation where it moves to a target position over time.
I used to this back in the flash days and have forgotten the process.
It remember using something like
property = (target - property)/speed;
But having a problem setting that up with the below setup.
I understand there is a 100 ways to do this and even using css but I just want to know how I could implement with that I have now. I just want that ease over time to happen with setInterval.
var sq = document.querySelector('.square');
button = document.querySelector('button');
var interval, toggle = 0, pos=0;
var targetX = 100;
var startX = 0;
button.addEventListener("click", (event) =>{
toggle += 1;
toggle = toggle % 2;
if( toggle > 0){
interval = setInterval(animate, 5);
}else{
clearInterval(interval);
}
});
function animate(){
//pos++
//sq.style.left = pos + 'px';
sq.style.left = (targetX - sq.style.left) / speed
}
Using setInterval() isn't the best choice for animations since it cannot sync to monitor updates. A better choice, and highly recommended, is requestAnimationFrame().
Right now the code uses an interval of 5ms which is way too low. The shortest frame update happens at 16.7ms (at 60Hz screens) so there is much overhead here.
I would also suggest using linear interpolation (AKA lerp) to do the animation, and time duration to define speed. Using interpolation allows you to further feed the t into an easing function.
Example
t is calculated based on start-time, current time and duration
t, now normalized to [0, 1] is fed to lerp function to obtain new position
translateX is used to move div to allow subpixeled animation. Final position can still be set by removing translation and a fixed position for left.
var reqId, // request ID so we can cancel anim.
startTime = 0,
source = 0,
target = 300,
duration = 1000, // duration in ms
sq = document.querySelector("div");
document.querySelector("button").onclick = function() {
startTime = 0; // reset time for t
cancelAnimationFrame(reqId); // abort existing animation if any
reqId = requestAnimationFrame(loop); // start new with time argument
function loop(time) {
if (!startTime) startTime = time; // set start time if not set already
t = (time - startTime) / duration; // calc t
var x = lerp(source, target, t); // lerp between source and target position
sq.style.transform = "translateX("+x+"px)"; // use transform to allow sub-pixeling
if (t < 1) requestAnimationFrame(loop); // loop until t=1
// else { here you can remove transform and set final position ie. using left}
}
// basic linear interpolation
function lerp(v1, v2, t) {return v1 + (v2-v1) * t}
};
div {position:absolute; left:0; top:40px; width:100px;height:100px;background:#c33}
<button>Animate</button>
<div></div>
I've got several functions all linked so it will ...
create new elements and set their properties and stuff
once elements are in place they should trigger function.
And they do! Kind of...
More like they trigger half of a function that's attached to them upon creation. The part where they onclick trigger a function that starts a loading on my progress bar (which is their purpose) is alright. But the much simpler part, where they hide after click, doesn't.
As the code is quite complex I'll place here larger part of it, so don't panic. Problem might be somewhere else then I expect. Here it is...
// defines function for checkpoint
function checkpointed() {
this.style.display = 'none'; // !here dwells the douch part!
if (toLoad < 100) {
toLoad += 100/$(this).attr('numButs');
var sim = setInterval(progressSim, 50);
}
// defining creation of checkpoints
function checkpoints(num) {
var angle = 4.72,
step = (2 * Math.PI) / num;
for (i = 1; i < num + 1; i++) {
var x = $('#progressBar').width()/2 + radius * Math.cos(angle) ;
var y = $('#progressBar').height()/2 + radius * Math.sin(angle);
angle += step;
var newContent = document.createElement('IMG');
var newCheckpoint = document.createElement('SPAN');
var numButs = document.createAttribute('numButs');
numButs.value = num;
var Class = document.createAttribute('class');
Class.value = 'checkpoint';
var img = document.createAttribute('src');
img.value = 'img/lock.png';
newContent.setAttributeNode(img);
newCheckpoint.setAttributeNode(numButs);
newCheckpoint.setAttributeNode(Class);
$(newCheckpoint).append(newContent);
$('.projectBar').append(newCheckpoint);
x -= 24;
y -= 24;
$(newCheckpoint).offset({top:y, left: x});
newCheckpoint.onclick = checkpointed;
};
};
// creates checkpoints upon clicking on create button
document.getElementById('create').onclick = function(){
checkpoints(document.getElementById('numCheckpoint').value);
$(this).hide();
$('#numCheckpoint').hide();
};
I should probably sum up what is this all about.
I have circular progressBar that measures progression of users project. User says "Hey, my project has like 5 steps (or 20 idc)" and "create" button will make 5 checkpoints placed on the progressBar evenly. By clicking on checkpoints you load the progress bar by 20% per clicked checkpoint.
Don't worry though, I've already figured out the code for loading and the geometrics.
However I'm bit stuck here... on simple onclick functions. Please if you have an idea try achieve it with plain JavaScript or jQuery (trying to do this without other frameworks, libraries or plugins).
EDIT: Just found out that checkpoint are set alright, as they really hide after clicking. Problem is in creation of checkpoints as the loop creates about 15 more checkpoints stacked one on another. So you have to click each of them to hide them all... So problem is in the loop.
EDIT2: Figured it out. The loop for (i = 1; i < num + 1; i++) had the numparameter as a String coming from input field. So simple parseInt() did the trick.
The mixed Jquery and plain Javascript is messing with my head... Any way how about when you create a new element, give it some sort of class. Instead of giving setting onclick, use jQuery's on selector to bind click events to those dynamic elements. Try The Following:
$(document).on("click", ".Checkpoint", function(event) {
$(event.target).hide();
if (toLoad < 100) {
toLoad += 100 / $(this).attr('numButs');
var sim = setInterval(progressSim, 50);
}
});
// defining creation of checkpoints
function checkpoints(num) {
var angle = 4.72,
step = (2 * Math.PI) / num;
for (i = 1; i < num + 1; i++) {
var x = $('#progressBar').width() / 2 + radius * Math.cos(angle);
var y = $('#progressBar').height() / 2 + radius * Math.sin(angle);
angle += step;
var newContent = $('<img></img>');
var newCheckpoint = $('<span></span>');
$("body").append(newCheckpoint);
newContent.attr("numButs", num);
newContent.attr("src", 'img/lock.png');
newContent.addClass("Checkpoint");
$(newCheckpoint).append(newContent);
$('.projectBar').append(newCheckpoint);
x -= 24;
y -= 24;
$(newCheckpoint).offset({
top: y,
left: x
});
}
}
// creates checkpoints upon clicking on create button
$(document).on("click","#create",function(e) {
checkpoints($('#numCheckpoint').val());
$(e.target).hide();
$('#numCheckpoint').hide();
});
Changed stuff to work more in jQuery, hope you don't mind...
OK, first time using Cesium. I have a simple, non-advanced question that I posted a couple times on their forms but no one is responding. So I thought I'd try here.
All I need to do is figure out how to stop an event before invoking another. Here's an example of what's happening. The globe spins on user-connect, but I need it to stop if they then click on "fly me home", etc.
Code below.
So the arg in spinGlobe is the spinRate. If I pass in 0.0 on the inital connect, it won't rotate. Perfect. So I though that if a click event ( user clicks fly me home ) then invoked spinGlobe(0.0) that the globe would stop rotating. But it doesn't. Can I not dynamically change the spinRate? Or am I going about this all wrong?
spinGlobe( 0.5 )
function spinGlobe( dynamicRate )
var previousTime = Date.now();
viewer.clock.onTick.addEventListener(function( clock ) {
var spinRate = dynamicRate;
var currentTime = Date.now();
var delta = ( currentTime - previousTime ) / 1000;
previousTime = currentTime;
viewer.scene.camera.rotate(Cesium.Cartesian3.UNIT_Z, -spinRate * delta);
});
}
spinGlobe( dynamicRate ){
var previousTime = Date.now();
this.viewer.scene.postRender.addEventListener(function (scene, time){
var spinRate = dynamicRate;
var currentTime = Date.now();
var delta = ( currentTime - previousTime ) / 1000;
previousTime = currentTime;
this.viewer.scene.camera.rotate(Cesium.Cartesian3.UNIT_Z, -spinRate * delta);
});
}