15

I have an Angular app that I want to use particles.js in however I have no clue how to add it and get it working.

I've added it to the .angular-cli.json

  "scripts": [
    "../node_modules/particles.js/particles.js"
  ],

And I've imported it into my component

import * as  particlesJS from 'particles.js';

And attempted to initialize it using

  particlesJS.load('particles-js', 'assets/particles.json', function() {
    console.log('callback - particles.js config loaded');
  });

Has anyone got this working?

2
  • Here is a description on how to utilize external javascript libraries in Angular2+ link Commented May 31, 2017 at 20:13
  • This works OK with the likes of JQuery but I cant get it to work with Particles.js Commented Jun 1, 2017 at 8:19

5 Answers 5

15

Using the original package, without the https://github.com/audrenbdb/angular-particlesjs you can go as follows:

Install it using npm i particles.js

app.component.html

<div id="particles-js"></div>

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ParticlesConfig } from './particles-config';

declare let particlesJS: any; // Required to be properly interpreted by TypeScript.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  public ngOnInit(): void {
    this.invokeParticles();
  }

  public invokeParticles(): void {
    particlesJS('particles-js', ParticlesConfig, function() {});
  }
}

particles-config.ts

    export const ParticlesConfig = {
      particles: {
        number: {
          value: 70,
          density: {
            enable: true,
            value_area: 1400
          }
        },
        color: {
          value: '#283593'
        },
        shape: {
          type: 'polygon',
          stroke: {
            width: 1,
            color: '#283593'
          },
          polygon: {
            nb_sides: 6
          }
        },
        opacity: {
          value: 1,
          random: true,
          anim: {
            enable: true,
            speed: 0.8,
            opacity_min: 0.25,
            sync: true
          }
        },
        size: {
          value: 2,
          random: true,
          anim: {
            enable: true,
            speed: 10,
            size_min: 1.25,
            sync: true
          }
        },
        line_linked: {
          enable: true,
          distance: 150,
          color: '#283593',
          opacity: 1,
          width: 1
        },
        move: {
          enable: true,
          speed: 8,
          direction: 'none',
          random: true,
          straight: false,
          out_mode: 'out',
          bounce: true,
          attract: {
            enable: true,
            rotateX: 2000,
            rotateY: 2000
          }
        }
      },
      interactivity: {
        detect_on: 'canvas',
        events: {
          onhover: {
            enable: true,
            mode: 'grab'
          },
          onclick: {
            enable: true,
            mode: 'repulse'
          },
          resize: true
        },
        modes: {
          grab: {
            distance: 200,
            line_linked: {
              opacity: 3
            }
          },
          repulse: {
            distance: 250,
            duration: 2
          }
        }
      },
      retina_detect: true
   };

app.component.scss (optional, to show it as full height)

#particles-js {
  height: 100vh;
}

angular.json

"scripts": ["node_modules/particles.js/particles.js"]
Sign up to request clarification or add additional context in comments.

6 Comments

This is my favourite answer, I don't like the directives, they seem messy. This is much better
what should I change for different animation
@Santosh just play with it :) Changing value, density, shape, repulse would be my first guess :)
thanks @DanielDanielecki I also found some config for different animation using particle js. https://vincentgarreau.com/particles.js/#nasa
This was perfect, and simple. Also, not having to have script references and just pulling in the javascript during build is great. Thank you.
|
12

Here is how to do that:

  1. Just import the particles.js in your index.html (cdn or local)

    <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
    
  2. Put in the div anchor into your component template (you could also put it to index.html or somewhere else)

    <div id="particles-js"></div>
    
  3. Make the package visible by adding a simple type definition (in your component or in the typings.d.ts)

    declare var particlesJS: any;
    
  4. Initialize it in ngOnInit (or somewhere else)

    particlesJS.load('particles-js', 'particles.json', null);
    

I have made a little plunker example: http://plnkr.co/edit/GLRvYgNPJue4KqdMuAJB?p=preview

3 Comments

This worked a treat thanks. Followed it step by step and couldn't figure out why it wasn't working, took a while for it to click that the particles where white on a white background in my app
Why is declaring it important ?
@frunkad If you don&#39;t have typings then you need to declare. Otherwise the transpiler claims about unknown things.
9

Update 22/11/2019

I have ported an angular version of particles.js into a directive. Inside the div you want to cover with particles, add a canvas element with repulse-particles. This is a very trimmed version of the library, but there are still quite a few options.

Performance wise, this directive is an upgrade over the original library with the use of a quadtree and several big optimizations. You can now have fun with 1k particles ~

Another tweak is the support of touch mobile device.

See example in the repo or in the stackblitz demo below.

Edit to your likings :)

Repo & stackblitz editor

https://github.com/audrenbdb/angular-particlesjs

https://stackblitz.com/edit/angular-dt2cjg

Demo

https://audrenbdb.github.io/particles/angular/index.html

Full directive code

import { Directive, ElementRef, Input, OnDestroy, HostListener, OnInit } from "@angular/core";

/* 
  Variables set outside of directive scope
  To improve performances.
*/
const TAU: number = Math.PI * 2;
const QUADTREE_CAPACITY: number = 4;
let linkBatches: number = 10;
let mouse: {x: number,y: number} = {x: 0, y: 0};


/*
  Variables to be initiated
*/
let linkDistance: number;
let linkDistance2: number;
let repulseDistance: number;
let particleSpeed: number;
let particleSize: number;
let bounce: boolean;
let quadTree: QuadTree;
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D;



@Directive({
  selector: "[repulse-particles]"
})
export class ParticlesDirective implements OnDestroy, OnInit {

  @Input() number: number = 80;
  @Input() speed: number = 6;
  @Input() linkWidth: number = .5;
  @Input() linkDistance: number = 140;
  @Input() size: number = 3;
  @Input() repulseDistance: number = 140;
  @Input() particleHex: string = "#FFF";
  @Input() linkHex: string = "#FFF";
  @Input() bounce: boolean = true;
  @Input() densityArea: number = 800;


  particlesNumber: number;
  particlesList: Particle[] = [];
  links: Link[][] = [];
  linkBatchAlphas: number[] = [];
  linkPool: Link[] = [];
  candidates: Particle[] = [];
  boundary: Bounds;

  animationFrame;

  constructor(
    public el: ElementRef,
  ) {
    canvas = this.el.nativeElement;
    canvas.style.height = "100%";
    canvas.style.width = "100%";
    ctx = canvas.getContext("2d");
    for (var i = 1/(linkBatches + 1); i < 1; i += 1/(linkBatches + 1)) {
      this.links.push([]);
      this.linkBatchAlphas.push(i);
    }
    this.setCanvasSize();
    this.initVariables();
  }

  ngOnInit() {
    this.animate();
  }

  @HostListener("window:resize") onResize() {
    this.setCanvasSize();
  }

  @HostListener("mouseleave") onMouseLeave() {
    this.stopMouse()
  }

  @HostListener("touchend") onTouchEnd() {
    this.stopMouse()
  }

  @HostListener("mousemove", ["$event"]) onMouseMove(e) {
    this.setMousePos(e.offsetX, e.offsetY);
  }

  @HostListener("touchmove", ["$event"]) onTouchMove(e) {
    this.setMousePos(e.touches[0].clientX, e.touches[0].clientY);
  }

  @HostListener("change") ngOnChanges() {
    this.initVariables();
    this.resetParticles();
  }

  setMousePos(x, y) {
    mouse.x = x;
    mouse.y = y;
  }

  stopMouse() {
    mouse.x = null;
  }

  initVariables() {
    linkDistance = this.linkDistance;
    linkDistance2 = (0.7 * linkDistance) ** 2;
    repulseDistance = this.repulseDistance;
    particleSpeed = this.speed;
    particleSize = this.size;
    bounce = this.bounce;
    if (this.densityArea) this.scaleDensity();
  }


  animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    this.updateParticles();
    this.updateLinks();
    this.animationFrame = requestAnimationFrame(this.animate.bind(this));
  }

  updateParticles() {
    quadTree.close();
    ctx.fillStyle = this.particleHex;
    ctx.beginPath();
    for (const p of this.particlesList) p.update(ctx, true);
    ctx.fill();
  }

  updateLinks() {
    let i: number;
    let link: Link;
    let alphaIdx = 0;

    for (const p1 of this.particlesList) {
      p1.explored = true;
      const count = quadTree.query(p1, 0, this.candidates);
      for (i = 0; i < count; i++) {
        const p2 = this.candidates[i];
        if (!p2.explored) {
          link = this.linkPool.length ? this.linkPool.pop() : new Link();
          link.init(p1, p2);
          this.links[link.batchId].push(link);
        }
      }
    }

    ctx.lineWidth = this.linkWidth;
    ctx.strokeStyle = this.linkHex;
    for (const l of this.links) {
      ctx.globalAlpha = this.linkBatchAlphas[alphaIdx++];
      ctx.beginPath();
      while (l.length) this.linkPool.push(l.pop().addPath(ctx));
      ctx.stroke();
    }
    ctx.globalAlpha = 1;
  }

  resetParticles() {
    this.particlesList = [];
    for (let i = 0; i < this.particlesNumber; i++) {
      this.particlesList.push(new Particle(canvas, particleSize))
    }
    quadTree = new QuadTree();
    for (const p of this.particlesList) p.reset(canvas);
  }

  scaleDensity() {
      var area = canvas.width * canvas.height / 1000;
      this.particlesNumber = (area * this.number / this.densityArea) | 0;
  }

  setCanvasSize() {
    canvas.height = canvas.offsetHeight;
    canvas.width = canvas.offsetWidth;
   if (this.densityArea) this.scaleDensity();
    this.resetParticles();
  }

  ngOnDestroy(): void {
    cancelAnimationFrame(this.animationFrame);
  }
}

class Link {
  p1: Particle;
  p2: Particle;
  alpha: number;
  batchId: number;
  constructor() {  }
  init(p1: Particle, p2: Particle) {
      this.p1 = p1;
      this.p2 = p2;
      const dx = p1.x - p2.x;
      const dy = p1.y - p2.y;
      this.alpha = 1 - (dx * dx + dy * dy) / linkDistance2;
      this.batchId = this.alpha * linkBatches | 0;
      this.batchId = this.batchId >= linkBatches ? linkBatches : this.batchId;
  }     
  addPath(ctx) {
      ctx.moveTo(this.p1.x, this.p1.y);
      ctx.lineTo(this.p2.x, this.p2.y);
      return this;
  }

}


class Particle {
  r: number;
  speedScale: number;
  x: number;
  y: number;
  vx: number;
  vy: number;
  quad: QuadTree;
  explored: boolean;
  constructor (canvas, r) {
      this.r = r;
      this.speedScale = particleSpeed / 2;
      this.reset(canvas, r);
  }
  reset(canvas, r = this.r) {
      const W = canvas.width - r * 2;
      const H = canvas.height - r * 2;
      this.x = Math.random() * W + r;
      this.y = Math.random() * H + r;
      this.vx = Math.random() - 0.5;
      this.vy = Math.random() - 0.5;
      this.quad = undefined;
      this.explored = false;

  }
  addPath(ctx) {
      ctx.moveTo(this.x + this.r,  this.y);
        ctx.arc(this.x,  this.y, this.r, 0, TAU);
  }
  near(p) {
      return ((p.x - this.x) ** 2 + (p.y - this.y) ** 2) <= linkDistance2;
  }
  intersects(range) {
      const xd = Math.abs(range.x - this.x);
      const yd = Math.abs(range.y - this.y);
      const r = linkDistance;
      const w = range.w;
      const h = range.h;
      if (xd > r + w || yd > r + h) { return false }
      if (xd <= w || yd <= h) { return true }
      return  ((xd - w) ** 2 + (yd - h) ** 2) <= linkDistance2;

  }
  update(ctx, repulse = true) { 
      this.explored = false;
      const r = this.r;
      let W, H;
      this.x += this.vx * this.speedScale;
      this.y += this.vy * this.speedScale;

      if (bounce) {
          W = ctx.canvas.width - r;
          H = ctx.canvas.height - r;
          if (this.x > W || this.x < 0) {
              this.vx = -this.vx;
          }
          if (this.y > H || this.y < 0) {
              this.vy = -this.vy;
          }
      } else {
          W = ctx.canvas.width + r;
          H = ctx.canvas.height + r;
          if (this.x > W) {
              this.x = 0;
              this.y = Math.random() * (H - r);
          } else if (this.x < -r) {
              this.x = W - r;
              this.y = Math.random() * (H - r);
          }
          if (this.y > H) {
              this.y = 0
              this.x = Math.random() * (W - r);
          } else if (this.y < -r) {
              this.y = H - r;
              this.x = Math.random() * (W - r);
          }
      }
      repulse && mouse.x && this.repulse();
      this.addPath(ctx);
      quadTree.insert(this);
      this.quad && (this.quad.drawn = false)
  }
  repulse() {
      var dx = this.x - mouse.x;
      var dy = this.y - mouse.y;

      const dist = (dx * dx + dy * dy) ** 0.5;
      var rf = ((1 - (dist / repulseDistance) ** 2)  * 100);
          rf = (rf < 0 ? 0 : rf > 50  ? 50 : rf) / dist;

      var posX = this.x + dx * rf;
      var posY = this.y + dy * rf;

      if (bounce) {
          if (posX - particleSize > 0 && posX + particleSize < canvas.width) this.x = posX;
          if (posY - particleSize > 0 && posY + particleSize < canvas.height) this.y = posY;
      } else {
          this.x = posX;
          this.y = posY;
      }
  }
}

class Bounds {
  x: number;
  y: number;
  w: number;
  h: number;
  left: number;
  right: number;
  top: number;
  bottom: number;
  diagonal: number;
  constructor(x, y, w, h) { this.init(x, y, w, h) }
  init(x,y,w,h) { 
      this.x = x; 
      this.y = y; 
      this.w = w; 
      this.h = h; 
      this.left = x - w;
      this.right = x + w;
      this.top = y - h;
      this.bottom = y + h;
      this.diagonal = (w * w + h * h);
  }

  contains(p) {
      return (p.x >= this.left && p.x <= this.right && p.y >= this.top && p.y <= this.bottom);
  }

  near(p) {
      if (!this.contains(p)) {
          const dx = p.x - this.x;
          const dy = p.y - this.y;
          const dist = (dx * dx + dy * dy) - this.diagonal - linkDistance2;
          return dist < 0;
      }
      return true;
  }
}

class QuadTree {
  boundary: Bounds;
  divided: boolean;
  points: Particle[];
  pointCount: number;
  drawn: boolean;
  depth: number;

  NE: QuadTree;
  NW: QuadTree;
  SE: QuadTree;
  SW: QuadTree;
  constructor(boundary: Bounds = new Bounds(canvas.width / 2,canvas.height / 2,canvas.width / 2 ,canvas.height / 2), depth = 0) {
  this.boundary = boundary;
      this.divided = false;     
      this.points = depth > 1 ? [] : null;
      this.pointCount = 0
      this.drawn = false;
      this.depth = depth;
      if(depth === 0) {   // BM67 Fix on resize
          this.subdivide();
          this.NE.subdivide();
          this.NW.subdivide();
          this.SE.subdivide();
          this.SW.subdivide();
      }
  }

  addPath() {
      const b = this.boundary;
      ctx.rect(b.left, b.top, b.w * 2, b.h * 2);
      this.drawn = true;
  }
  addToSubQuad(particle) {
      if (this.NE.insert(particle)) { return true }
      if (this.NW.insert(particle)) { return true }
      if (this.SE.insert(particle)) { return true }
      if (this.SW.insert(particle)) { return true } 
      particle.quad = undefined;        
  }
  insert(particle) {
      if (this.depth > 0 && !this.boundary.contains(particle)) { return false }

      if (this.depth > 1 && this.pointCount < QUADTREE_CAPACITY) { 
          this.points[this.pointCount++] = particle;
          particle.quad = this;
          return true;
      } 
      if (!this.divided) { this.subdivide() }
      return this.addToSubQuad(particle);
  }

  subdivide() {
      if (!this.NW) {
          const x = this.boundary.x;
          const y = this.boundary.y;
          const w = this.boundary.w / 2;
          const h = this.boundary.h / 2;
          const depth = this.depth + 1;

          this.NE = new QuadTree(new Bounds(x + w, y - h, w, h), depth);
          this.NW = new QuadTree(new Bounds(x - w, y - h, w, h), depth); 
          this.SE = new QuadTree(new Bounds(x + w, y + h, w, h), depth);
          this.SW = new QuadTree(new Bounds(x - w, y + h, w, h), depth);
      } else {
          this.NE.pointCount = 0;
          this.NW.pointCount = 0;            
          this.SE.pointCount = 0;
          this.SW.pointCount = 0;            
      }

      this.divided = true;
  }
  query(part, fc, found) {
      var i = this.pointCount;
      if (this.depth === 0 || this.boundary.near(part)) {
          if (this.depth > 1) {
              while (i--) {
                  const p = this.points[i];
                  if (!p.explored && part.near(p)) { found[fc++] = p }
              }
              if (this.divided) {
                  fc = this.NE.pointCount ? this.NE.query(part, fc, found) : fc;
                  fc = this.NW.pointCount ? this.NW.query(part, fc, found) : fc;
                  fc = this.SE.pointCount ? this.SE.query(part, fc, found) : fc;
                  fc = this.SW.pointCount ? this.SW.query(part, fc, found) : fc;
              }
          } else if(this.divided) {
              fc = this.NE.query(part, fc, found);
              fc = this.NW.query(part, fc, found);
              fc = this.SE.query(part, fc, found);
              fc = this.SW.query(part, fc, found);
          }
      }
      return fc;
  }

  close() {
      if (this.divided) {
         this.NE.close();
         this.NW.close();
         this.SE.close();
         this.SW.close();
      }

      if (this.depth === 2 && this.divided) {
          this.NE.pointCount = 0;
          this.NW.pointCount = 0;
          this.SE.pointCount = 0;
          this.SW.pointCount = 0;
      } else if (this.depth > 2) {
          this.divided = false;
      }
  }
}

4 Comments

Hi Ado, did you make an npm package for this or do you recommend that we just clone your repo? Thanks!
Hey Thanh, updated the repo to used an optimized directive over the previous standalone component. No need to make a package just copy paste file content into a new directive. See repo for steps.
Hi there, My particles don't appear and then when I right click and inspect in chrome they suddenly appear - any idea what that could be?
Turns out it was the HostListener("window:resize") that was triggering the setCanvasSize() function when I inspected in chrome - I moved the setCanvasSize call in the constructor to ngOninit and now the canvas size is set without the window needing to be resized. I'm on angular 9 so I'm not sure if that has something to do with it.
2

Just want to add a comment to the Ado Ren's solution ( I'm not able to comment on his solution because I have not enough reputation)

It works well on Angular 8 but with little changes

Change 28th row in particles.component.ts file :

From :  @ViewChild('particles') particlesCanvas: ElementRef;
To   :  @ViewChild('particles', {static: true}) particlesCanvas: ElementRef;

Without this little change the error is thrown in angular 8

ERROR in app/particles/particles.component.ts(28,4): error TS2554: Expected 2 arguments, but got 1.

Also, you should change the background color of particles component from the white to something else. Otherwise, you won't see them as they are white too.

1 Comment

updated repo to now use a directive and is optimized. Works on angular 8+ :) Thanks for your comment
-3

I was getting the error:

Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them.

Solution: Go to the particle.js file and modify it:


    -//Object.deepExtend = function (destination, source) {
    +Object.deepExtend = function deepExtendFunction(destination, source) {
      for (var property in source) {
        if (source[property] && source[property].constructor &&
         source[property].constructor === Object) {
          destination[property] = destination[property] || {}; 
    -       //arguments.callee(destination[property], source[property]);
    +      deepExtendFunction(destination[property], source[property]);
        } else {
          destination[property] = source[property];
        }
      }
      return destination;
    };

2 Comments

This does not really answer the question. If you have a different question, you can ask it by clicking Ask Question. To get notified when this question gets new answers, you can follow this question. Once you have enough reputation, you can also add a bounty to draw more attention to this question. - From Review
@StephanVierkant: Why not? It seems to expand on the problem by offering an error message, but then goes on o offer a solution.

Your Answer

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.