<div class="container" id="webgl"></div>
html, body
{
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
background-color: black;
}
View Compiled
console.clear();
class Stage {
constructor(mount) {
this.container = mount;
this.lines = [];
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color( 'black' );
this.tickFunctions = [];
this.size = {
width: 1,
height: 1
}
this.setupCamera();
this.setupRenderer();
this.onResize();
window.addEventListener('resize', () => this.onResize());
this.tick();
}
setupCamera() {
this.lookAt = new THREE.Vector3(0, 0, 0);
this.camera = new THREE.PerspectiveCamera(40, this.size.width / this.size.height, 0.1, 150);
this.camera.position.set(0, 80, 100);
this.camera.home = {
position: { ...this.camera.position }
}
this.add(this.camera);
}
setupRenderer() {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
antialias: true,
})
this.container.appendChild( this.renderer.domElement );
}
onResize() {
this.size.width = this.container.clientWidth;
this.size.height = this.container.clientHeight;
this.camera.aspect = this.size.width / this.size.height
this.camera.updateProjectionMatrix()
this.renderer.setSize(this.size.width, this.size.height)
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
}
addTickFunction(tickFunction)
{
this.tickFunctions.push(tickFunction)
}
tick() {
this.camera.lookAt(this.lookAt);
this.tickFunctions.forEach(func => func())
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(() => this.tick())
}
add(element) { this.scene.add(element);}
destroy() {
this.container.removeChild( this.renderer.domElement);
window.removeEventListener('resize', this.onResize);
}
}
class Line
{
constructor(width, color, radius, near, far) {
this.color = color;
this.width = width;
this.radius = radius;
this.near = near;
this.far = far;
this.dashLength = 5 ;
this.travelDistance = 1;
this.mesh = null;
this.createLine()
}
createLine()
{
const points = [];
var angle = Math.random() * TAU
this.radius += Math.random() * 0.1
let pos = {
x: Math.cos(angle) * this.radius,
y: 0,
z: Math.sin(angle) * this.radius
}
let direction = angle + 1 + (Math.random() * 0.1);
let y = 0;
const steps = 75;
for (let j = 0; j < steps; j ++) {
direction += (0.012 * ((steps - j) * 0.1)) + (-0.4 + Math.random() * 0.8 ) * (j * (Math.random() * 0.02));
y += (-1 + Math.random() * 2) * (j * 0.001);
pos.x += Math.cos(direction) * this.travelDistance
pos.y += Math.sin(y) * this.travelDistance
pos.z += Math.sin(direction) * this.travelDistance
points.push(pos.x, pos.y, pos.z);
}
const line = new MeshLine();
line.setPoints(points, p => this.width * Math.pow(1 - Math.cos(TAU * p), 0.3) );
const material = new MeshLineMaterial({
color: new THREE.Color(this.color),
transparent: true,
depthTest: false,
dashArray: this.dashLength,
dashOffset: 0,
dashRatio: 0.92
});
this.mesh = new THREE.Mesh(line, material);
setTimeout(() => {
const delayedStart = Math.random() > 0.3;
if(delayedStart) setTimeout(() => this.animate(), 100 + Math.random() * 4000)
else this.animate(false)
}, 1000)
}
animate(slow = true)
{
const duration = (slow ? 4 : 3) + Math.random() * 1;
const ease = 'power4.inOut';
const brightness = slow ? .8 : 1;
gsap.fromTo(this.mesh.material.uniforms.dashOffset,
{ value: 0 },
{
value: -this.dashLength * 0.3,
duration,
ease,
onComplete: () => setTimeout(() => this.animate(), Math.random() * 4000)
})
const color = this.mesh.material.uniforms.color.value;
gsap.fromTo(color, { r: 0, g: 0, b: 0, }, {
motionPath: [
{
r: color.r + brightness,
g: color.g + brightness,
b: color.b + brightness,
},
{
r: color.r,
g: color.g,
b: color.b,
}
],
duration: duration * 0.5,
ease: 'power2.in'
})
}
}
const TAU = Math.PI * 2;
const colors = ["#f94144","#f3722c","#f8961e","#f9844a","#f9c74f","#90be6d","#43aa8b","#4d908e","#577590","#277da1"]
const canvas = document.getElementById('webgl');
const stage = new Stage(canvas);
const lineGroup = new THREE.Group();
lineGroup.rotation.y = Math.PI
stage.add(lineGroup);
for (let i = 0; i < 600; i++) {
const lineThickness = 0.1 + Math.random() * 0.1;
const startRadius = 2 + Math.random() * 0.5;
const lineColor = colors[Math.floor(Math.random() * colors.length)];
const line = new Line(
lineThickness,
lineColor,
startRadius,
stage.camera.near,
stage.camera.far
)
lineGroup.add(line.mesh);
stage.lines.push({
obj: line,
speed: 0.002 + Math.random() * 0.009
});
}
const tick = () => {
lineGroup.rotation.y += 0.01;
lineGroup.rotation.x = Math.cos(lineGroup.rotation.y * 1.2) * 0.5;
lineGroup.rotation.z = Math.sin(lineGroup.rotation.y) * 0.01;
}
stage.addTickFunction(tick)
View Compiled
This Pen doesn't use any external CSS resources.