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>
)
}
Related
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
I am trying to create a canvas element on the fly, in order to execute .toDataURL() method after I've done some drawing. But the document.createElement does not have the expected behavior.
When I try this:
componentDidMount() {
var canvas = document.createElement('canvas');
canvas.height = 100;
canvas.width = 200;
console.log(canvas);
}
I get in the console:
<canvas height="0" width="0"></canvas>
But using componentDidUpdate method works fine.
componentDidUpdate() {
var canvas = document.createElement('canvas');
canvas.height = 100;
canvas.width = 200;
console.log(canvas);
}
<canvas height="100" width="200"></canvas>
What exactly is happening here ?
Please any answers concerning the right way in React using either ref or applying <canvas /> element to DOM won't help me, as I am particularly trying to understand why there is this difference between componentDidMount and componentDidUpdate in React.
Ref is the proper way to target an element in React, and The proper way to creating an element in React returns them using jsx.
This example shows you how to make canvas:
const App = () => {
const drawCanvas = (ref) => {
if (ref) {
ref.width = 200;
ref.height = 100;
const ctx = ref.getContext("2d");
ctx.clearRect(0, 0, 200, 100);
ctx.fillStyle = "grey";
ctx.fillRect(0, 0, 200, 100);
}
};
return <canvas ref={drawCanvas}/>
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I'm quite new to svelte and I'm trying to get a canvas to render on the full screen using svelte. Sounds quite easy to do, but I can't get it to work properly. I'm binding a width and height variable to the clientWidth/clientHeight of the parent and using these variables to set the dimensions of the canvas. The issue now is that when onMount is called, the width and height variables are set but they are not applied to the canvas element yet. This means when the canvas renders for the first time, it still has the initial dimensions and not the ones of the parent. Only when I render it a second time it has the proper dimensions. How can get the canvas to have the right dimensions on the first render or render the canvas again when it has the proper dimensions?
Here you can find a "working" version.
<script>
import { onMount } from "svelte";
let canvas;
let ctx;
let width = 1007;
let height = 1140;
const draw = () => {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.moveTo(width/2 - 50, height/2);
ctx.arc(width/2, height/2, 50, 0, 2 * Math.PI);
ctx.fill();
}
onMount(() => {
ctx = canvas.getContext("2d");
draw();
setTimeout(draw, 5000);
});
</script>
<style>
.container {
width: 100%;
height: 100%;
}
</style>
<div
class="container"
bind:clientWidth={width}
bind:clientHeight={height}>
<canvas bind:this={canvas} {width} {height} />
</div>
You can solve this by waiting for the next 'tick'
import { onMount, tick } from 'svelte';
onMount(async () => {
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
await tick()
draw();
});
What await tick() does is to effectively pause execution until all current changes have been applied to the dom
tick in the docs
I am implementing Draw function, to draw a line in canvas. The Draw function is activated on the first double click and on mouse move the line is being drawn and on the second double click, the draw function is deactivated.
When I double click the third time the previously drawn line is disappearing. My requirement is I want to retain all of the lines. How do I do that?
Below is my draw function:
handleMouseMove(event){
if(this.state.isDouble)
{
this.Draw(event)
}
else{
}
}
Draw(event){
x2=event.offsetX
y2=event.offsetY
const canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.lineWidth = 3;
//Deleting The State
ctx.canvas.width = ctx.canvas.width;
console.log("First" + this.state.previousPointX,this.state.previousPointY)
console.log("Second" + x2,y2)
xtemp=x2
ytemp=this.state.previousPointY
console.log("Temp" + xtemp,ytemp)
ctx.beginPath()
ctx.moveTo(xtemp,ytemp);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.moveTo(xtemp,ytemp);
ctx.lineTo(this.state.previousPointX,this.state.previousPointY);
ctx.strokeStyle = "black";
ctx.stroke();
}
I have read here that you can save the canvas by converting it to json
for Redux store, I have tested using state for my Fabric canvas instance, its a 3rd party library that is a wrapper on a HTML canvas DOM element.
import React, { useEffect, useState } from 'react';
import './WallToDecorate.css';
import { fabric } from 'fabric';
const WallToDecorate = ({templateList=[]}) => {
const [wallCanvas, setCanvas] = useState();
useEffect(() => {
const canvas = new fabric.Canvas('fabric-canvas-to-decorate', {
width: rect.width,
height: rect.height
});
canvas.setBackgroundImage(
originalWallImage.src,
canvas.renderAll.bind(canvas),
{
backgroundImageOpacity: 1,
backgroundImageStretch: false,
top: top,
left: left,
}
);
setCanvas(canvas);
}, []);
const addImageToCanvas = (event) => {
fabric.util.loadImage(
event.target.src,
function (img) {
var fab_image = new fabric.Image(img, {
});
wallCanvas.add(fab_image);
wallCanvas.renderAll();
},
{ left: 100, top: 100, angle: 0, opacity: 1 }
);
};
return (
<>
<div id="select-image-dimensions" className="placeholder canvas-size">
<canvas id="fabric-canvas-to-decorate" />
</div>
</>
);
}
export default WallToDecorate;
In my case the wallCanvas.renderAll() refreshes the canvas element.
Consider this component,
import React, { Component } from 'react';
class Canvas extends Component {
componentDidMount() {
let canvas = this.refs.canvas;
const ctx = canvas.getContext('2d');
ctx.fillRect(0,0, 100, 100);
}
render() {
let width, height;
width = height = window.innerHeight - 50;
let canvas = <canvas ref="canvas" width={width} height={height} />;
// canvas.getContext('2d') does not work here
// this.refs is also empty: {}
return canvas;
}
}
export default Canvas;
Observe how we can get a hold of the actual HTML element in componentDidMount using this.refs and thus call it's method getContext on it. I was under the impression that if we assigned the JSX to a variable like here,
let canvas = <canvas ref="canvas" width={width} height={height} />;
we got the actual HTML element returned by the JSX. That does not seem to the case because the variable canvas here is a React component and not the HTML element thus I can not just call getContext on it. this.refs right after the definition is also empty.
I was wondering if there is a way to get the actual HTML element once defined through JSX and be able to call it's functions on it? Or, is that a bad idea? I'd like to do something like this in the render of the above component,
render() {
let width, height;
width = height = window.innerHeight - 50;
let canvas = <canvas ref="canvas" width={width} height={height} />;
const ctx = canvas.getContext('2d');
ctx.fillRect(0,0, 100, 100);
return canvas;
}
so after rendering only you will be able to getContext, so basically the flow is constructor, componentWillMount(Unsafe method ), render and last componentDidMount. you can get the context on after render by calling componentDidMount and you can update it after once render
You can check the below code
class Canvas extends Component {
componentDidMount() {
let canvas = this.refs.canvas;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 100, 100);
// after render
console.log(canvas.getContext("2d"));
}
render() {
let width, height;
width = height = window.innerHeight - 50;
let canvas = <canvas ref="canvas" width={width} height={height} />;
return canvas;
}
}
export default Canvas;
Codepen