Working on some javascript. I found a pretty good function that calculates the speed of the cursor. The problem is that i want to return the actual value, not a callback. How would you do that?
function makeVelocityCalculator(e_init, callback) {
var x = e_init.clientX,
y = e_init.clientY,
t = Date.now();
return function(e) {
var new_x = e.clientX,
new_y = e.clientY,
new_t = Date.now();
var x_dist = new_x - x,
y_dist = new_y - y,
interval = new_t - t;
// update values:
x = new_x;
y = new_y;
t = new_t;
var velocity = Math.sqrt(x_dist*x_dist+y_dist*y_dist)/interval;
callback(velocity);
};
}
well , then change that function to return velocity, instead of "callback(velocity)"
Js Fiddle sample
Or you can use it the way it was intended
makeVelocityCalculator(initialEvent, function(velocity) {
console.log("velocity is", velocity);
});
is pretty much same as
var velocity = makeVelocityCalculator(initialEvent);
console.log("velocity is", velocity);
function calculateVelocity(e_init) {
var x = e_init.clientX,
y = e_init.clientY,
t = Date.now();
return function(e) {
var new_x = e.clientX,
new_y = e.clientY,
new_t = Date.now();
var x_dist = new_x - x,
y_dist = new_y - y,
interval = new_t - t;
// update values:
x = new_x;
y = new_y;
t = new_t;
var velocity = Math.sqrt(x_dist*x_dist+y_dist*y_dist)/interval;
return velocity;
};
}
var velocity = calculateVelocity(e_init)(e);
// OR
var v_f = calculateVelocity(e_init);
// then some code ...
v_f(e);
Use an immediately invoking function (function(){})() if you want the call to calculateVelocity to return the velocity, otherwise return the function, which in turn returns the velocity.
Related
I'm trying to stop image(player, x, y) from going off the road and stop at leftWall and rightWall for a small p5 js project. No matter how I've tried implementing constrain(n, low, high) it will not work. So I've settled on using this instead:
if (x < leftWall) { x = leftWall;}
Can someone tell me how to correctly use constrain in this code?
const A_Key = 65;
const D_Key = 68;
const initialX = 350;
const initialY = 550;
let x = initialX;
let y = initialY;
let leftWall = 54;
let rightWall = 516;
function draw() {
background(bg);
image(player, x, y);
if (keyIsDown(A_Key)) {
x -= speed;
if (x < leftWall) {
x = leftWall;
}
}
if (keyIsDown(D_Key)) {
x += speed;
if (x > rightWall) {
x = rightWall;
}
}
}
You need to assign x to another variable each time you do a constrain
const A_Key = 65;
const D_Key = 68;
const initialX = 350;
const initialY = 550;
let x = initialX;
let y = initialY;
let leftWall = 54;
let rightWall = 516;
let img; // Declare variable 'img'.
let speed = 10;
function setup() {
createCanvas(2000, 720);
img = loadImage('s.jpg'); // Load the image
}
function draw() {
background(220);
let xc = constrain(x, leftWall, rightWall);
image(img, xc, y);
// Draw the walls.
stroke(150);
line(leftWall, 0, leftWall,height);
line(rightWall, 0, rightWall,height);
if (keyIsDown(A_Key)) {
x -= speed;
}
if (keyIsDown(D_Key)) {
x += speed;
}
}
This is for reference:
https://p5js.org/reference/#/p5/constrain
I'm trying to have a simple rectangle move from current location to the clicked location on the canvas. When I provide a constant speed, the rectangle appears and moves fine. But when I multiply it by deltatime the rectangle doesn't appear anymore. I'm using requestAnimationFrame as the draw loop.
canvas.js
window.addEventListener('DOMContentLoaded', (event) => {
let canvas = document.getElementById("gamecanvas");
let ctx = canvas.getContext('2d');
var oldframetime = 0;
var x = 0;
var y = 0;
var dstx = 0;
var dsty = 0;
var deltatime = 0;
var speed = 5;
function getmousepos(evt){
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener("mousedown",e=>{
let coord = getmousepos(e);
dstx = coord.x;
dsty = coord.y;
});
function movetowards(current,target,maxdistancedelta){
if(Math.abs(target - current)<=maxdistancedelta){
return target;
}
return current+Math.sign(target - current) * maxdistancedelta;
}
function tick(timestamp){
deltatime = (timestamp - oldframetime)/1000;
var step = deltatime*speed;
var newlocx = movetowards(x,dstx-50,5);
var newlocy = movetowards(y,dsty-50,5);
ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight);
ctx.fillStyle = 'green';
ctx.fillRect(newlocx,newlocy,100,100);
x = newlocx;
y = newlocy;
console.log(newlocx+":"+newlocy);
oldframetime = timestamp;
requestAnimationFrame(tick);
}
requestAnimationFrame(function(){
tick();
});
});
In that example, newlocx and newlocy both print NaN:NaN
But if I choose not to use step and give it a constant speed of like 5, then it works fine.
function tick(timestamp){
deltatime = (timestamp - oldframetime)/1000;
var step = deltatime*speed;
var newlocx = movetowards(x,dstx-50,5);
var newlocy = movetowards(y,dsty-50,5);
ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight);
ctx.fillStyle = 'green';
ctx.fillRect(newlocx,newlocy,100,100);
x = newlocx;
y = newlocy;
console.log(newlocx+":"+newlocy);
oldframetime = timestamp;
requestAnimationFrame(tick);
}
The print is also accurate now. Why does multiplyng step by deltatime prevent the rectangle from moving? Or even appearing?
Here's the HTML if anyone's interested.
index.html
<html>
<head>
<script src="canvas.js"></script>
</head>
<body>
<canvas id="gamecanvas" width="2000" height="1000"></canvas>
</body>
</html>
I have checked and found out the issue is in the first statement of tick function. When program starts then value of parameter is undefined. And thats why first statement of tick function result in NaN.
Just use some default value for parameter "timestamp". Its working now but speed is low and i hope you are aware about it.
Line to check:
function tick(timestamp=10)
function tick(timestamp=10) {
deltatime = (timestamp - oldframetime) / 1000;
var step = deltatime * speed;
var newlocx = movetowards(x, dstx - 50, 5);
var newlocy = movetowards(y, dsty - 50, 5);
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.fillStyle = 'green';
ctx.fillRect(newlocx, newlocy, 100, 100);
x = newlocx;
y = newlocy;
console.log(newlocx + ":" + newlocy);
oldframetime = timestamp;
requestAnimationFrame(tick);
}
You should be starting the animation with requestAnimationFrame rather than indirectly as you have done.
Replace
requestAnimationFrame(function(){
tick();
});
with
requestAnimationFrame(tick)
Then in the function tick you check if oldframetime has been set, if not set deltaTime to 0, as no time has passed, and thus starts the animation at the start. If you set the value of deltaTime to any other value you end up not rendering the animation from the start.
function tick(timestamp){
if (!oldframetime) {
deltaTime = 0;
} else {
deltatime = (timestamp - oldframetime)/1000;
}
// .. animation code
oldframetime = timestamp;
requestAnimationFrame(tick);
}
I am helping a friend with a website, and he is using the Ken Burns Effect with Javascript and Canvas from this site https://www.willmcgugan.com/blog/tech/post/ken-burns-effect-with-javascript-and-canvas/
for a slide-show. It works perfectly, but he would like to change the zoom effect to where all of the images zoom OUT, instead of alternating between zooming in and out.
After about a week of "scrambling" the code unsuccessfully, he posted a question about it on the site. The reply he received was (quote) "That's definitely possible, with a few tweaks of the code. Sorry, no time to give you guidance at the moment, but it shouldn't be all that difficult" (end quote).
I can't seem to figure it out either, so I'm hoping that someone here may be of help. Below is the code as posted on the willmcgugan.com website. Any help on how to change the zoom effect would be greatly appreciated.
(function($){
$.fn.kenburns = function(options) {
var $canvas = $(this);
var ctx = this[0].getContext('2d');
var start_time = null;
var width = $canvas.width();
var height = $canvas.height();
var image_paths = options.images;
var display_time = options.display_time || 7000;
var fade_time = Math.min(display_time / 2, options.fade_time || 1000);
var solid_time = display_time - (fade_time * 2);
var fade_ratio = fade_time - display_time
var frames_per_second = options.frames_per_second || 30;
var frame_time = (1 / frames_per_second) * 1000;
var zoom_level = 1 / (options.zoom || 2);
var clear_color = options.background_color || '#000000';
var images = [];
$(image_paths).each(function(i, image_path){
images.push({path:image_path,
initialized:false,
loaded:false});
});
function get_time() {
var d = new Date();
return d.getTime() - start_time;
}
function interpolate_point(x1, y1, x2, y2, i) {
// Finds a point between two other points
return {x: x1 + (x2 - x1) * i,
y: y1 + (y2 - y1) * i}
}
function interpolate_rect(r1, r2, i) {
// Blend one rect in to another
var p1 = interpolate_point(r1[0], r1[1], r2[0], r2[1], i);
var p2 = interpolate_point(r1[2], r1[3], r2[2], r2[3], i);
return [p1.x, p1.y, p2.x, p2.y];
}
function scale_rect(r, scale) {
// Scale a rect around its center
var w = r[2] - r[0];
var h = r[3] - r[1];
var cx = (r[2] + r[0]) / 2;
var cy = (r[3] + r[1]) / 2;
var scalew = w * scale;
var scaleh = h * scale;
return [cx - scalew/2,
cy - scaleh/2,
cx + scalew/2,
cy + scaleh/2];
}
function fit(src_w, src_h, dst_w, dst_h) {
// Finds the best-fit rect so that the destination can be covered
var src_a = src_w / src_h;
var dst_a = dst_w / dst_h;
var w = src_h * dst_a;
var h = src_h;
if (w > src_w)
{
var w = src_w;
var h = src_w / dst_a;
}
var x = (src_w - w) / 2;
var y = (src_h - h) / 2;
return [x, y, x+w, y+h];
}
function get_image_info(image_index, load_callback) {
// Gets information structure for a given index
// Also loads the image asynchronously, if required
var image_info = images[image_index];
if (!image_info.initialized) {
var image = new Image();
image_info.image = image;
image_info.loaded = false;
image.onload = function(){
image_info.loaded = true;
var iw = image.width;
var ih = image.height;
var r1 = fit(iw, ih, width, height);;
var r2 = scale_rect(r1, zoom_level);
var align_x = Math.floor(Math.random() * 3) - 1;
var align_y = Math.floor(Math.random() * 3) - 1;
align_x /= 2;
align_y /= 2;
var x = r2[0];
r2[0] += x * align_x;
r2[2] += x * align_x;
var y = r2[1];
r2[1] += y * align_y;
r2[3] += y * align_y;
if (image_index % 2) {
image_info.r1 = r1;
image_info.r2 = r2;
}
else {
image_info.r1 = r2;
image_info.r2 = r1;
}
if(load_callback) {
load_callback();
}
}
image_info.initialized = true;
image.src = image_info.path;
}
return image_info;
}
function render_image(image_index, anim, fade) {
// Renders a frame of the effect
if (anim > 1) {
return;
}
var image_info = get_image_info(image_index);
if (image_info.loaded) {
var r = interpolate_rect(image_info.r1, image_info.r2, anim);
var transparency = Math.min(1, fade);
if (transparency > 0) {
ctx.save();
ctx.globalAlpha = Math.min(1, transparency);
ctx.drawImage(image_info.image, r[0], r[1], r[2] - r[0], r[3] - r[1], 0, 0, width, height);
ctx.restore();
}
}
}
function clear() {
// Clear the canvas
ctx.save();
ctx.globalAlpha = 1;
ctx.fillStyle = clear_color;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore();
}
function update() {
// Render the next frame
var update_time = get_time();
var top_frame = Math.floor(update_time / (display_time - fade_time));
var frame_start_time = top_frame * (display_time - fade_time);
var time_passed = update_time - frame_start_time;
function wrap_index(i) {
return (i + images.length) % images.length;
}
if (time_passed < fade_time)
{
var bottom_frame = top_frame - 1;
var bottom_frame_start_time = frame_start_time - display_time + fade_time;
var bottom_time_passed = update_time - bottom_frame_start_time;
if (update_time < fade_time) {
clear();
} else {
render_image(wrap_index(bottom_frame), bottom_time_passed / display_time, 1);
}
}
render_image(wrap_index(top_frame), time_passed / display_time, time_passed / fade_time);
if (options.post_render_callback) {
options.post_render_callback($canvas, ctx);
}
// Pre-load the next image in the sequence, so it has loaded
// by the time we get to it
var preload_image = wrap_index(top_frame + 1);
get_image_info(preload_image);
}
// Pre-load the first two images then start a timer
get_image_info(0, function(){
get_image_info(1, function(){
start_time = get_time();
setInterval(update, frame_time);
})
});
};
})( jQuery );
If you want the simplest solution, I forked and modified your Codepen here:
http://codepen.io/jjwilly16/pen/NAovkp?editors=1010
I just removed a conditional that controls whether the zoom is moving out or in.
if (image_index % 2) {
image_info.r1 = r1;
image_info.r2 = r2;
}
else {
image_info.r1 = r2;
image_info.r2 = r1;
}
Changed to:
image_info.r1 = r2;
image_info.r2 = r1;
Now it only zooms out :)
I'm facing a problem right now, the mouse event doesn't seem to work. I have a button sprite and a player sprite , and I want that when I click on the button , the player should move butit doesn't, well here's mycode:
var canvas = document.getElementById("canvas_1");
ctx = canvas.getContext("2d");
canvas.addEventListener('mousedown', doMouseDown, true);
var X = 100;
var Y = 0;
var x = 0;
var y = 0;
var player = new Image();
var butt = new Image();
player.src = 'images/player.png';
butt.src = 'images/right.png';
player.onload = function () {
ctx.drawImage(player, x, 0);
}
butt.onload = function () {
ctx.drawImage(butt, X, Y);
}
update = function () {
ctx.clearRect(0, 0);
ctx.drawImage(player, x, y);
}
setInterval(update, 1000 / fps.getFPS());
var fps = {
startTime: 0,
frameNumber: 0,
getFPS: function () {
this.frameNumber++;
var d = new Date().getTime(),
currentTime = (d - this.startTime) / 1000,
result = Math.floor((this.frameNumber / currentTime));
if (currentTime > 1) {
this.startTime = new Date().getTime();
this.frameNumber = 0;
}
return result;
}
};
function doMouseDown(event) {
var mousePos = getMousePos(canvas, event);
if ((mousePos.x >= X && mousePos.x <= X + butt.width) && (mousePos.y >= Y && mousePos.y <= Y + butt.height)) {
x += 4;
}
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
what you are doing is like you clicked, doMouseDown function called and end.
and you needed to call this function many times to get animation.so,
you can try like
if clicked var clicked=true
and put this below code in loop
if (clicked){
x+=4
}
I am having issues where I am trying to animate an element. I have a recursive function animate that contains setTimeout to delay the animation. The issue is that the next line after the recursive method is called gets run before the animation is called. For example with this code:
this.slideTo = function(x,y) {
var originalStyle = this.element.style.cssText;
var endX = x;
var endY = y;
var pos = this.getPosition();
var startX = pos.x;
var startY = pos.y;
var distX = x - pos.x;
var distY = y - pos.y;
var totalDistance = Math.sqrt((distX*distX) + (distY*distY));
var count = 0;
var done = false;
animate(this.element);
function animate(element) {
count += 5;
var curPos = _(element).getPosition();
var curDistX = endX - curPos.x;
var curDistY = endY - curPos.y;
var curDistance = Math.sqrt((curDistX*curDistX) + (curDistY*curDistY));
var percentDone = count/totalDistance;
var moveToX = Math.round((percentDone*distX) + startX);
var moveToY = Math.round((percentDone*distY) + startY);
_(element).moveTo(moveToX,moveToY);
if(percentDone <= 1) {
setTimeout(function(){animate(element);},1);
} else {
done = true;
}
}
console.log(done);
this.element.style.cssText = originalStyle;
}
The console displays false because it is run before the animation is done. How can I solve this? btw: this is being called on a mouse event. Could that have anything to do with it?
It's not working because JS, and particularly setTimeout, is asynchronous -- that is, it doesn't block (sleep/wait/etc). You're basically setting the timeout and then continuing on with the rest of the code. JS calls your function when the time has elapsed and it's not doing anything else.
Most of the time, you can work around this by passing in a callback that you call when the animation completes (instead of or at the same time as setting done, would be the straightforward way).
Try next little changed code:
this.slideTo = function(x,y) {
var originalStyle = this.element.style.cssText;
var endX = x;
var endY = y;
var pos = this.getPosition();
var startX = pos.x;
var startY = pos.y;
var distX = x - pos.x;
var distY = y - pos.y;
var totalDistance = Math.sqrt((distX*distX) + (distY*distY));
var count = 0;
var done = false;
function restoreState(element) {
element.style.cssText = originalStyle;
/// ....
}
function animate(element) {
count += 5;
var curPos = _(element).getPosition();
var curDistX = endX - curPos.x;
var curDistY = endY - curPos.y;
var curDistance = Math.sqrt((curDistX*curDistX) + (curDistY*curDistY));
var percentDone = count/totalDistance;
var moveToX = Math.round((percentDone*distX) + startX);
var moveToY = Math.round((percentDone*distY) + startY);
_(element).moveTo(moveToX,moveToY);
if(percentDone <= 1) {
setTimeout(function(){animate(element);},1);
} else {
done = true;
restoreState(element);
}
}
animate(this.element);
}
In short, setTimeout does not stop execution of your code -- it just runs the function in question after the given timeout:
var a = 'now';
function go(){ a = 'later'; };
setTimeout(go, 50);
console.log(a); // 'now'
setTimeout(function(){ console.log(a); },51); // 'later'
You want to use a callback pattern:
function animate(element, callback){
// do some stuff
if(stuffDone){
callback();
}
}
animate(element, function(){console.log(whatever); });