Creating a random particle generator with HTML5 Canvas API and Ionic Framework

December 5, 2017, 9:00 am Categories:

Categories

What we'll be building

By the end of this tutorial you should be able to run an ionic application within your browser that randomly generates particles within a HTML5 Canvas object and additionally provides buttons and a slider to perform the following:

  • Stop the current particle generator animation playing
  • Replay the particle generator animation
  • Change the number of particles available for use with the particle generator

Ionic framework project demonstrating a random particle animation with controls to stop the animation and change the number of generated particles

As the animation progresses the number of generated particles proliferates across the HTML5 Canvas area:

Ionic framework project demonstrating the progress of a random particle animation

Pretty fun huh? Pure abstract expressionism driven by code....

Jackson Pollock eat your heart out!

Now we know what we'll be developing let's get started...

First steps

Open your system terminal, navigate to where you store your digital projects and use the Ionic CLI to perform the following actions:

  • Generate a new Ionic Framework project named ionic-particles (that uses a blank template)
  • A service named particles
ionic start ionic-particles blank
cd ./ionic-particles
ionic g provider particles

With the project, and the particlesProvider servce, now generated the next step involves ensuring that the HttpClientModule is imported and configured within the application root module - which can be located at ionic-particles/src/app/app.module.ts - like so:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { ParticlesProvider } from '../providers/particles/particles';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    ParticlesProvider
  ]
})
export class AppModule {}

Now that we have everything in place for the project let's turn our attention towards coding the necessary logic for the random particle animation, starting with the ParticlesProvider service.

Defining the particle logic

The ParticlesProvider service will be used to provide the necessary properties and methods for generating an individual particle that will be subsequently rendered to the template's canvas area.

Modify the ionic-particles/src/providers/particles/particles.ts service so that it resembles the following:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

/*
  Generated class for the ParticlesProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class ParticlesProvider {



   
   constructor(public http: HttpClient) 
   {  }




   /**
    *
    * Creates a particle using the Canvas API
    *
    * @public
    * @method renderParticle
    * @param context     		{Object}   		The HTML Canvas context object
    * @param canvasWidth      	{Number}   		The Canvas object width value
    * @param canvasHeight     	{Number}   		The Canvas object height value
    * @return {None}
    */
   renderParticle(context			: any,
                  canvasWidth 	    : number,
                  canvasHeight 	    : number) 
   {      

      
      // Define particle properties
      let startingX     : number 	= Math.round(canvasWidth/2) + Math.random() * 225,
          startingY     : number 	= Math.round(canvasHeight/2) + Math.random() * 115 - 57,
          radius        : number    = this.generateRandomValue(7, 2),
          startAngle    : number    = 0,
          endAngle      : number    = 2 * Math.PI,
          clockwise     : boolean   = false,

          // Define the colour value for each generated particle using the HSLA CSS property          
          hue           : number    = this.generateRandomValue(50, 0),
          saturation    : any       = 100 + '%',
          lightness     : any       = 50 + '%',
          alpha         : number    = 1,
          colourFill    : any       = "hsla(" + hue + "," + saturation + "," + lightness + "," + alpha + ")";


      // Update the X * Y axis values for the particle
      startingX 					= startingX + Math.random() * 310 - 245;
      startingY 					= startingY + Math.random() * 410 - 225;


      // Generate the shape
      // - startingX 	(position on X axis)
      // - startingY 	(position on Y axis)
      // - radius    	(width of particle) 
      // - startAngle   (starts from)  
      // - endAngle   	(ends at)       
      // - clockwise    (direction - clockwise or not)   
      context.beginPath();
      context.arc(startingX,
                  startingY,
                  radius,
                  startAngle,
                  endAngle,
                  clockwise);
      context.fillStyle = colourFill;
      context.fill();
   }




   /**
    *
    * Generates a random numeric value
    *
    * @public
    * @method generateRandomValue
    * @param min     {Number}   		Minimum numeric value
    * @param max     {Number}   		Maximum numeric value
    * @return {Number}
    */
   generateRandomValue(min : number, 
                       max : number) : number
   {
      let maxVal : number     = max,
          minVal : number     = min,
          genVal : number;

      
      // Generate max value
      if(maxVal === 0)
      {
         maxVal = maxVal;
      }
      else {
         maxVal = 1;
      }


      // Generate min value
      if(minVal)
      {
         minVal = minVal;
      }
      else {
         minVal = 0;
      }
      
      genVal  = minVal + (maxVal - minVal) * Math.random();

      return genVal;
   }

}

Although, at first glance, the logic might seem a little complex what we have within the ParticlesProvider service is actually quite straightforward.

Our renderParticle method accepts the following parameters (the values for which will be supplied from within the HomePage component class - which we'll come to shortly):

  • The Canvas Context object
  • The Canvas object width
  • The Canvas object height

These are then used by this method to manage the generation of a circular shape whose position on the X and Y axis of the canvas area are randomly assigned with the following logic:

// The initial X and Y coordinates are generated with the following math
startingX     : number 	= Math.round(canvasWidth/2) + Math.random() * 225,
startingY     : number 	= Math.round(canvasHeight/2) + Math.random() * 115 - 57


// These are then updated a little later on in the method with further math
startingX 		= startingX + Math.random() * 310 - 245;
startingY 		= startingY + Math.random() * 410 - 225;

The generateRandomValue method, as the name implies, handles the generation of a random number from minimum and maximum values that are supplied as parameters.

The numeric value returned by this method is then used to define the radius for the generated particle.

With the logic for the ParticlesProvider service now in place the next step in developing our random particle generator involves the coding for the HomePage component.

Tying it together

Within the HomePage component we'll be crafting the necessary logic to handle the following scenarios:

  • Programmatically create a link to the template Canvas area using Angular's ViewChild and ElementRef
  • Set-up the properties for managing the template Canvas area
  • Define the number of particles to be generated
  • Generate a new particle using the renderParticle method of the ParticlesProvider service
  • Use the requestAnimationFrame method to generate the specified number of particles 60 times a second

Let's go ahead and accomplish these by opening the ionic-particles/src/pages/home/home.ts component class and coding the following:

import { Component, ElementRef, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ParticlesProvider } from '../../providers/particles/particles';


@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

   // Programmatically link to the HTML5 Canvas area 
   @ViewChild('canvasObj') canvasElement : ElementRef;


   /**
    *
    * @name _CANVAS
    * @type {object}
    * @private
    * @description 			Object reference that programmatically links into the DOM Canvas tag
    *
    */
   private _CANVAS 			: any;



   /**
    *
    * @name _CONTEXT
    * @type {Object}
    * @private
    * @description 			Object reference that determines the drawing context for the DOM Canvas tag
    *
    */
   private _CONTEXT 		: any;




   /**
    *
    * @name particleQuantity
    * @type {Number}
    * @public
    * @description 			Angular model for setting/retrieving particle count
    *
    */
   public particleQuantity  : number 	= 10;




   /**
    *
    * @name _NUM_PARTICLES
    * @type {Number}
    * @private
    * @description 			Number of particles to be generated on each animation frame request
    						derived from particleQuantity model value
    *
    */
   private _NUM_PARTICLES 	: number 				= 	this.particleQuantity;




   /**
    *
    * @name _ANIMATION
    * @type {Object}
    * @private
    * @description 			Handle for initiating/cancelling the requestAnimationFrame object 
    *
    */
   private _ANIMATION;




   /**
    *
    * @name isPlaying
    * @type {Boolean}
    * @public
    * @description 			Used to handle whether the animation state is active/inactive
    *
    */
   public isPlaying  				: boolean;




   constructor(public navCtrl 		: NavController,
               private _PARTICLE 	: ParticlesProvider) 
   {

   }



   /**
    *
    * Set up the Canvas on view loaded
    *
    * @public
    * @method ionViewDidLoad
    * @return {None}
    */
   ionViewDidLoad() : void
   {
      this.prepareCanvas();
   }




   /**
    *
    * Programmatically link to the HTML5 Canvas object and define 
    * the necessary width and height for the Canvas
    *
    * @public
    * @method prepareCanvas
    * @return {None}
    */
   prepareCanvas() : void
   {
      this._CANVAS 			= this.canvasElement.nativeElement;
      this._CANVAS.width  	= 500;
      this._CANVAS.height 	= 500;

      this.initialiseCanvas();
   }




   /**
    *
    * Initialise and render the HTML5 Canvas object
    *
    * @public
    * @method initialiseCanvas
    * @return {None}
    */
   initialiseCanvas() : void
   {
      if(this._CANVAS.getContext)
      {
         this.setupCanvas();
         this.renderToCanvas();
      }
   }




   /**
    *
    * Sets up the HTML5 Canvas 
    *
    * @public
    * @method setupCanvas
    * @return {None}
    */
   setupCanvas() : void
   {
      this._CONTEXT 			= this._CANVAS.getContext('2d');
      this._CONTEXT.fillStyle 	= "#3e3e3e";
      this._CONTEXT.fillRect(0, 0, 500, 500);
   }




   /**
    *
    * Clear and recreate the HTML5 Canvas object
    *
    * @public
    * @method clearCanvas
    * @return {None}
    */
   clearCanvas() : void
   {
      this._CONTEXT.clearRect(0, 0, this._CANVAS.width, this._CANVAS.height);
      this.setupCanvas();
   }




   /**
    *
    * Render the particle animation to the HTML5 Canvas object
    *
    * @public
    * @method renderToCanvas
    * @return {None}
    */
   renderToCanvas() : void
   {
      this.createParticleAnimation();
      this.isPlaying 		= 	true;
   }




   /**
    *
    * Create the particle animation using the requestAnimationFrame object
    *
    * @public
    * @method createParticleAnimation
    * @return {None}
    */
   createParticleAnimation() : void
   {
      // Generate a new particle via loop iteration
      for(var i = 0; 
              i < this._NUM_PARTICLES; 
              i++) 
      {
         this._PARTICLE.renderParticle(this._CONTEXT, 
         							   this._CANVAS.width,
         							   this._CANVAS.height);          							     
      }


      // Use the requestAnimationFrame method to generate new particles a minimum 
      // of 60x a second (or whatever the browser refresh rate is) BEFORE the next 
      // browser repaint
      this._ANIMATION = requestAnimationFrame(() =>
      {
         this.createParticleAnimation();
      });
   }




   /**
    *
    * Refreshes the HTML Canvas and reinitialises the particle animation using 
    * a numeric value supplied from the ion-range component selection
    *
    * @public
    * @method refreshCanvas
    * @param val     {Number}   		The number of particles to re-initiate the animation with
    * @return {None}
    */
   refreshCanvas(val : any) : void
   {
      this._NUM_PARTICLES 		= val.value;
      this.clearCanvas();
      this.renderToCanvas();
   }




   /**
    *
    * Stop the current animation from playing
    *
    * @public
    * @method stopAnimation
    * @return {None}
    */
   stopAnimation() : void
   {
      // Cancel the current animation
      cancelAnimationFrame(this._ANIMATION);
      this.isPlaying   = false;
   }




   /**
    *
    * Replay, from the start, the animation that had 
    * been previously stopped
    *
    * @public
    * @method replayAnimation
    * @return {None}
    */
   replayAnimation() : void
   {
      this.clearCanvas();
      this.renderToCanvas();
   }
  


   
}

With the particle animation rendering logic in place our next step in developing the HomePage component is to add the necessary HTML for the template.

Open the ionic-particles/src/pages/home/home.html component view and add the following markup:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Particle Generator
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  

   <!-- Column #1 - HTML5 Canvas area -->
   <section class="columns">

      <section class="column">

         <!-- 
            Declare a template reference variable of canvasObj 
            (This allows Ionic to plug into the Canvas area from 
             the component class) 
         -->   
         <canvas #canvasObj></canvas>      
      </section>    
   

   
      <!-- Column #2 - Particle management tools -->   
      <section class="column">
         <ion-list-header icon-start>            
            <ion-icon name="logo-buffer"></ion-icon>
            Manage animation
         </ion-list-header>


         <ion-list>

            <!-- Display Stop animation button IF particles are being generated -->   
            <button *ngIf="isPlaying"
              ion-button 
              color="primary" 
              (click)="stopAnimation()">Stop animation</button>


            <!-- Display Reset animation button IF particles have stopped being generated -->
            <button 
              *ngIf="!isPlaying"
              ion-button 
              color="danger" 
              (click)="replayAnimation()">Reset animation</button>
         </ion-list>
      
      


         <ion-list-header icon-start>            
            <ion-icon name="settings"></ion-icon>
            Manage particles
         </ion-list-header>


         <!-- Allow user to select number of particles for generation (between 1 to 10) -->
         <ion-list>
            <ion-label>Number of particles ( 1 - 10)</ion-label>
            <ion-range 
               step="1" 
	           snaps="true" 
	           min="1" 
	           max="10"
               [(ngModel)]="particleQuantity"
               (ionChange)="refreshCanvas($event, particleQuantity)">
               <ion-icon 
                  range-left 
                  small 
                  name="remove"></ion-icon>
               <ion-icon 
                  range-right 
                  name="add"></ion-icon>
            </ion-range>
         </ion-list>
     
      </section>
   </section>

</ion-content>

Finally we'll add the required style rules for rendering the template elements in a columnar format using a CSS Multi-column layout.

Open the ionic-particles/src/pages/home/home.scss style sheet and add the following rules:

page-home {
   .columns {
      column-count: 2;
      column-gap: 1em;


      .column {
      	 width: 100%;
      	 
      }
   }
}

With that final modification for the HomePage component we can now test our application within the browser using the following command from the Ionic CLI:

ionic serve

Which, if no errors have occured during the development phase (and don't you just love being greeted by unexpected TypeScript errors during the transpilation process?), will run the following application:

Ionic framework project demonstrating a random particle animation with controls to stop the animation and change the number of generated particles

If you're seeing this then congratulations - you've built your first random particle generator in Ionic!

In summary

Hopefully you've found the above tutorial useful and it's given you a taste for how HTML Canvas animations can be implemented within an Ionic project.

There's definitely further ways in which we could develop the functionality for this application such as, for example:

  • Allowing the user to generate different sized particles
  • Allowing the user to change the particle colour
  • Adding the ability to save or export the generated animation

These shouldn't be too difficult to accomplish (particularly if we integrated further Ionic components and models to transfer values from the template to the component class logic) but I'll leave these for you to experiment with!

If you've enjoyed what you've read and/or found this helpful please feel free to share your comments, thoughts and suggestions in the comments area below.

I explore animations, using Angular's built in animation modules, for the Ionic framework within my e-book featured below and if you're interested in learning more about further articles and e-books please sign up to my FREE mailing list.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top