
Hello everyone, today we are going to create a simple particle text effect with Tailwind CSS and JavaScript. This effect is great for creating a fun and playful animation.
What is a particle text effect?
A particle text effect is an animation where individual particles move and come together to form text. These particles can start off scattered randomly across a container and gradually reform into specific characters, giving a dynamic visual impact. This technique often involves JavaScript to control particle movement and a framework like Tailwind CSS for styling the particles and text.
Use Cases
- Landing Pages: To grab attention with an interactive and engaging animation as part of the hero section.
- Loading Screens: To keep users entertained while a page or resource is loading.
- Text Reveals: For cool text reveals in digital presentations, banners, or promotional content.
- Interactive Typography: For adding playful animations to headlines or call-to-action buttons.
To the code!
The container
ID’s
id="text-container"
: This line of code will define the id of the container. This id will be used to target the container in the JavaScript code. Classesh-96
: This class will make the container element have a height of 96 pixels.lg:h-32
: This class will make the container element have a height of 32 pixels on screens with a width of 1024 pixels or greater.
We need to add a height to the container so that the particles can be positioned correctly.
flex
: This class will make the container element use flexbox.items-center
: This class will center the container element vertically.
<div
id="text-container"
class="flex items-center h-96 lg:h-32">
</div>
Time for the script!
The constructor
Parameters
containerId
: This parameter will be used to get the container element with the id of “text-container” from the document.initialText
: This parameter will be used to set the initial text of the particles.finalText
: This parameter will be used to set the final text of the particles.particleCount
: This parameter will be used to set the number of particles. Return Valuethis.container
: This line of code will return the container element with the id of “text-container” from the document.this.initialText
: This line of code will return the initial text of the particles.this.finalText
: This line of code will return the final text of the particles.this.particleCount
: This line of code will return the number of particles.this.particles
: This line of code will return an empty array.this.animationFrame
: This line of code will returnnull
. Initializationthis.init()
: This line of code will call theinit
function.this.createParticles()
: This line of code will call thecreateParticles
function.this.addEventListeners()
: This line of code will call theaddEventListeners
function.this.startAnimation()
: This line of code will call thestartAnimation
function.
constructor(containerId, initialText, finalText, particleCount = 500) {
this.container = document.getElementById(containerId);
this.initialText = initialText;
this.finalText = finalText;
this.particleCount = particleCount;
this.particles = [];
this.animationFrame = null;
this.init();
}
init() {
this.createParticles();
this.addEventListeners();
this.startAnimation();
}
The createParticles function
Variables
const fragment
: This line of code will define a variable calledfragment
and set its value todocument.createDocumentFragment()
.const containerRect
: This line of code will define a variable calledcontainerRect
and set its value tothis.container.getBoundingClientRect()
.
Loop
for (let i = 0; i < this.particleCount; i++)
: This line of code will create a loop that will iteratethis.particleCount
times. Variablesconst particle
: This line of code will define a variable calledparticle
and set its value todocument.createElement("span")
.particle.textContent
: This line of code will set thetextContent
of theparticle
element tothis.getRandomChar()
.particle.className
: This line of code will set theclassName
of theparticle
element to"absolute opacity-0 transition-all duration-1000 ease-out"
.fragment.appendChild(particle)
: This line of code will append theparticle
element to thefragment
element.this.particles.push({ element: particle, x: Math.random() * containerRect.width, y: Math.random() * containerRect.height, speedX: Math.random() * 2 - 1, speedY: Math.random() * 2 - 1 });
: This line of code will push an object with theelement
property set to theparticle
element, thex
property set to a random number between 0 and the width of thecontainerRect
, they
property set to a random number between 0 and the height of thecontainerRect
, thespeedX
property set to a random number between -1 and 1, and thespeedY
property set to a random number between -1 and 1.this.container.appendChild(fragment);
: This line of code will append thefragment
element to thethis.container
element.
createParticles() {
const fragment = document.createDocumentFragment();
const containerRect = this.container.getBoundingClientRect();
for (let i = 0; i < this.particleCount; i++) {
const particle = document.createElement("span");
particle.textContent = this.getRandomChar();
particle.className =
"absolute opacity-0 transition-all duration-1000 ease-out";
fragment.appendChild(particle);
this.particles.push({
element: particle,
x: Math.random() * containerRect.width,
y: Math.random() * containerRect.height,
speedX: Math.random() * 2 - 1,
speedY: Math.random() * 2 - 1,
});
}
this.container.appendChild(fragment);
}
Random character
getRandomChar()
: This line of code will define a function calledgetRandomChar
that will be called when thecreateParticles
function is called. Return Valuereturn this.initialText[Math.floor(Math.random() * this.initialText.length)]
: This line of code will return a random character from thethis.initialText
array.
getRandomChar() {
return this.initialText[
Math.floor(Math.random() * this.initialText.length)
];
}
Animating the particles
Variables
const containerRect
: This line of code will define a variable calledcontainerRect
and set its value tothis.container.getBoundingClientRect()
. Loopthis.particles.forEach((particle) => {
: This line of code will iterate over each particle in thethis.particles
array and pass the particle to the anonymous function.particle.x += particle.speedX;
: This line of code will add thespeedX
property of theparticle
object to thex
property of theparticle
object.particle.y += particle.speedY;
: This line of code will add thespeedY
property of theparticle
object to they
property of theparticle
object.particle.x < 0 || particle.x > containerRect.width
: This line of code will check if thex
property of theparticle
object is less than 0 or greater than the width of thecontainerRect
.particle.y < 0 || particle.y > containerRect.height
: This line of code will check if they
property of theparticle
object is less than 0 or greater than the height of thecontainerRect
.Object.assign(particle.element.style, { transform:
translate(${particle.x}px, ${particle.y}px), opacity: "1" });
: This line of code will assign thetransform
andopacity
styles to theparticle
element. Return Valuethis.animationFrame = requestAnimationFrame(this.animateParticles.bind(this));
: This line of code will set theanimationFrame
property of thethis
object to the result of calling therequestAnimationFrame
function with theanimateParticles
function as an argument.
animateParticles() {
const containerRect = this.container.getBoundingClientRect();
this.particles.forEach((particle) => {
particle.x += particle.speedX;
particle.y += particle.speedY;
if (particle.x < 0 || particle.x > containerRect.width)
particle.speedX *= -1;
if (particle.y < 0 || particle.y > containerRect.height)
particle.speedY *= -1;
Object.assign(particle.element.style, {
transform: `translate(${particle.x}px, ${particle.y}px)`,
opacity: "1",
});
});
this.animationFrame = requestAnimationFrame(
this.animateParticles.bind(this)
);
}
The reformText function
Variables
const containerRect
: This line of code will define a variable calledcontainerRect
and set its value tothis.container.getBoundingClientRect()
.const fontSize
: This line of code will define a variable calledfontSize
and set its value towindow.innerWidth < 1024 ? 16 : 54
. This line of code will check if thewindow.innerWidth
is less than 1024 pixels and if so, it will set thefontSize
to 16.const letterSpacing
: This line of code will define a variable calledletterSpacing
and set its value tofontSize * 0 .8
. This line of code will multiply thefontSize
by 0.8 to get theletterSpacing
.const textWidth
: This line of code will define a variable calledtextWidth
and set its value tothis.finalText.length * letterSpacing
.startX
: This line of code will define a variable calledstartX
and set its value to(containerRect.width - textWidth) / 2
. This line of code will calculate thestartX
by subtracting thetextWidth
from thecontainerRect.width
and then dividing by 2.startY
: This line of code will define a variable calledstartY
and set its value tocontainerRect.height / 2
. This line of code will calculate thestartY
by dividing thecontainerRect.height
by 2. Loopthis.particles.forEach((particle, index) => {
: This line of code will iterate over each particle in thethis.particles
array and pass the particle and the index to the anonymous function.if (index < this.finalText.length) {
: This line of code will check if theindex
is less than the length of thethis.finalText
array.const targetX = startX + index * letterSpacing;
: This line of code will define a variable calledtargetX
and set its value tostartX + index * letterSpacing
.const targetY = startY + (Math.random() - 0.5) * (fontSize / 2);
: This line of code will define a variable calledtargetY
and set its value tostartY + (Math.random() - 0.5) * (fontSize / 2)
.Object.assign(particle.element.style, { transform:
translate(${targetX}px, ${targetY}px), opacity: "1" });
: This line of code will assign thetransform
andopacity
styles to theparticle
element.particle.element.textContent = this.finalText[index];
: This line of code will set thetextContent
of theparticle
element to thethis.finalText[index]
.} else {
: This line of code will close the if statement ifindex
is less than the length of thethis.finalText
array.particle.element.style.opacity = "0";
: This line of code will set theopacity
style of theparticle
element to “0”.
reformText() {
const containerRect = this.container.getBoundingClientRect();
const fontSize = window.innerWidth < 1024 ? 16 : 54;
const letterSpacing = fontSize * 0.8;
const textWidth = this.finalText.length * letterSpacing;
const startX = (containerRect.width - textWidth) / 2;
const startY = containerRect.height / 2;
this.particles.forEach((particle, index) => {
if (index < this.finalText.length) {
const targetX = startX + index * letterSpacing;
const targetY = startY + (Math.random() - 0.5) * (fontSize / 2);
Object.assign(particle.element.style, {
transform: `translate(${targetX}px, ${targetY}px)`,
opacity: "1",
});
particle.element.textContent = this.finalText[index];
} else {
particle.element.style.opacity = "0";
}
});
}
Start animation function
cancelAnimationFrame(this.animationFrame);
: This line of code will cancel theanimationFrame
property of thethis
object.this.createParticles();
: This line of code will call thecreateParticles
function.this.animateParticles();
: This line of code will call theanimateParticles
function.setTimeout(() => {
: This line of code will use thesetTimeout
function to delay the execution of the following code by 2000 milliseconds.cancelAnimationFrame(this.animationFrame);
: This line of code will cancel theanimationFrame
property of thethis
object.this.reformText();
: This line of code will call thereformText
function.}, 2000);
: This line of code will close thesetTimeout
function.
startAnimation() {
cancelAnimationFrame(this.animationFrame);
this.createParticles();
this.animateParticles();
setTimeout(() => {
cancelAnimationFrame(this.animationFrame);
this.reformText();
}, 2000);
}
Handle resize function
const containerRect = this.container.getBoundingClientRect();
: This line of code will define a variable calledcontainerRect
and set its value tothis.container.getBoundingClientRect()
.this.particles.forEach((particle) => {
: This line of code will iterate over each particle in thethis.particles
array and pass the particle to the anonymous function.particle.x = Math.random() * containerRect.width;
: This line of code will set thex
property of theparticle
object to a random number between 0 and the width of thecontainerRect
.particle.y = Math.random() * containerRect.height;
: This line of code will set they
property of theparticle
object to a random number between 0 and the height of thecontainerRect
.cancelAnimationFrame(this.animationFrame);
: This line of code will cancel theanimationFrame
property of thethis
object.this.animateParticles();
: This line of code will call theanimateParticles
function.
handleResize() {
const containerRect = this.container.getBoundingClientRect();
this.particles.forEach((particle) => {
particle.x = Math.random() * containerRect.width;
particle.y = Math.random() * containerRect.height;
});
cancelAnimationFrame(this.animationFrame);
this.animateParticles();
}
The addEventListeners function
this.container.addEventListener("click", this.startAnimation.bind(this));
: This line of code will add a click event listener to thethis.container
element that will call thestartAnimation
function when the element is clicked.window.addEventListener("resize", this.handleResize.bind(this));
: This line of code will add a resize event listener to thewindow
object that will call thehandleResize
function when the window is resized.
addEventListeners() {
this.container.addEventListener("click", this.startAnimation.bind(this));
window.addEventListener("resize", this.handleResize.bind(this));
}
Event listener for the window
new ParticleTextAnimation("text-container", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "HELLO EVERYONE");
: This line of code will create a new instance of theParticleTextAnimation
class with thetext-container
id, theABCDEFGHIJKLMNOPQRSTUVWXYZ
initial text, and theHELLO EVERYONE
final text.
document.addEventListener("DOMContentLoaded", () => {
new ParticleTextAnimation(
"text-container",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"HELLO EVERYONE"
);
});
The full script.
This is how the full script will look like when you’re done.
class ParticleTextAnimation {
constructor(containerId, initialText, finalText, particleCount = 500) {
this.container = document.getElementById(containerId);
this.initialText = initialText;
this.finalText = finalText;
this.particleCount = particleCount;
this.particles = [];
this.animationFrame = null;
this.init();
}
init() {
this.createParticles();
this.addEventListeners();
this.startAnimation();
}
createParticles() {
const fragment = document.createDocumentFragment();
const containerRect = this.container.getBoundingClientRect();
for (let i = 0; i < this.particleCount; i++) {
const particle = document.createElement("span");
particle.textContent = this.getRandomChar();
particle.className =
"absolute opacity-0 transition-all duration-1000 ease-out";
fragment.appendChild(particle);
this.particles.push({
element: particle,
x: Math.random() * containerRect.width,
y: Math.random() * containerRect.height,
speedX: Math.random() * 2 - 1,
speedY: Math.random() * 2 - 1,
});
}
this.container.appendChild(fragment);
}
getRandomChar() {
return this.initialText[
Math.floor(Math.random() * this.initialText.length)
];
}
animateParticles() {
const containerRect = this.container.getBoundingClientRect();
this.particles.forEach((particle) => {
particle.x += particle.speedX;
particle.y += particle.speedY;
if (particle.x < 0 || particle.x > containerRect.width)
particle.speedX *= -1;
if (particle.y < 0 || particle.y > containerRect.height)
particle.speedY *= -1;
Object.assign(particle.element.style, {
transform: `translate(${particle.x}px, ${particle.y}px)`,
opacity: "1",
});
});
this.animationFrame = requestAnimationFrame(
this.animateParticles.bind(this)
);
}
reformText() {
const containerRect = this.container.getBoundingClientRect();
const fontSize = window.innerWidth < 1024 ? 16 : 54;
const letterSpacing = fontSize * 0.8;
const textWidth = this.finalText.length * letterSpacing;
const startX = (containerRect.width - textWidth) / 2;
const startY = containerRect.height / 2;
this.particles.forEach((particle, index) => {
if (index < this.finalText.length) {
const targetX = startX + index * letterSpacing;
const targetY = startY + (Math.random() - 0.5) * (fontSize / 2);
Object.assign(particle.element.style, {
transform: `translate(${targetX}px, ${targetY}px)`,
opacity: "1",
});
particle.element.textContent = this.finalText[index];
} else {
particle.element.style.opacity = "0";
}
});
}
startAnimation() {
cancelAnimationFrame(this.animationFrame);
this.createParticles();
this.animateParticles();
setTimeout(() => {
cancelAnimationFrame(this.animationFrame);
this.reformText();
}, 2000);
}
handleResize() {
const containerRect = this.container.getBoundingClientRect();
this.particles.forEach((particle) => {
particle.x = Math.random() * containerRect.width;
particle.y = Math.random() * containerRect.height;
});
cancelAnimationFrame(this.animationFrame);
this.animateParticles();
}
addEventListeners() {
this.container.addEventListener("click", this.startAnimation.bind(this));
window.addEventListener("resize", this.handleResize.bind(this));
}
}
document.addEventListener("DOMContentLoaded", () => {
new ParticleTextAnimation(
"text-container",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"HELLO EVERYONE"
);
});
Conclusion
This is a simple particle text effect that you can recreate with Tailwind CSS and JavaScript. You can customize the colors, number of particles, and text to your liking.
Hope you enjoyed this tutorial and have a good day!
/Michael Andreuzza
Unlock all themes for $199$139 and own them forever! Includes lifetime
updates, new themes, unlimited projects, and support
