m.kaczmarek
m.kaczmarek

Reputation: 5

Self-breaking interval in React.js / router implementation of background particles animation

SOLVED - problem was setInterval method in containers rendered with '/about' and '/portfolio' paths - it was making animation to lag

https://github.com/kaczmarekm/portfolio - full app code

http://users.pja.edu.pl/~s17335/portfolio/ - application - watch how particle animation start to lag, slow down and stop after changing page by clicking nav button - it happens after couple seconds, and it's not completly dead - it changes color due to SetParticleColor() function, but with biig delay, just like interval were set for much longer time than 10ms

Problem is that I don't know why this happens, so I hope someone will find the source.

Code:

App.js

export default class App extends Component {

  constructor(){
    super();
    this.state = {
        particleInterval: null
    }
  }

  componentWillMount() {
    InitCanvas();
    Paint();
  }

  componentDidMount(){
    this.setState({
        particleInterval: setInterval(() => 
            requestAnimationFrame(Particles), 10)
    })
  }

  render() {
    return (
        <Router>
            <div>
                <NavigationContainer/>
                <Switch>
                   <Route path="/home" component={HomeContainer}/>
                   <Route path="/about" component={AboutContainer}/>
                   <Route path="/portfolio" component={PortfolioContainer}/>
                   <Route path="/contact" component={ContactContainer}/>
                   <Route path="*" component={EntryPageContainer}/>
                </Switch>
            </div>
        </Router>
    );
  }
}

InitCanvas.js

export function InitCanvas() {
  const rootWidth = window.innerWidth;
  const rootHeight = window.innerHeight;

  const canvas = document.createElement('canvas');
  canvas.id = 'canvas';
  canvas.width = rootWidth;
  canvas.height = rootHeight;
  canvas.style.zIndex = '-1';
  canvas.style.position = 'absolute';
  canvas.style.margin = '0';
  canvas.style.padding = '0';
  canvas.style.display = 'block';

  const root = document.getElementById('root');
  root.appendChild(canvas);
}

Particles.js

import { ParticleArray } from "../../Constants/ParticleArray";
import { SetParticleColor } from "./SetParticleColor";

const rootWidth = window.innerWidth;
const rootHeight = window.innerHeight;

 for (let i = 0; i < 500; i++) {
  let moveX = Math.random() - 0.5;
  let moveY = Math.random() - 0.5;   
  ParticleArray[i] = Math.random()*rootWidth, Math.random()*rootWidth,  
                     moveX,moveY];
 }

 export function Particles() {

   const canvas = document.getElementById('canvas');
   const ctx = canvas.getContext('2d');

   ctx.clearRect(0, 0, rootWidth, rootHeight);

   for (let i = 0; i < 500; i++) {
     let centerX = ParticleArray[i][0];
     let centerY = ParticleArray[i][1];
     let moveX = ParticleArray[i][2];
     let moveY = ParticleArray[i][3];
     let radius = 2;

     ctx.beginPath();
     ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
     ctx.fillStyle = SetParticleColor();
     ctx.fill();

     centerX += moveX;
     centerY += moveY;

     if(centerX >= rootWidth || centerX <= 0 || centerY >= rootHeight || 
        centerY <= 0){
         centerX = Math.random() * rootWidth;
         centerY = Math.random() * rootHeight;
     }

     ParticleArray[i] = [centerX, centerY, ParticleArray[i][2], 
     ParticleArray[i][3]];
 }
}

Upvotes: 0

Views: 204

Answers (1)

Dacre Denny
Dacre Denny

Reputation: 30360

Consider the following change to minimise the load that you're potentially putting on memory management - the key thing is to avoid the recreation of ~500 particle objects per animation iteration as shown below.

Also consider the reuse of color results, to offer some additional performance gains (depending on what computation is happening in SetParticleColor()):

/* Avoid calling for each particle seeing the color is the same
*/
var currentColor = SetParticleColor();

for (let i = 0; i < 500; i++) {

     /* Consider extracting the current particle, and working with it
        as shown below
     */
     let particle = ParticleArray[i]

     let centerX = particle[0];
     let centerY = particle[1];
     let moveX = particle[2];
     let moveY = particle[3];
     let radius = 2;

     ctx.beginPath();
     ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
     ctx.fillStyle = currentColor;
     ctx.fill();

     centerX += moveX;
     centerY += moveY;

     if(centerX >= rootWidth || centerX <= 0 || centerY >= rootHeight || 
        centerY <= 0){
         centerX = Math.random() * rootWidth;
         centerY = Math.random() * rootHeight;
     }

     /* Mutate the existing particle in your particles array rather
        than recreating a new replacement particle for every iteration
     */
     particle[0] = centerX
     particle[1] = centerY
     particle[2] = moveX
     particle[3] = moveY
 }

I would also suggest the use of setTimeout rather than setInterval for updating animation, along with the following revision to your animation loop. setInterval will re-run on the interval you set and will rerun even if the interval task has not not completed. This means there is potential for consequential tasks to overlap in execution, if prior interval tasks have not completed - in an animation like this, that will be problematic. Consider the following change to componentDidMount:

componentDidMount(){

    // Render frame itteration
    const renderFrame = () => {

      // Request a re-render
      requestAnimationFrame(Particles)

      // Sechedule the next renderFrame when the
      // browser is ready
      setTimeout(() => renderFrame(), 10)
    }

    renderFrame();
  }

Hope that helps (and nice portfolio website)! :-)

Upvotes: 0

Related Questions