
Particle Text Effect using HTML CSS and JavaScript (Source Code)
Introduction
Particle text effects are a visually appealing way to make your website stand out.
These effects transform ordinary text into dynamic animations made up of particles, creating an engaging and modern design element.
In this guide, I will explain how to create a particle text effect using HTML, CSS, and JavaScript.
✅ Watch Live Preview 👉👉
Click Here to Download the Code
What is a Particle Text Effect?
A particle text effect involves breaking text into tiny dots or particles that move or interact dynamically.
For example, the particles can scatter when a cursor hovers over them or align with the text. This effect not only surprises users but also adds an interactive element to your website that enhances user engagement.
Why Use Particle Text Effects?
It is a fantastic way to make your website dynamic and interactive. They capture attention and leave a memorable impression on visitors.
Whether you’re designing a portfolio, a landing page, or an interactive marketing campaign, these effects can elevate the user experience by adding a visually striking and modern touch.
Not only does it make the text more visually engaging, but it also creates a sense of depth and movement that draws the viewer’s eye.
This effect is especially effective when paired with calls to action, headlines, or key messages, ensuring that important content stands out.
What are the Steps to Begin?
Let’s break the process into simple steps to make it easy to understand.
Set Up Your Project
To get started, create a folder and include the following files:
- index.html
- style.css
- script.js
Source Code
HTML Code Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>How to Create Particle Text Effect using HTML CSS and JavaScript</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- CODE -->
<div>
<canvas> </canvas>
</div>
<!-- CODE -->
<script src="script.js"></script>
</body>
</html>
CSS Code Structure
< /* Your CSS code here */ >
body {
display: grid;
place-items: center;
height: 100dvh;
background: #222;
}
canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
}
JavaScript Code Structure
< // JavaScript Code >
const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
// Configurable parameters
const config = {
particleCount: 5000,
textArray: ["Creative.", "Design.", "Idea."],
mouseRadius: 0.1,
particleSize: 2,
forceMultiplier: 0.001,
returnSpeed: 0.005,
velocityDamping: 0.95,
colorMultiplier: 40000,
saturationMultiplier: 1000,
textChangeInterval: 10000,
rotationForceMultiplier: 0.5
};
let currentTextIndex = 0;
let nextTextTimeout;
let textCoordinates = [];
const mouse = {
x: -500,
y: -500,
radius: config.mouseRadius
};
const particles = [];
for (let i = 0; i < config.particleCount; i++) {
particles.push({ x: 0, y: 0, baseX: 0, baseY: 0, vx: 0, vy: 0 });
}
const vertexShaderSource = `
attribute vec2 a_position;
attribute float a_hue;
attribute float a_saturation;
varying float v_hue;
varying float v_saturation;
void main() {
gl_PointSize = ${config.particleSize.toFixed(1)};
gl_Position = vec4(a_position, 0.0, 1.0);
v_hue = a_hue;
v_saturation = a_saturation;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying float v_hue;
varying float v_saturation;
void main() {
float c = v_hue * 6.0;
float x = 1.0 - abs(mod(c, 2.0) - 1.0);
vec3 color;
if (c < 1.0) color = vec3(1.0, x, 0.0);
else if (c < 2.0) color = vec3(x, 1.0, 0.0);
else if (c < 3.0) color = vec3(0.0, 1.0, x);
else if (c < 4.0) color = vec3(0.0, x, 1.0);
else if (c < 5.0) color = vec3(x, 0.0, 1.0);
else color = vec3(1.0, 0.0, x);
vec3 finalColor = mix(vec3(1.0), color, v_saturation);
gl_FragColor = vec4(finalColor, 1.0);
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(
gl,
gl.FRAGMENT_SHADER,
fragmentShaderSource
);
const program = createProgram(gl, vertexShader, fragmentShader);
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const hueAttributeLocation = gl.getAttribLocation(program, "a_hue");
const saturationAttributeLocation = gl.getAttribLocation(
program,
"a_saturation"
);
const positionBuffer = gl.createBuffer();
const hueBuffer = gl.createBuffer();
const saturationBuffer = gl.createBuffer();
const positions = new Float32Array(config.particleCount * 2);
const hues = new Float32Array(config.particleCount);
const saturations = new Float32Array(config.particleCount);
function getTextCoordinates(text) {
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = canvas.width;
ctx.canvas.height = canvas.height;
const fontSize = Math.min(canvas.width / 6, canvas.height / 6);
ctx.font = `900 ${fontSize}px Arial`;
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
const coordinates = [];
for (let y = 0; y < canvas.height; y += 4) {
for (let x = 0; x < canvas.width; x += 4) {
const index = (y * canvas.width + x) * 4;
if (imageData[index + 3] > 128) {
coordinates.push({
x: (x / canvas.width) * 2 - 1,
y: (y / canvas.height) * -2 + 1
});
}
}
}
return coordinates;
}
function createParticles() {
textCoordinates = getTextCoordinates(config.textArray[currentTextIndex]);
for (let i = 0; i < config.particleCount; i++) {
const randomIndex = Math.floor(Math.random() * textCoordinates.length);
const { x, y } = textCoordinates[randomIndex];
particles[i].x = particles[i].baseX = x;
particles[i].y = particles[i].baseY = y;
}
}
function updateParticles() {
for (let i = 0; i < config.particleCount; i++) {
const particle = particles[i];
const dx = mouse.x - particle.x;
const dy = mouse.y - particle.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const forceDirectionX = dx / distance;
const forceDirectionY = dy / distance;
const maxDistance = mouse.radius;
const force = (maxDistance - distance) / maxDistance;
const directionX = forceDirectionX * force * config.forceMultiplier;
const directionY = forceDirectionY * force * config.forceMultiplier;
const angle = Math.atan2(dy, dx);
const rotationForceX = Math.sin(
-Math.cos(angle * -1) *
Math.sin(config.rotationForceMultiplier * Math.cos(force)) *
Math.sin(distance * distance) *
Math.sin(angle * distance)
);
const rotationForceY = Math.sin(
Math.cos(angle * 1) *
Math.sin(config.rotationForceMultiplier * Math.sin(force)) *
Math.sin(distance * distance) *
Math.cos(angle * distance)
);
if (distance < mouse.radius) {
particle.vx -= directionX + rotationForceX;
particle.vy -= directionY + rotationForceY;
} else {
particle.vx += (particle.baseX - particle.x) * config.returnSpeed;
particle.vy += (particle.baseY - particle.y) * config.returnSpeed;
}
particle.x += particle.vx;
particle.y += particle.vy;
particle.vx *= config.velocityDamping;
particle.vy *= config.velocityDamping;
const speed = Math.sqrt(
particle.vx * particle.vx + particle.vy * particle.vy
);
const hue = (speed * config.colorMultiplier) % 360;
hues[i] = hue / 360;
saturations[i] = Math.min(speed * config.saturationMultiplier, 1);
positions[i * 2] = particle.x;
positions[i * 2 + 1] = particle.y;
}
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, hueBuffer);
gl.bufferData(gl.ARRAY_BUFFER, hues, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, saturationBuffer);
gl.bufferData(gl.ARRAY_BUFFER, saturations, gl.DYNAMIC_DRAW);
}
function animate() {
updateParticles();
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, hueBuffer);
gl.vertexAttribPointer(hueAttributeLocation, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(hueAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, saturationBuffer);
gl.vertexAttribPointer(saturationAttributeLocation, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(saturationAttributeLocation);
gl.useProgram(program);
gl.drawArrays(gl.POINTS, 0, config.particleCount);
requestAnimationFrame(animate);
}
canvas.addEventListener("mousemove", (event) => {
mouse.x = (event.clientX / canvas.width) * 2 - 1;
mouse.y = (event.clientY / canvas.height) * -2 + 1;
});
canvas.addEventListener("mouseleave", () => {
mouse.x = -500;
mouse.y = -500;
});
window.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
createParticles();
});
function changeText() {
currentTextIndex = (currentTextIndex + 1) % config.textArray.length;
const newCoordinates = getTextCoordinates(config.textArray[currentTextIndex]);
for (let i = 0; i < config.particleCount; i++) {
const randomIndex = Math.floor(Math.random() * newCoordinates.length);
const { x, y } = newCoordinates[randomIndex];
particles[i].baseX = x;
particles[i].baseY = y;
}
nextTextTimeout = setTimeout(changeText, config.textChangeInterval);
}
gl.clearColor(0, 0, 0, 1);
createParticles();
animate();
nextTextTimeout = setTimeout(changeText, config.textChangeInterval);
Proven Methods to Use
This effect can be used in various ways:
1. Highlight headings or titles.
2. Create interactive logos.
3. Enhance the aesthetic appeal of landing pages.
4. Add a modern touch to banners or advertisements.
Conclusion
By following the step-by-step code mentioned above, you can create these effects and customize them to fit your design needs.
Whether for personal projects or professional websites, a particle text effect adds creativity and uniqueness to your content.
13 people downloaded this
Get It FREE Now



