2
\$\begingroup\$

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 refs. 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.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jun 1, 2019 at 15:03
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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.

answered Jun 1, 2019 at 16:32
\$\endgroup\$
3
  • \$\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\$ Commented 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 the render if it's set will work... \$\endgroup\$ Commented 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 reacts forceUpdate. Should use requestAnimationFrame in componentDidMount and stop it in componentWillUnmount() \$\endgroup\$ Commented Jun 2, 2019 at 3:47

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.