Back to Components
Interactive Text Reveal Card with Mouse and Touch Animation using Pure JavaScript and CSS
Component

Interactive Text Reveal Card with Mouse and Touch Animation using Pure JavaScript and CSS

CodewithLord
October 24, 2025

Create an eye-catching text reveal animation card using pure HTML, CSS, and JavaScript — no libraries needed. This interactive card smoothly reveals text as users move their mouse or swipe, with a glowing divider and twinkling star effects in the background.

🧠 Description

This fun interactive To-Do List animates a hand-drawn SVG line across each task whenever you check its box.
It combines HTML structure, CSS transitions, and JavaScript DOM manipulation to create a smooth, satisfying animation effect — making your task list feel alive.
You can use it for daily planner UIs, interactive learning projects, or creative portfolio demos.


💻 HTML Code


1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width,initial-scale=1" /> 6 <title>Text Reveal Card — Pure JS</title> 7 <!-- <style>Paste The Css code here</style> --> 8 9</head> 10<body> 11 12 <div class="text-reveal-card" id="card"> 13 <h3 class="card-head">Text Reveal Card Title</h3> 14 <p class="card-desc">Move pointer (or touch) left/right to reveal the upper text and hide the lower text below it.</p> 15 16 <div class="text-container" id="textContainer"> 17 <div class="stars" id="stars"></div> 18 19 <!-- base text (dark) --> 20 <p class="text-base" id="baseText">Original Text That Sits Underneath</p> 21 22 <!-- reveal text (white gradient) - will be clipped to reveal portion --> 23 <p class="text-reveal" id="revealText">Revealed Text Appears Here</p> 24 25 <!-- divider --> 26 <div class="divider" id="divider"></div> 27 </div> 28 </div> 29 30 <!-- <script> "Paste the following script code inside this "<script> --> 31</body> 32</html> 33

The HTML defines a single .text-reveal-card containing:

A title and description explaining the interaction.

A .text-container that houses:

A stars layer for background animation.

Two overlapping text elements — text-base (bottom) and text-reveal (top).

A vertical divider line that moves with the cursor or touch.

This clean structure allows the top text to be “clipped” dynamically, showing only a portion of it based on user interaction.

CSS Code


1<style>> 2 :root { 3 --card-bg: #1d1c20; 4 --card-border: rgba(255,255,255,0.08); 5 --card-width: 40rem; 6 --card-radius: 12px; 7 --reveal-gradient: linear-gradient(to bottom, #ffffff, #d4d4d4); 8 --base-gradient: linear-gradient(to bottom, #323238, #1f1f22); 9 } 10 11 html,body{ 12 height:100%; 13 margin:0; 14 } 15 16 body { 17 display: flex; 18 align-items: center; 19 justify-content: center; 20 background: #0b0b0b; 21 font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; 22 color: #fff; 23 padding: 2rem; 24 } 25 26 .text-reveal-card { 27 width: var(--card-width); 28 background: var(--card-bg); 29 border: 1px solid var(--card-border); 30 border-radius: var(--card-radius); 31 padding: 2rem; 32 box-sizing: border-box; 33 position: relative; 34 overflow: hidden; 35 } 36 37 .card-head { 38 margin-bottom: 0.6rem; 39 } 40 41 .card-desc { 42 color: #a9a9a9; 43 font-size: 0.95rem; 44 margin-bottom: 1.2rem; 45 } 46 47 .text-container { 48 height: 10rem; /* same as original h-40 (approx) */ 49 position: relative; 50 display: flex; 51 align-items: center; 52 overflow: hidden; 53 user-select: none; 54 -webkit-user-select: none; 55 touch-action: none; 56 } 57 58 /* Both texts share identical layout so they sit exactly on top of each other */ 59 .text-base, 60 .text-reveal { 61 font-weight: 800; 62 font-size: 2rem; 63 line-height: 1; 64 white-space: nowrap; 65 margin: 0; 66 padding: 1rem 0; 67 position: absolute; 68 left: 2rem; /* small horizontal padding inside card */ 69 right: 2rem; 70 top: 50%; 71 transform: translateY(-50%); 72 z-index: 1; 73 pointer-events: none; 74 -webkit-background-clip: text; 75 background-clip: text; 76 color: transparent; 77 } 78 79 /* base text: darker background gradient */ 80 .text-base { 81 background-image: var(--base-gradient); 82 z-index: 10; /* underneath reveal but above stars */ 83 filter: none; 84 /* mask gradient similar to original */ 85 -webkit-mask-image: linear-gradient(to bottom, transparent, white, transparent); 86 mask-image: linear-gradient(to bottom, transparent, white, transparent); 87 } 88 89 /* reveal text sits above base and is clipped to show only the portion we want */ 90 .text-reveal { 91 background-image: var(--reveal-gradient); 92 z-index: 30; 93 /* start fully hidden by clip-path (covering from the right) */ 94 clip-path: inset(0 100% 0 0); /* top right bottom left — 100% hides all */ 95 transition: clip-path .08s linear; /* quick smoothing while moving */ 96 text-shadow: 4px 4px 15px rgba(0,0,0,0.5); 97 } 98 99 /* vertical divider line */ 100 .divider { 101 position: absolute; 102 top: 0; 103 height: 100%; 104 width: 8px; 105 z-index: 40; 106 pointer-events: none; 107 transform-origin: center; 108 opacity: 0; 109 transition: left .08s linear, transform .08s linear, opacity .2s ease; 110 background: linear-gradient(to bottom, transparent, rgba(150,150,150,0.9), transparent); 111 } 112 113 /* stars layer */ 114 .stars { 115 position: absolute; 116 inset: 0; 117 z-index: 5; 118 overflow: hidden; 119 pointer-events: none; 120 } 121 122 .star { 123 position: absolute; 124 width: 2px; 125 height: 2px; 126 border-radius: 50%; 127 background: white; 128 opacity: 0.8; 129 transform-origin: center; 130 animation: twinkle linear infinite; 131 z-index: 5; 132 } 133 134 @keyframes twinkle { 135 0% { opacity: 0; transform: scale(1); } 136 50% { opacity: 1; transform: scale(1.2); } 137 100% { opacity: 0; transform: scale(0); } 138 } 139 140 /* small responsive tweak */ 141 @media (max-width:640px){ 142 :root { --card-width: calc(100% - 2rem); } 143 .text-base, .text-reveal { font-size: 1.6rem; left: 1rem; right:1rem; } 144 .text-container { height: 6.5rem; } 145 } 146 </style>

The CSS handles the layout, gradients, and animation effects:

:root defines theme variables like colors, gradients, and card size.

.text-reveal-card styles the card with padding, rounded corners, and dark background.

.text-base and .text-reveal sit directly on top of each other with transparent text and gradient backgrounds.

text-base uses a darker gradient.

text-reveal uses a lighter gradient and a clip-path that hides or reveals parts of the text dynamically.

.divider is a glowing vertical line that moves with the cursor, marking the reveal boundary.

.stars and .star elements create twinkling particles using CSS keyframes for subtle motion.

The result is a futuristic card design with elegant motion and lighting effects.

Javascipt Code


The JavaScript adds all the interactivity

It dynamically fills the text using:

1const BASE_TEXT_CONTENT = "Programer Devloper"; 2const REVEAL_TEXT_CONTENT = "CodewithLord Devloper";

It generates 80 stars at random positions for the animated background.

Mouse and touch events (mousemove, touchmove) calculate how far the pointer has moved across the container and adjust:

clip-path: inset(0 ${rightInset}% 0 0) This determines how much of the text-reveal layer is visible.

The divider follows this movement, rotating slightly and fading in/out for smooth visual feedback.

When the user leaves the area or lifts their finger, the reveal resets.

1 2<script> 3 // CONFIG: change these to set the displayed strings 4 const BASE_TEXT_CONTENT = "Programer Devloper"; 5 const REVEAL_TEXT_CONTENT = "CodewithLord Devloper"; 6 7 // DOM references 8 const card = document.getElementById("card"); 9 const container = document.getElementById("textContainer"); 10 const baseText = document.getElementById("baseText"); 11 const revealText = document.getElementById("revealText"); 12 const divider = document.getElementById("divider"); 13 const stars = document.getElementById("stars"); 14 15 // set texts from config 16 baseText.textContent = BASE_TEXT_CONTENT; 17 revealText.textContent = REVEAL_TEXT_CONTENT; 18 19 // Create stars (like original ~80) 20 for (let i = 0; i < 80; i++) { 21 const s = document.createElement("span"); 22 s.className = "star"; 23 // random placement + slight pixel offset for natural look 24 s.style.left = Math.random() * 100 + "%"; 25 s.style.top = Math.random() * 100 + "%"; 26 const dur = 15 + Math.random() * 25; // random duration 27 s.style.animationDuration = dur + "s"; 28 s.style.opacity = Math.random() * 0.9; 29 // small transform offset so animation appears to move a little 30 s.style.transform = `translate(${(Math.random()*4-2).toFixed(2)}px, ${(Math.random()*4-2).toFixed(2)}px)`; 31 stars.appendChild(s); 32 } 33 34 let isActive = false; // whether pointer/touch is active inside 35 let rectLeft = 0; 36 let rectWidth = 0; 37 38 function refreshRect() { 39 const rect = container.getBoundingClientRect(); 40 rectLeft = rect.left; 41 rectWidth = rect.width; 42 } 43 44 // Calculate percent (0..100) then apply via clip-path on reveal text 45 function applyPercentFromClientX(clientX) { 46 refreshRect(); 47 let rel = clientX - rectLeft; 48 // clamp 49 if (rel < 0) rel = 0; 50 if (rel > rectWidth) rel = rectWidth; 51 const pct = (rel / rectWidth) * 100; 52 53 // clip-path inset: top right bottom left 54 // we want to show the left portion up to 'pct' => hide the remainder (right side) 55 const rightInset = (100 - pct).toFixed(2) + "%"; 56 revealText.style.clipPath = `inset(0 ${rightInset} 0 0)`; 57 revealText.style.webkitClipPath = `inset(0 ${rightInset} 0 0)`; 58 59 // place divider at the boundary 60 divider.style.left = pct + "%"; 61 // rotate slightly based on pct (replicates rotateDeg) 62 const rotateDeg = (pct - 50) * 0.1; // same formula as original 63 divider.style.transform = `rotate(${rotateDeg}deg)`; 64 divider.style.opacity = pct > 0 ? "1" : "0"; 65 } 66 67 // Mouse events 68 container.addEventListener("mouseenter", (e) => { 69 isActive = true; 70 refreshRect(); 71 }); 72 73 container.addEventListener("mousemove", (e) => { 74 if (!isActive) return; 75 applyPercentFromClientX(e.clientX); 76 }); 77 78 container.addEventListener("mouseleave", () => { 79 isActive = false; 80 // hide reveal smoothly 81 revealText.style.clipPath = "inset(0 100% 0 0)"; 82 revealText.style.webkitClipPath = "inset(0 100% 0 0)"; 83 divider.style.opacity = 0; 84 }); 85 86 // Touch events (mobile) 87 container.addEventListener("touchstart", (e) => { 88 isActive = true; 89 refreshRect(); 90 // prevent native scrolling while interacting 91 e.preventDefault(); 92 applyPercentFromClientX(e.touches[0].clientX); 93 }, {passive:false}); 94 95 container.addEventListener("touchmove", (e) => { 96 if (!isActive) return; 97 applyPercentFromClientX(e.touches[0].clientX); 98 e.preventDefault(); 99 }, {passive:false}); 100 101 container.addEventListener("touchend", () => { 102 isActive = false; 103 revealText.style.clipPath = "inset(0 100% 0 0)"; 104 revealText.style.webkitClipPath = "inset(0 100% 0 0)"; 105 divider.style.opacity = 0; 106 }); 107 108 // Recompute positions on resize 109 window.addEventListener("resize", refreshRect); 110 111 // initialize rect 112 refreshRect(); 113 114 // OPTIONAL: If you want reveal to show a little when tapping quickly, you can 115 // keep it visible for a short timeout on touchend — currently it hides immediately. 116 </script>

Love this component?

Explore more components and build amazing UIs.

View All Components