如何在 Angular/Angular2/Angular4 应用程序中导入和使用 particles.js

How to import and use particles.js in an Angular/Angular2/Angular4 app

我有一个 Angular 应用程序,我想在其中使用 particles.js,但是我不知道如何添加它并使其正常工作。

我已将其添加到 .angular-cli.json

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

并且我已经将它导入到我的组件中

import * as  particlesJS from 'particles.js';

并尝试使用

对其进行初始化
  particlesJS.load('particles-js', 'assets/particles.json', function() {
    console.log('callback - particles.js config loaded');
  });

有人成功了吗?

操作方法如下:

  1. 只需在您的 index.html(cdn 或本地)中导入 particles.js

    <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
    
  2. 将 div 锚点放入您的组件模板中(您也可以将其放入 index.html 或其他位置)

    <div id="particles-js"></div>
    
  3. 通过添加简单类型定义(在您的组件中或 typings.d.ts 中)使包可见

    declare var particlesJS: any;
    
  4. 在 ngOnInit(或其他地方)初始化它

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

我做了一个小例子: http://plnkr.co/edit/GLRvYgNPJue4KqdMuAJB?p=preview

2019 年 11 月 22 日更新

我已经将 particles.js 的 angular 版本移植到指令中。在要用粒子覆盖的 div 中,添加一个带有 repulse-particles 的 canvas 元素。这是一个非常精简 版本的库,但仍然有很多选项。

在性能方面,该指令是对原始库的升级,使用四叉树和几项重大优化。您现在可以享受 1k 粒子的乐趣了 ~

另一个调整是对触摸移动设备的支持。

请参阅存储库中的示例或下面的 stackblitz 演示。

根据自己的喜好进行编辑:)

Repo 和 stackblitz 编辑器

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

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

演示

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

完整指令代码

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;
      }
  }
}

只想对Ado Ren的解决方案进行评论(我无法评论他的解决方案,因为我没有足够的声誉)

它在 Angular 8 上运行良好,但变化不大

更改 particles.component.ts 文件中的第 28 行:

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

如果没有这个小改动,错误将在 angular 8

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

此外,您应该将粒子组件的背景颜色从白色更改为其他颜色。否则,您将看不到它们,因为它们也是白色的。

使用原包,没有https://github.com/audrenbdb/angular-particlesjs可以按如下方式进行:

使用 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() {});
  }
}

粒子-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(可选,显示全高)

#particles-js {
  height: 100vh;
}

angular.json

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

我遇到了错误:

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

解决方法:进入particle.js文件修改:


    -//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;
    };