preface

Hello, this is CSS magic – Alphardex.

Particle animation refers to the animation in which many particles move in order or disorder in a certain space, and the color size can also be changed according to certain rules. In this paper, it is a particle according to the path luminous moving effect

Key words interpretation

  • Dazzle light: custom coloring material
  • Path: path of SVG
  • Particles: Points object of three.js
  • Animation: Updates to some variables in the requestAnimationFrame

The preparatory work

The author’s own encapsulation of three. Js template: three. Js Starter

Readers can start the project by clicking on the bottom right fork

The path to SVG can be directly CV the path to the following demo (drawn casually with Inkscape)

Codepen. IO/alphardex/p…

positive

Take a good shelf

<div class="relative w-screen h-screen">
  <div class="travelling-particles w-full h-full bg-black"></div>
  <svg class="svg-particles hidden" xmlns="http://www.w3.org/2000/svg">(Path data CV here)</svg>
</div>
Copy the code
class TravellingParticles extends Base {
  constructor(sel: string, debug: boolean) {
    super(sel, debug);
    this.perspectiveCameraParams.near = 100;
    this.perspectiveCameraParams.far = 1000;
    this.cameraPosition = new THREE.Vector3(0.0.600);
    this.lines = [];
    this.pointSize = 4;
    this.activePointCount = 0;
    this.params = {
      mapOffsetX: -80.mapOffsetY: 160.activePointPerLine: 100.opacityRate: 15.pointSize: 30000.pointSpeed: 1.pointColor: "#4ec0e9"}; }/ / initialization
  init() {
    this.createScene();
    this.createPerspectiveCamera();
    this.createRenderer();
    this.createEverything();
    this.createLight();
    this.createOrbitControls();
    this.addListeners();
    this.setLoop();
  }
  // Create everything
  createEverything() {
    if (this.map) {
      this.scene.remove(this.map);
    }
    this.lines = [];
    if (this.points) {
      this.scene.remove(this.points);
      this.points = null;
    }
    this.getSvgPathsPointLineData();
    this.createPoints(); }}const start = () = > {
  const travellingParticles = new TravellingParticles(
    ".travelling-particles".true
  );
  travellingParticles.init();
};

start();
Copy the code

Gets the data at the midpoint of the path

class TravellingParticles extends Base {
  getSvgPathsPointLineData() {
    const paths = ([
      ...document.querySelectorAll(".svg-particles path"),]as unknown) as SVGPathElement[];
    paths.forEach((path) = > {
      const pathLength = path.getTotalLength();
      const pointCount = Math.floor(pathLength / this.pointSize);
      const points = [];
      for (let i = 0; i < pointCount; i++) {
        // Get the distance between the point and the path origin, and then get its coordinates
        const distance = (pathLength * i) / pointCount;
        const point = path.getPointAtLength(distance);
        if (point) {
          let { x, y } = point;
          // Place the dot in the center of the screen
          x -= this.params.mapOffsetX;
          y -= this.params.mapOffsetY;
          // add some randomness
          const randX = ky.randomNumberInRange(-1.5.1.5);
          const randY = ky.randomNumberInRange(-1.5.1.5);
          x += randX;
          y += randY;
          points.push(new THREE.Vector3(x, y, 0)); }}const line = {
        points,
        pointCount,
        currentPos: 0,}as Line;
      this.lines.push(line); }); }}Copy the code

Select all path elements and process them one by one:

  1. Gets the total number of points on the path
  2. The distance between the points and the path origin can be obtained according to the total number of points
  3. Use getPointAtLength to calculate the coordinates of a point based on this distance
  4. Once you have the coordinates of the points, you can form lines

Create a point

class TravellingParticles extends Base {
  createPoints() {
    this.activePointCount = this.lines.length * this.params.activePointPerLine;
    const geometry = new THREE.BufferGeometry();
    const pointCoords = this.lines
      .map((line) = > line.points.map((point) = > [point.x, point.y, point.z]))
      .flat(1)
      .slice(0.this.activePointCount)
      .flat(1);
    const positions = new Float32Array(pointCoords);
    this.positions = positions;
    const opacitys = new Float32Array(positions.length).map(
      () = > Math.random() / this.params.opacityRate
    );
    this.opacitys = opacitys;
    geometry.setAttribute("position".new THREE.BufferAttribute(positions, 3));
    geometry.setAttribute("aOpacity".new THREE.BufferAttribute(opacitys, 1));
    this.geometry = geometry;
    const material = new THREE.ShaderMaterial({
      vertexShader: travellingParticlesVertexShader,
      fragmentShader: travellingParticlesFragmentShader,
      side: THREE.DoubleSide,
      transparent: true.depthTest: true.depthWrite: true.blending: THREE.AdditiveBlending,
      uniforms: {
        uSize: {
          value: this.params.pointSize,
        },
        uColor: {
          value: new THREE.Color(this.params.pointColor),
        },
      },
    });
    this.material = material;
    const points = new THREE.Points(geometry, material);
    this.scene.add(points);
    this.points = points; }}Copy the code

Here, how to use three.js to customize the shape and material, mainly by BufferGeometry

  1. First, get the coordinates of all the points, pass inpositionThis attribute
  2. Generate random transparent values, passed inaOpacityThis attribute
  3. Create custom shader materials to achieve the effect of dazzling light particles
  4. Finally, create the Points instance and add it to the scenario

AdditiveBlending is the behind-the-scenes creator of the dazzling light effect

Let us to write the shader: vertex shader travellingParticlesVertexShader and fragment shader travellingParticlesFragmentShader

Vertex shader

Determines the position of the particle. The following code is generic and can be used as a template

attribute float aOpacity;

uniform float uSize;

varying float vOpacity;

void main(){
    vec4 modelPosition=modelMatrix*vec4(position,1.);
    vec4 viewPosition=viewMatrix*modelPosition;
    vec4 projectedPosition=projectionMatrix*viewPosition;
    gl_Position=projectedPosition;
    gl_PointSize*=(uSize/-viewPosition.z);

    vOpacity=aOpacity;
}
Copy the code

Note that opacity is independent of position, and is passed to the slice shader, hence the function vOpacity is used for this function

Chip shader

Determines the color of the particle

varying float vOpacity;

uniform vec3 uColor;

float invert(float n){
    return 1.-n;
}

void main(){
    vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
    vec2 cUv=2.*uv1.;
    vec4 color=vec4(1./length(cUv));
    color*=vOpacity;
    color.rgb*=uColor;
    gl_FragColor=color;
}
Copy the code

The above color formula calculation does not understand it does not matter, because the chip shader also has many general formula, here the formula is to form a luminous dot pattern, we just need to assign color and transparency to it

move

With so many points created, how do you get them to “move” online?

The answer is: for each line, just iterate over all the points they want to move and increase their subscripts. (Note the use of the complementary notation, which is used to keep the motion going over and over again.)

But this is not enough; you must also synchronize the data to the shader

class TravellingParticles extends Base {
  update() {
    if (this.points) {
      let activePoint = 0;
      this.lines.forEach((line) = > {
        // make the first n points of the line move
        line.currentPos += this.params.pointSpeed;
        for (let i = 0; i < this.params.activePointPerLine; i++) {
          const currentIndex = (line.currentPos + i) % line.pointCount;
          // Synchronize data to the shader
          const point = line.points[currentIndex];
          if (point) {
            const { x, y, z } = point;
            this.positions.set([x, y, z], activePoint * 3);
            this.opacitys.set(
              [i / (this.params.activePointPerLine * this.params.opacityRate)], activePoint ); activePoint++; }}});this.geometry.attributes.position.needsUpdate = true; }}}Copy the code

rendering

The last

Js custom shape BufferGeometry with ShaderMaterial coloring equipment can also achieve many more cool effects, you can explore.