lexington®
Use code LEX35 at checkout
 
 
 
 7k+ customers.
How to create a particle text effect with Tailwind CSS and JavaScript
Published on October 10, 2024 by Michael Andreuzza
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 theinitfunction.this.createParticles(): This line of code will call thecreateParticlesfunction.this.addEventListeners(): This line of code will call theaddEventListenersfunction.this.startAnimation(): This line of code will call thestartAnimationfunction.
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 calledfragmentand set its value todocument.createDocumentFragment().const containerRect: This line of code will define a variable calledcontainerRectand 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.particleCounttimes. Variablesconst particle: This line of code will define a variable calledparticleand set its value todocument.createElement("span").particle.textContent: This line of code will set thetextContentof theparticleelement tothis.getRandomChar().particle.className: This line of code will set theclassNameof theparticleelement to"absolute opacity-0 transition-all duration-1000 ease-out".fragment.appendChild(particle): This line of code will append theparticleelement to thefragmentelement.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 theelementproperty set to theparticleelement, thexproperty set to a random number between 0 and the width of thecontainerRect, theyproperty set to a random number between 0 and the height of thecontainerRect, thespeedXproperty set to a random number between -1 and 1, and thespeedYproperty set to a random number between -1 and 1.this.container.appendChild(fragment);: This line of code will append thefragmentelement to thethis.containerelement.
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 calledgetRandomCharthat will be called when thecreateParticlesfunction is called. Return Valuereturn this.initialText[Math.floor(Math.random() * this.initialText.length)]: This line of code will return a random character from thethis.initialTextarray.
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 calledcontainerRectand set its value tothis.container.getBoundingClientRect(). Loopthis.particles.forEach((particle) => {: This line of code will iterate over each particle in thethis.particlesarray and pass the particle to the anonymous function.particle.x += particle.speedX;: This line of code will add thespeedXproperty of theparticleobject to thexproperty of theparticleobject.particle.y += particle.speedY;: This line of code will add thespeedYproperty of theparticleobject to theyproperty of theparticleobject.particle.x < 0 || particle.x > containerRect.width: This line of code will check if thexproperty of theparticleobject 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 theyproperty of theparticleobject 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 thetransformandopacitystyles to theparticleelement. Return Valuethis.animationFrame = requestAnimationFrame(this.animateParticles.bind(this));: This line of code will set theanimationFrameproperty of thethisobject to the result of calling therequestAnimationFramefunction with theanimateParticlesfunction 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 calledcontainerRectand set its value tothis.container.getBoundingClientRect().const fontSize: This line of code will define a variable calledfontSizeand set its value towindow.innerWidth < 1024 ? 16 : 54. This line of code will check if thewindow.innerWidthis less than 1024 pixels and if so, it will set thefontSizeto 16.const letterSpacing: This line of code will define a variable calledletterSpacingand set its value tofontSize * 0 .8. This line of code will multiply thefontSizeby 0.8 to get theletterSpacing.const textWidth: This line of code will define a variable calledtextWidthand set its value tothis.finalText.length * letterSpacing.startX: This line of code will define a variable calledstartXand set its value to(containerRect.width - textWidth) / 2. This line of code will calculate thestartXby subtracting thetextWidthfrom thecontainerRect.widthand then dividing by 2.startY: This line of code will define a variable calledstartYand set its value tocontainerRect.height / 2. This line of code will calculate thestartYby dividing thecontainerRect.heightby 2. Loopthis.particles.forEach((particle, index) => {: This line of code will iterate over each particle in thethis.particlesarray and pass the particle and the index to the anonymous function.if (index < this.finalText.length) {: This line of code will check if theindexis less than the length of thethis.finalTextarray.const targetX = startX + index * letterSpacing;: This line of code will define a variable calledtargetXand set its value tostartX + index * letterSpacing.const targetY = startY + (Math.random() - 0.5) * (fontSize / 2);: This line of code will define a variable calledtargetYand 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 thetransformandopacitystyles to theparticleelement.particle.element.textContent = this.finalText[index];: This line of code will set thetextContentof theparticleelement to thethis.finalText[index].} else {: This line of code will close the if statement ifindexis less than the length of thethis.finalTextarray.particle.element.style.opacity = "0";: This line of code will set theopacitystyle of theparticleelement 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 theanimationFrameproperty of thethisobject.this.createParticles();: This line of code will call thecreateParticlesfunction.this.animateParticles();: This line of code will call theanimateParticlesfunction.setTimeout(() => {: This line of code will use thesetTimeoutfunction to delay the execution of the following code by 2000 milliseconds.cancelAnimationFrame(this.animationFrame);: This line of code will cancel theanimationFrameproperty of thethisobject.this.reformText();: This line of code will call thereformTextfunction.}, 2000);: This line of code will close thesetTimeoutfunction.
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 calledcontainerRectand set its value tothis.container.getBoundingClientRect().this.particles.forEach((particle) => {: This line of code will iterate over each particle in thethis.particlesarray and pass the particle to the anonymous function.particle.x = Math.random() * containerRect.width;: This line of code will set thexproperty of theparticleobject to a random number between 0 and the width of thecontainerRect.particle.y = Math.random() * containerRect.height;: This line of code will set theyproperty of theparticleobject to a random number between 0 and the height of thecontainerRect.cancelAnimationFrame(this.animationFrame);: This line of code will cancel theanimationFrameproperty of thethisobject.this.animateParticles();: This line of code will call theanimateParticlesfunction.
 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.containerelement that will call thestartAnimationfunction when the element is clicked.window.addEventListener("resize", this.handleResize.bind(this));: This line of code will add a resize event listener to thewindowobject that will call thehandleResizefunction 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 theParticleTextAnimationclass with thetext-containerid, theABCDEFGHIJKLMNOPQRSTUVWXYZinitial text, and theHELLO EVERYONEfinal 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