I've been working on a project for fun where I want to have a couple of different eyes bouncing around a screen. I want the pupil of the eye to follow the user's mouse as it moves around the page. In order to accomplish this, I planned to add an eventlistener for mousemove and update state every time the position is updated.
However, I have found that the state update triggers a re-run of my useEffect for drawing the canvas. This causes the appearance that the ball moves faster when the user is moving their mouse. I was wondering if anyone has any advice on how to avoid this or how to better organize my code to get around this issue?
You can use the "Console" tab in the sandbox to see log statements that occur during unintended updates.
const draw = (ctx: CanvasRenderingContext2D, mousePos: {x: number, y: number}) => {
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
bubbles.forEach((bubble, index) => {
bubble.intersectWall();
bubble.move();
bubble.draw(mousePos);
});
};
const handleMouseMove = (e: MouseEvent) => {
setMousePos({ x: e.screenX, y: e.screenY });
};
// Initialize bubbles on page load
useEffect(() => {
const canvas = canvasRef.current as HTMLCanvasElement;
const context = canvas.getContext("2d") as CanvasRenderingContext2D;
window.addEventListener("mousemove", handleMouseMove);
const initBubbles = [];
for (let i = 0; i < 1; i++) {
initBubbles.push(
new Bubble(
randomNumber(0 + BUBBLE_RADIUS, window.innerWidth - BUBBLE_RADIUS),
randomNumber(0 + BUBBLE_RADIUS, window.innerHeight - BUBBLE_RADIUS),
randomNumber(-1, 4),
randomNumber(-4, 4),
context
)
);
}
setBubbles(initBubbles);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
useEffect(() => {
const canvas = canvasRef.current as HTMLCanvasElement;
const context = canvas.getContext("2d") as CanvasRenderingContext2D;
let animationFrameId: number;
const render = () => {
draw(context, mousePos);
animationFrameId = window.requestAnimationFrame(render);
};
render();
return () => {
window.cancelAnimationFrame(animationFrameId);
};
}, [draw]);
Codepen example: https://codepen.io/pietrofan12/pen/wvEBPPx
Thank you in advance and sorry for bad english
Related
I'm just starting JavaScript and still struggling a lot to reach my goals. I managed to make this little script work, which allows me to start a frame by frame animation related to the scroll of the page. My animation is a loop and I would like from now on simply that arrived at the bottom of the page the scroll restart automatically at the top of the page. So that the animation seems infinite.
I have seen some solutions with a clone of my content but it does not seem very suitable. Here is the JS as is.
Thanks in advance for your ideas!
const html = document.documentElement;
const canvas = document.getElementById("fuck");
const context = canvas.getContext("2d");
const frameCount = 100;
const currentFrame = index => (`https://www.jabberwock.archi/maquette/img/${index.toString().padStart(4, '0')}.jpg`)
const preloadImages = () => {
for (let i = 1; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
}
};
const img = new Image()
img.src = currentFrame(1);
canvas.width=1800;
canvas.height=1206;
img.onload=function(){
context.drawImage(img, 0, 0);
}
const updateImage = index => {
img.src = currentFrame(index);
context.drawImage(img, 0, 0);
}
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
preloadImages()
and the link of the page to see the animation :
https://jabberwock.archi/maquette/
Thank a lot !
I'm using useGuesture and to make some elements draggable in R3F.
When I start dragging (or even click the element) it moves to the center of the screen before it is then possible to drag it around.
How can I get it to drag from it's current position?
This is some code I've got from https://maxrohde.com/2019/10/19/creating-a-draggable-shape-with-react-three-fiber/ and tried to get it to work with objects that do not start in center of screen.
export default function Draggable(props) {
const [position, setPosition] = useState(props.position);
const { size, viewport } = useThree();
const aspect = size.width / viewport.width;
const bind: any = useGesture(
{
onDrag: ({ event, offset: [x, y] }) => {
const [, , z] = position;
setPosition([x / aspect, -y / aspect, z]);
},
onDragEnd: event => {
props.onMove(position);
},
onClick: props.onClick,
},
{ drag: { filterTaps: true } },
);
if (!props.children) return null;
return (
<mesh position={position} {...bind()}>
{props.children}
</mesh>
);
}
I know this is a bit old now but I was having the same issue. I've not used 'aspect' in mine as my use is a lot simpler, and used direction instead of offset - offset sends it flying off the page:
const bind = useDrag(({ down, direction: [x, y] }) => {
if (down) {
setPosition((prev) => [prev[0] + x, prev[1] - y, prev[2]]);
}
});
I've created a component that renders only an empty canvas element. After initialising in use effect, I draw a little square then start an interval to redraw the square at different locations. Initially I did this without using state and just stored the value in a local variable, the square redrew as expected and the component wasn't repeatedly unmounted and rendered.
I then tried putting the variable into state to see if the canvas would be cleared/deleted on unmount and recreated fresh and clean on render, but it isn't. The component is constantly getting unmounted and re-rendered but it always seems to render the contents of the old canvas.
Is this virtual DOM at work? I can't seem to find anything about this as most articles about React Render just say "The Render function inserts HTML into the DOM".
import React, { FunctionComponent, useEffect, useState } from 'react';
interface Babylon_props { }
export const Babylon: FunctionComponent<Babylon_props> = (props) =>
{
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D;
let interval: number;
//let x = 0;
let tick = () =>
{
ctx.fillRect(x, x, 20, 20);
setX(x + 10);
//x += 10;
}
const [x, setX] = useState(0);
useEffect(() =>
{
console.log("Creating Babylon component...");
canvas = document.getElementById("BblCanvas") as HTMLCanvasElement;
ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 10, 10);
interval = setInterval(tick, 1000);
return () =>
{
console.log("Destroying Babylon component...");
clearInterval(interval);
};
}, [x]);
return (
<canvas id="BblCanvas" width="800" height="600"></canvas>
)
}
I'm writing one of those simple games to learn JS and I'm learning HTML5 in the process so I need to draw things on canvas.
Here's the code:
let paddle = new Paddle(GAME_WIDTH,GAME_HEIGHT);
new InputHandler(paddle);
let lastTime = 0;
const ball = new Image();
ball.src = 'assets/ball.png';
function gameLoop(timeStamp){
let dt = timeStamp - lastTime;
lastTime = timeStamp;
ctx.clearRect(0,0,600,600);
paddle.update(dt);
paddle.draw(ctx);
ball.onload = () => {
ctx.drawImage(ball,20,20);
}
window.requestAnimationFrame(gameLoop);
}
gameLoop();
screenshot: no ball
before comment
now I comment out the clearRect():
after comment
hello ball.
There's also a paddle at the bottom of the canvas that doesn't seem to be affected by the clearRect() method. It works just fine. What am I missing here?
It doesn't make much sense to put the image's onload handler inside the game loop. This means the game has to begin running before the image's onload function is set, leading to a pretty confusing situation.
The correct sequence is to set the onload handlers, then the image sources, then await all of the image onloads firing before running the game loop. Setting the main loop to an onload directly is pretty easy when you only have one image, but for a game with multiple assets, this can get awkward quickly.
Here's a minimal example of how you might load many game assets using Promise.all. Very likely, you'll want to unpack the loaded images into more descriptive objects rather than an array, but this is a start.
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = 400;
canvas.height = 250;
const ctx = canvas.getContext("2d");
const assets = [
"http://placekitten.com/120/100",
"http://placekitten.com/120/120",
"http://placekitten.com/120/140",
];
const assetsLoaded = assets.map(url =>
new Promise(resolve => {
const img = new Image();
img.onerror = e => reject(`${url} failed to load`);
img.onload = e => resolve(img);
img.src = url;
})
);
Promise
.all(assetsLoaded)
.then(images => {
(function gameLoop() {
requestAnimationFrame(gameLoop);
ctx.clearRect(0, 0, canvas.width, canvas.height);
images.forEach((e, i) =>
ctx.drawImage(
e,
i * 120, // x
Math.sin(Date.now() * 0.005) * 20 + 40 // y
)
);
})();
})
.catch(err => console.error(err))
;
I have a function that draw a CANVAS line and make this get the same coordinates of a <div> by using offsetLeft and move its searching the same position of the <div>. It is working good.
drawCanvas() {
const c = document.getElementById("canvas");
const lineH = c.getContext("2d");
c.width = window.innerWidth;
c.height = window.innerHeight;
const positionCanvas = () => {
const divPosition = document.querySelector('.myDiv').offsetLeft
lineV.fillStyle = "grey";
lineV.fillRect(divPosition , 0, 2, window.innerHeight);
lineV.fill();
}
positionCanvas()
window.onresize = () => {
lineV.height = window.innerHeight;
positionCanvas()
}
The problem is I don't know how avoid the default CANVAS behavior that duplicates many times the line when I resize the windows. How do I solve it? Thank you
The answer is:
const positionCanvas = () => {
lineV.clearRect(0, 0, c.width, c.height); //<-- new line added in the code
//... rest of the code ommited