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:
- Gets the total number of points on the path
- The distance between the points and the path origin can be obtained according to the total number of points
- Use getPointAtLength to calculate the coordinates of a point based on this distance
- 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
- First, get the coordinates of all the points, pass in
position
This attribute - Generate random transparent values, passed in
aOpacity
This attribute - Create custom shader materials to achieve the effect of dazzling light particles
- 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.