From our sponsor: Chromatic - Visual testing for Storybook, Playwright & Cypress. Catch UI bugs before your users do.
Two years ago, I started playing with lines in WebGL using THREE.MeshLine, a library made by Jaume Sanchez Elias for Three.js.
This library tackles the problem that you cannot handle the width of your lines with classic lines in Three.js. A MeshLine builds a strip of triangles billboarded to create a custom geometry instead of using the native WebGL GL_LINE method that does not support the width parameter.
These lines shaped as ribbons have a really interesting graphic style. They also have less vertices than a TubeGeometry usually used to create thick lines.
Animate a MeshLine
The only thing missing is the ability to animate lines without having to rebuild the geometry for each frame.
Based on what had already been started and how SVG Line animation works, I added three new parameters to MeshLineMaterial to visualize animated dashed line directly through the shader.
- DashRatio: The ratio between what is visible or not (
~0
: more visible,~1
: less visible) - DashArray: The length of a dash and its space (0 == no dash)
- DashOffset: The location where the first dash begins
Like with an SVG path, these parameters allow you to animate the entire traced line if they are correctly handled.
Here is a complete example of how to create and animate a MeshLine:
// Build an array of points
const segmentLength = 1;
const nbrOfPoints = 10;
const points = [];
for (let i = 0; i < nbrOfPoints; i++) {
points.push(i * segmentLength, 0, 0);
}
// Build the geometry
const line = new MeshLine();
line.setGeometry(points);
const geometry = line.geometry;
// Build the material with good parameters to animate it.
const material = new MeshLineMaterial({
transparent: true,
lineWidth: 0.1,
color: new Color('#ff0000'),
dashArray: 2, // always has to be the double of the line
dashOffset: 0, // start the dash at zero
dashRatio: 0.75, // visible length range min: 0.99, max: 0.5
});
// Build the Mesh
const lineMesh = new Mesh(geometry, material);
lineMesh.position.x = -4.5;
// ! Assuming you have your own webgl engine to add meshes on scene and update them.
webgl.add(lineMesh);
// ! Call each frame
function update() {
// Check if the dash is out to stop animate it.
if (lineMesh.material.uniforms.dashOffset.value < -2) return;
// Decrement the dashOffset value to animate the path with the dash.
lineMesh.material.uniforms.dashOffset.value -= 0.01;
}
Create your own line style
Now that you know how to animate lines, I will show you some tips on how to customize the shape of your lines.
Use SplineCurve or CatmullRomCurve3
These classes smooth an array of points that is roughly positioned. They are perfect to build curved and fluid lines and keep control of them (length, orientation, turbulences…).
For instance, let’s add some turbulences to our previous array of points:
const segmentLength = 1;
const nbrOfPoints = 10;
const points = [];
const turbulence = 0.5;
for (let i = 0; i < nbrOfPoints; i++) {
// ! We have to wrapped points into a THREE.Vector3 this time
points.push(new Vector3(
i * segmentLength,
(Math.random() * (turbulence * 2)) - turbulence,
(Math.random() * (turbulence * 2)) - turbulence,
));
}
Then, use one of these classes to smooth your array of lines before you create the geometry:
// 2D spline
// const linePoints = new Geometry().setFromPoints(new SplineCurve(points).getPoints(50));
// 3D spline
const linePoints = new Geometry().setFromPoints(new CatmullRomCurve3(points).getPoints(50));
const line = new MeshLine();
line.setGeometry(linePoints);
const geometry = line.geometry;
And like that you create your smooth curved line!
Note that SplineCurve only smoothes in 2D (x and y axis) compared to CatmullRomCurve3 that takes into account three axes.
I recommend to use the SplineCurve, anyway. It is more performant to calculate lines and is often enough to create the desired curved effect.
For instance, my demos Confetti and Energy are only made with the SplineCurve method:
Use Raycasting
Another technique taken from a THREE.MeshLine example is using a Raycaster to scan a Mesh already present in the scene.
Thus, you can create your lines that follow the shape of an object:
const radius = 4;
const yMax = -4;
const points = [];
const origin = new Vector3();
const direction = new Vector3();
const raycaster = new Raycaster();
let y = 0;
let angle = 0;
// Start the scan
while (y < yMax) {
// Update the orientation and the position of the raycaster
y -= 0.1;
angle += 0.2;
origin.set(radius * Math.cos(angle), y, radius * Math.sin(angle));
direction.set(-origin.x, 0, -origin.z);
direction.normalize();
raycaster.set(origin, direction);
// Save the coordinates raycsted.
// !Assuming the raycaster cross the object in the scene each time
const intersect = raycaster.intersectObject(objectToRaycast, true);
if (intersect.length) {
points.push(
intersect[0].point.x,
intersect[0].point.y,
intersect[0].point.z,
);
}
}
This method is employed in the Boreal Sky demo. Here I used a sphere part as geometry to create the mesh objectToRaycast
:
Now, you have enough tools to play and animate MeshLines. Many of these methods are inspired by the library’s examples. Feel free to explore these and share your own experiments and methods to create your own lines!
What about chance ‘Confetti’ font family and font size?
Even I’m enable to change the font family.
Please Help!
Awesome!
Can anybody help me to find information about hiding canvas with reverse camera motion ( example Shooting Stars). I’ll be very appreciated.
This does not work on Safari iOS. Throws this error in the console.
SyntaxError: Cannot declare a const variable twice: ‘t’.
Not sure if anyone knows the fix.
shouldnt confetti fall DOWN from the ceiling – instead of ‘falling UP’???
Thank you very much for your great work, I’ve tried but I couldn’t figure out how to stop the animation (after sometime or on a user action). Anyone have guess?