document.createElement does not work as expected in componentDidMount in React? - javascript

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>

Related

Clear canvas by JavaScript

I am using this jQuery plugin to make free drawing over a canvas.
I want to clear the canvas for redrawing but after I do and when I click inside canvas for redrawing the old drawing that I cleared appears again
$('#canvasFirst').sketch();
$('button').on("click", (evt) => {
var canvas = document.getElementById('canvasFirst');
let context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
});
canvas { border: 1px solid; vertical-align: top }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mobomo/sketch.js#master/lib/sketch.min.js"></script>
<p>Draw on the below canvas, then press "clear" and try to draw again.</p>
<canvas id="canvasFirst">
</canvas>
<button>clear</button>
This plugin stores all the drawing commands in an actions array, and at each redraw it will go through that whole list and draw the full thing again. (Which allows to avoid having holes in your stroke).
The docs are very hard to grasp, but you can set this options's content through the .sketch("options", value) method.
As hinted in this issue, setting it to an empty Array will thus remove all the previous commands. All you have to do then is to redraw the whole scene, now empty:
const $canvas = $('#canvasFirst');
$canvas.sketch();
$('button').on("click", (evt) => {
$canvas.sketch("actions", []);
$canvas.sketch("redraw");
});
canvas { border: 1px solid; vertical-align: top }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mobomo/sketch.js#master/lib/sketch.min.js"></script>
<p>Draw on the below canvas, then press "clear" and try to draw again.</p>
<canvas id="canvasFirst">
</canvas>
<button>clear</button>
You can try to save the state of the canvas as a blob. For example, as image/png.
There are two bad things here:
A small inconvenience. Methods for converting a blob to an image and back are asynchronous. Promises have to be used.
Speed. I strongly doubt that this solution is suitable for tasks that require speed - games, videos, etc.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const saveCanvasState = (canvas) => {
const ctx = canvas.getContext('2d');
ctx.save(); // save state
let blob;
return new Promise((r) => {
canvas.toBlob( // toBlob is async method
(b) => {
blob = b;
r(() =>
new Promise((r) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
const img = new Image();
img.addEventListener('load', () => {
URL.revokeObjectURL(img.src);
ctx.drawImage(img, 0, 0);
r();
});
img.src = URL.createObjectURL(blob);
}));
},
'image/png',
1
);
});
};
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 100, 100);
saveCanvasState(canvas).then((restore) => {
ctx.fillStyle = 'black';
ctx.fillRect(150, 40, 100, 100);
ctx.fillRect(100, 40, 100, 100);
restore().then(() => {
console.log('restored, can draw');
});
});
<canvas id="canvas"></canvas>

Canvas rendering from component is not working on Svelte

I'm trying to render canvas and draw rectangle on it using svelte.js, but it does not work if I write rendering code on the component side.
Here is the reproduction REPL.
It works if I move entire code in Canvas.svelte to app.svelte.
App.svelte:
<script>
import Canvas from './Canvas.svelte';
</script>
<Canvas/>
Canvas.svelte:
<script>
import { onMount } from 'svelte';
let canvasEl;
const canvas = {
width: 1000,
height: 1000
};
const drawRect = (context) => {
context.beginPath();
context.fillStyle = '#000000';
context.rect(0, 0, 40, 10);
context.fill();
};
onMount(() => {
console.log('Onmount');
const context = canvasEl.getContext('2d');
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
drawRect(context);
});
</script>
<canvas bind:this={canvasEl} width={canvas.width} height={canvas.height} />
Does anyone know the solution?
Thank you,
Didn't find a reason of error, but found the source:
onMount(() => {
//...
// this 2 lines cause silent error
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
//...
});
Remove them and you will see the canvas and rect

Responsive full width canvas in sveltejs

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

How to get a hold of the html element defined through React JSX?

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

How to Stop multiply a canvas line when the windows is resizing?

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

Categories

Resources