I wrote some JavaScript to generate some graphics on a canvas, on a regular HTML page.
I now want the same code to run in a component that is part of a React app. I've done this:
componentDidMount() {
const canvas = this.refs.firstCanvas
const ctx = canvas.getContext('2d')
const bgCanvas = this.refs.firstbgCanvas
const bgCtx = bgCanvas.getContext('2d')
function generateStarfield() {
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height)
for (let i = 0; i < 2000; i++) {
bgCtx.beginPath()
const x = Math.random()*bgCanvas.width
const y = Math.random()*bgCanvas.height
bgCtx.arc(x, y, 0.35, 0, 2*Math.PI, 'anticlockwise')
bgCtx.closePath()
bgCtx.fillStyle = 'white'
bgCtx.fill()
}
}
generateStarfield()
const origin_x = 1000
const origin_y = 1000
const scale = 1000
class planet {
constructor(orbital_velocity, offset_theta, orbital_radius, radius, colour) {
this.orbital_velocity = orbital_velocity
this.orbital_radius = orbital_radius
this.colour = colour
this.radius = radius
this.offset_theta = offset_theta
this.draw()
}
draw() {
const theta = this.offset_theta + (t*this.orbital_velocity)
const x = origin_x + (this.orbital_radius*Math.cos(theta))
const y = origin_y + (this.orbital_radius*Math.sin(theta))
ctx.beginPath()
ctx.arc(x, y, this.radius, 0, 2*Math.PI, 'anticlockwise')
ctx.closePath()
ctx.fillStyle = this.colour
ctx.fill()
}
}
let t = 0
const a = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.1, 10, 'white')
const b = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.2, 16, 'white')
const c = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.3, 18, 'white')
const d = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.4, 14, 'white')
const e = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.5, 12, 'white')
const f = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.6, 28, 'white')
const g = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.7, 22, 'white')
const h = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.8, 20, 'white')
setInterval(function() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.beginPath()
a.draw()
b.draw()
c.draw()
d.draw()
e.draw()
f.draw()
g.draw()
h.draw()
t += 0.05
}, 40)
}
Now, this works (!), but I've just lifted it directly from the original JavaScript, and modified the document.getElementByID
calls to use ref
s. Is it bad practice to stick all of this stuff into componentDidMount
?
Note - I'm aware that the code itself could be tidied up, I'm mainly asking about putting all of this (especially the definitions for planet
and generateStarField
) into componentDidMount
.
1 Answer 1
You can do this, if it works... but it's not really using the React architecture or patterns.
Most of this code that is drawing should be in the
render
method.You'll want to move some of the state (
canvas
,ctx
,bgCanvas
,bgCtx
) into the class's state, or just as instance variables.You can probably do most of the creation in the constructor, although doing it in
componentDidMount
will work.You can integrate the animation with React,
setInterval(() => this.forceUpdate(() => this.t += 0.05))
. This makes sense in componentDidMount. You'll also want to turn this off in the unmount method.
EDIT/ADDED LATER
...maybe you just want to leave it the way it is, although it's quite non-React... it's working fine, though, and there aren't really problems...
To make it "React" you're probably going to have to re-write it completely-- because just moving pieces around like I suggested may make you end up fighting the framework more than leveraging. Looking more, you'd probably make separate components for the different canvases and perhaps pass in t
as a property, so there's a container component that functions as the "clock". Doing something like that would allow the regular React rendering mechanisms work.
-
\$\begingroup\$ I'm a bit confused about the order of this - the canvas, ctx, bgCanvas, and bgCtx can't be defined until after render has completed (as render creates the <canvas /> elements), so how can any drawing be done until the componentDidMount? \$\endgroup\$Alex– Alex2019年06月01日 17:10:18 +00:00Commented Jun 1, 2019 at 17:10
-
\$\begingroup\$ Yeah, there's a bit of a start-up ordering issue, but I think having
React.createRef()
in the constructor and then you can just check in therender
if it's set will work... \$\endgroup\$ndp– ndp2019年06月01日 21:04:15 +00:00Commented Jun 1, 2019 at 21:04 -
\$\begingroup\$ No you should not render animated content via
setInterval
and worse as canvas context is completely independent of the DOM you should never animate its content via reactsforceUpdate
. Should userequestAnimationFrame
incomponentDidMount
and stop it incomponentWillUnmount()
\$\endgroup\$Blindman67– Blindman672019年06月02日 03:47:59 +00:00Commented Jun 2, 2019 at 3:47
Explore related questions
See similar questions with these tags.