Let’s create a Cards Beam Animation using HTML, CSS, and JavaScript. This project will feature a smooth and futuristic effect where cards animate with a glowing beam transition, making your design look dynamic and eye-catching.
We’ll use:
- HTML to build the card structure.
- CSS to style the cards and add glowing beam effects.
- JavaScript to trigger and control the animations.
This is a fun and creative project that will help you learn how to combine styling with interactive animations. Perfect for showcasing portfolios, product cards, or just adding some wow factor to your web projects! ⚡🃏
HTML :
This HTML code creates an interactive web page that displays a card beam animation with control buttons. It links a CSS file for styling and a JavaScript file for animation logic. The three buttons let you pause/resume, reset the animation, and change the card movement direction. There’s also a speed indicator showing how fast the cards move. Inside the container, two canvas elements are used for particle and scanner effects, and a card stream section shows the animated cards. External Three.js is included to handle advanced graphics.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Cards Beam Animation | @coding.stella</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="controls"> <button class="control-btn" onclick="toggleAnimation()">⏸️ Pause</button> <button class="control-btn" onclick="resetPosition()">🔄 Reset</button> <button class="control-btn" onclick="changeDirection()">↔️ Direction</button> </div> <div class="speed-indicator"> Speed: <span id="speedValue">120</span> px/s </div> <div class="container"> <canvas id="particleCanvas"></canvas> <canvas id="scannerCanvas"></canvas> <div class="scanner"></div> <div class="card-stream" id="cardStream"> <div class="card-line" id="cardLine"></div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="script.js"></script> </body> </html>
CSS :
This CSS styles the card beam animation UI. It sets a dark background and positions the controls, speed indicator, and info neatly on the screen with glassy blurred buttons. The main container centers everything, while the card stream scrolls horizontally with smooth animations. Each card is styled like a glowing credit card with chips, logos, numbers, and even an ASCII glitch overlay for a futuristic effect. Animations such as scrollCards
, glitch
, scanPulse
, and scanEffect
bring motion – cards slide, ASCII text flickers, and a glowing cyan scanner line pulses across. Extra canvases (particleCanvas
, scannerCanvas
) allow visual effects in the background, giving the whole project a sci-fi holographic look.
@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&display=swap"); * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000000; min-height: 100vh; overflow: hidden; font-family: "Arial", sans-serif; } .controls { position: absolute; top: 20px; left: 20px; display: flex; gap: 10px; z-index: 100; } .control-btn { padding: 10px 20px; background: rgba(255, 255, 255, 0.2); border: none; border-radius: 25px; color: white; font-weight: bold; cursor: pointer; backdrop-filter: blur(5px); transition: all 0.3s ease; font-size: 14px; } .control-btn:hover { background: rgba(255, 255, 255, 0.3); transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); } .speed-indicator { position: absolute; top: 20px; right: 20px; color: white; font-size: 16px; background: rgba(0, 0, 0, 0.3); padding: 8px 16px; border-radius: 20px; backdrop-filter: blur(5px); z-index: 100; } .info { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: rgba(255, 255, 255, 0.9); text-align: center; font-size: 14px; background: rgba(0, 0, 0, 0.3); padding: 15px 25px; border-radius: 20px; backdrop-filter: blur(5px); z-index: 100; line-height: 1.4; } .container { position: relative; width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; } .card-stream { position: absolute; width: 100vw; height: 180px; display: flex; align-items: center; overflow: visible; } .card-line { display: flex; align-items: center; gap: 60px; white-space: nowrap; cursor: grab; user-select: none; will-change: transform; } .card-line:active { cursor: grabbing; } .card-line.dragging { cursor: grabbing; } .card-line.css-animated { animation: scrollCards 40s linear infinite; } @keyframes scrollCards { 0% { transform: translateX(-100%); } 100% { transform: translateX(100vw); } } .card-wrapper { position: relative; width: 400px; height: 250px; flex-shrink: 0; } .card { position: absolute; top: 0; left: 0; width: 400px; height: 250px; border-radius: 15px; overflow: hidden; } .card-normal { background: transparent; box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4); display: flex; flex-direction: column; justify-content: space-between; padding: 0; color: white; z-index: 2; position: relative; overflow: hidden; } .card-image { width: 100%; height: 100%; object-fit: cover; border-radius: 15px; transition: all 0.3s ease; filter: brightness(1.1) contrast(1.1); box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.1); } .card-image:hover { filter: brightness(1.2) contrast(1.2); } .card-ascii { background: transparent; z-index: 1; position: absolute; top: 0; left: 0; width: 400px; height: 250px; border-radius: 15px; overflow: hidden; } .card-chip { width: 40px; height: 30px; background: linear-gradient(45deg, #ffd700, #ffed4e); border-radius: 5px; position: relative; margin-bottom: 20px; } .card-chip::before { content: ""; position: absolute; top: 3px; left: 3px; right: 3px; bottom: 3px; background: linear-gradient(45deg, #e6c200, #f4d03f); border-radius: 2px; } .contactless { position: absolute; top: 60px; left: 20px; width: 25px; height: 25px; border: 2px solid rgba(255, 255, 255, 0.8); border-radius: 50%; background: radial-gradient(circle, rgba(255, 255, 255, 0.2), transparent); } .contactless::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 15px; height: 15px; border: 1px solid rgba(255, 255, 255, 0.6); border-radius: 50%; } .card-number { font-size: 22px; font-weight: bold; letter-spacing: 3px; margin-bottom: 15px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .card-info { display: flex; justify-content: space-between; align-items: flex-end; } .card-holder { color: white; font-size: 14px; text-transform: uppercase; } .card-expiry { color: white; font-size: 14px; } .card-logo { position: absolute; top: 20px; right: 20px; font-size: 18px; font-weight: bold; color: white; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .ascii-content { position: absolute; top: 0; left: 0; width: 100%; height: 100%; color: rgba(220, 210, 255, 0.6); font-family: "Courier New", monospace; font-size: 11px; line-height: 13px; overflow: hidden; white-space: pre; clip-path: inset(0 calc(100% - var(--clip-left, 0%)) 0 0); animation: glitch 0.1s infinite linear alternate-reverse; margin: 0; padding: 0; text-align: left; vertical-align: top; box-sizing: border-box; -webkit-mask-image: linear-gradient( to right, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.8) 30%, rgba(0, 0, 0, 0.6) 50%, rgba(0, 0, 0, 0.4) 80%, rgba(0, 0, 0, 0.2) 100% ); mask-image: linear-gradient( to right, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.8) 30%, rgba(0, 0, 0, 0.6) 50%, rgba(0, 0, 0, 0.4) 80%, rgba(0, 0, 0, 0.2) 100% ); } @keyframes glitch { 0% { opacity: 1; } 15% { opacity: 0.9; } 16% { opacity: 1; } 49% { opacity: 0.8; } 50% { opacity: 1; } 99% { opacity: 0.9; } 100% { opacity: 1; } } .scanner { display: none; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 4px; height: 300px; border-radius: 30px; background: linear-gradient( to bottom, transparent, rgba(0, 255, 255, 0.8), rgba(0, 255, 255, 1), rgba(0, 255, 255, 0.8), transparent ); box-shadow: 0 0 20px rgba(0, 255, 255, 0.8), 0 0 40px rgba(0, 255, 255, 0.4); animation: scanPulse 2s ease-in-out infinite alternate; z-index: 10; } @keyframes scanPulse { 0% { opacity: 0.8; transform: translate(-50%, -50%) scaleY(1); } 100% { opacity: 1; transform: translate(-50%, -50%) scaleY(1.1); } } .scanner-label { position: absolute; bottom: -40px; left: 50%; transform: translateX(-50%); color: rgba(0, 255, 255, 0.9); font-size: 12px; font-weight: bold; text-transform: uppercase; letter-spacing: 2px; text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); } .card-normal { clip-path: inset(0 0 0 var(--clip-right, 0%)); } .card-ascii { clip-path: inset(0 calc(100% - var(--clip-left, 0%)) 0 0); } .scan-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient( 90deg, transparent, rgba(0, 255, 255, 0.4), transparent ); animation: scanEffect 0.6s ease-out; pointer-events: none; z-index: 5; } @keyframes scanEffect { 0% { transform: translateX(-100%); opacity: 0; } 50% { opacity: 1; } 100% { transform: translateX(100%); opacity: 0; } } .instructions { position: absolute; top: 50%; right: 30px; transform: translateY(-50%); color: rgba(255, 255, 255, 0.7); font-size: 14px; max-width: 200px; text-align: right; z-index: 5; } #particleCanvas { position: absolute; top: 50%; left: 0; transform: translateY(-50%); width: 100vw; height: 250px; z-index: 0; pointer-events: none; } #scannerCanvas { position: absolute; top: 50%; left: -3px; transform: translateY(-50%); width: 100vw; height: 300px; z-index: 15; pointer-events: none; }
JavaScript:
This JavaScript builds the logic behind the card beam animation. It defines a CardStreamController
class that handles the scrolling cards: setting their speed, direction, and position, and letting users pause, reset, drag, or scroll them. It keeps cards looping across the screen and shows the current speed. Each card has two layers—a normal card image and an ASCII-style code version – and as the glowing scanner line passes through, it clips and reveals the ASCII part to create a scanning effect. The code also supports mouse drag, touch, and wheel scrolling, with realistic friction so cards glide smoothly after dragging. In short, it makes the cards move endlessly and interactively while syncing the scanner beam effect.
const codeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]<>;:,._-+=!@#$%^&*|\\/\"'`~?"; const scannerLeft = window.innerWidth / 2 - 2; const scannerRight = window.innerWidth / 2 + 2; class CardStreamController { constructor() { this.container = document.getElementById("cardStream"); this.cardLine = document.getElementById("cardLine"); this.speedIndicator = document.getElementById("speedValue"); this.position = 0; this.velocity = 120; this.direction = -1; this.isAnimating = true; this.isDragging = false; this.lastTime = 0; this.lastMouseX = 0; this.mouseVelocity = 0; this.friction = 0.95; this.minVelocity = 30; this.containerWidth = 0; this.cardLineWidth = 0; this.init(); } init() { this.populateCardLine(); this.calculateDimensions(); this.setupEventListeners(); this.updateCardPosition(); this.animate(); this.startPeriodicUpdates(); } calculateDimensions() { this.containerWidth = this.container.offsetWidth; const cardWidth = 400; const cardGap = 60; const cardCount = this.cardLine.children.length; this.cardLineWidth = (cardWidth + cardGap) * cardCount; } setupEventListeners() { this.cardLine.addEventListener("mousedown", (e) => this.startDrag(e)); document.addEventListener("mousemove", (e) => this.onDrag(e)); document.addEventListener("mouseup", () => this.endDrag()); this.cardLine.addEventListener( "touchstart", (e) => this.startDrag(e.touches[0]), { passive: false } ); document.addEventListener("touchmove", (e) => this.onDrag(e.touches[0]), { passive: false, }); document.addEventListener("touchend", () => this.endDrag()); this.cardLine.addEventListener("wheel", (e) => this.onWheel(e)); this.cardLine.addEventListener("selectstart", (e) => e.preventDefault()); this.cardLine.addEventListener("dragstart", (e) => e.preventDefault()); window.addEventListener("resize", () => this.calculateDimensions()); } startDrag(e) { e.preventDefault(); this.isDragging = true; this.isAnimating = false; this.lastMouseX = e.clientX; this.mouseVelocity = 0; const transform = window.getComputedStyle(this.cardLine).transform; if (transform !== "none") { const matrix = new DOMMatrix(transform); this.position = matrix.m41; } this.cardLine.style.animation = "none"; this.cardLine.classList.add("dragging"); document.body.style.userSelect = "none"; document.body.style.cursor = "grabbing"; } onDrag(e) { if (!this.isDragging) return; e.preventDefault(); const deltaX = e.clientX - this.lastMouseX; this.position += deltaX; this.mouseVelocity = deltaX * 60; this.lastMouseX = e.clientX; this.cardLine.style.transform = `translateX(${this.position}px)`; this.updateCardClipping(); } endDrag() { if (!this.isDragging) return; this.isDragging = false; this.cardLine.classList.remove("dragging"); if (Math.abs(this.mouseVelocity) > this.minVelocity) { this.velocity = Math.abs(this.mouseVelocity); this.direction = this.mouseVelocity > 0 ? 1 : -1; } else { this.velocity = 120; } this.isAnimating = true; this.updateSpeedIndicator(); document.body.style.userSelect = ""; document.body.style.cursor = ""; } animate() { const currentTime = performance.now(); const deltaTime = (currentTime - this.lastTime) / 1000; this.lastTime = currentTime; if (this.isAnimating && !this.isDragging) { if (this.velocity > this.minVelocity) { this.velocity *= this.friction; } else { this.velocity = Math.max(this.minVelocity, this.velocity); } this.position += this.velocity * this.direction * deltaTime; this.updateCardPosition(); this.updateSpeedIndicator(); } requestAnimationFrame(() => this.animate()); } updateCardPosition() { const containerWidth = this.containerWidth; const cardLineWidth = this.cardLineWidth; if (this.position < -cardLineWidth) { this.position = containerWidth; } else if (this.position > containerWidth) { this.position = -cardLineWidth; } this.cardLine.style.transform = `translateX(${this.position}px)`; this.updateCardClipping(); } updateSpeedIndicator() { this.speedIndicator.textContent = Math.round(this.velocity); } toggleAnimation() { this.isAnimating = !this.isAnimating; const btn = document.querySelector(".control-btn"); btn.textContent = this.isAnimating ? "⏸️ Pause" : "▶️ Play"; if (this.isAnimating) { this.cardLine.style.animation = "none"; } } resetPosition() { this.position = this.containerWidth; this.velocity = 120; this.direction = -1; this.isAnimating = true; this.isDragging = false; this.cardLine.style.animation = "none"; this.cardLine.style.transform = `translateX(${this.position}px)`; this.cardLine.classList.remove("dragging"); this.updateSpeedIndicator(); const btn = document.querySelector(".control-btn"); btn.textContent = "⏸️ Pause"; } changeDirection() { this.direction *= -1; this.updateSpeedIndicator(); } onWheel(e) { e.preventDefault(); const scrollSpeed = 20; const delta = e.deltaY > 0 ? scrollSpeed : -scrollSpeed; this.position += delta; this.updateCardPosition(); this.updateCardClipping(); } generateCode(width, height) { const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; const pick = (arr) => arr[randInt(0, arr.length - 1)]; const header = [ "// compiled preview • scanner demo", "/* generated for visual effect – not executed */", "const SCAN_WIDTH = 8;", "const FADE_ZONE = 35;", "const MAX_PARTICLES = 2500;", "const TRANSITION = 0.05;", ]; const helpers = [ "function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }", "function lerp(a, b, t) { return a + (b - a) * t; }", "const now = () => performance.now();", "function rng(min, max) { return Math.random() * (max - min) + min; }", ]; const particleBlock = (idx) => [ `class Particle${idx} {`, " constructor(x, y, vx, vy, r, a) {", " this.x = x; this.y = y;", " this.vx = vx; this.vy = vy;", " this.r = r; this.a = a;", " }", " step(dt) { this.x += this.vx * dt; this.y += this.vy * dt; }", "}", ]; const scannerBlock = [ "const scanner = {", " x: Math.floor(window.innerWidth / 2),", " width: SCAN_WIDTH,", " glow: 3.5,", "};", "", "function drawParticle(ctx, p) {", " ctx.globalAlpha = clamp(p.a, 0, 1);", " ctx.drawImage(gradient, p.x - p.r, p.y - p.r, p.r * 2, p.r * 2);", "}", ]; const loopBlock = [ "function tick(t) {", " // requestAnimationFrame(tick);", " const dt = 0.016;", " // update & render", "}", ]; const misc = [ "const state = { intensity: 1.2, particles: MAX_PARTICLES };", "const bounds = { w: window.innerWidth, h: 300 };", "const gradient = document.createElement('canvas');", "const ctx = gradient.getContext('2d');", "ctx.globalCompositeOperation = 'lighter';", "// ascii overlay is masked with a 3-phase gradient", ]; const library = []; header.forEach((l) => library.push(l)); helpers.forEach((l) => library.push(l)); for (let b = 0; b < 3; b++) particleBlock(b).forEach((l) => library.push(l)); scannerBlock.forEach((l) => library.push(l)); loopBlock.forEach((l) => library.push(l)); misc.forEach((l) => library.push(l)); for (let i = 0; i < 40; i++) { const n1 = randInt(1, 9); const n2 = randInt(10, 99); library.push(`const v${i} = (${n1} + ${n2}) * 0.${randInt(1, 9)};`); } for (let i = 0; i < 20; i++) { library.push( `if (state.intensity > ${1 + (i % 3)}) { scanner.glow += 0.01; }` ); } let flow = library.join(" "); flow = flow.replace(/\s+/g, " ").trim(); const totalChars = width * height; while (flow.length < totalChars + width) { const extra = pick(library).replace(/\s+/g, " ").trim(); flow += " " + extra; } let out = ""; let offset = 0; for (let row = 0; row < height; row++) { let line = flow.slice(offset, offset + width); if (line.length < width) line = line + " ".repeat(width - line.length); out += line + (row < height - 1 ? "\n" : ""); offset += width; } return out; } calculateCodeDimensions(cardWidth, cardHeight) { const fontSize = 11; const lineHeight = 13; const charWidth = 6; const width = Math.floor(cardWidth / charWidth); const height = Math.floor(cardHeight / lineHeight); return { width, height, fontSize, lineHeight }; } createCardWrapper(index) { const wrapper = document.createElement("div"); wrapper.className = "card-wrapper"; const normalCard = document.createElement("div"); normalCard.className = "card card-normal"; const cardImages = [ "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b55e654d1341fb06f8_4.1.png", "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5a080a31ee7154b19_1.png", "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5c1e4919fd69672b8_3.png", "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5f6a5e232e7beb4be_2.png", "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5bea2f1b07392d936_4.png", ]; const cardImage = document.createElement("img"); cardImage.className = "card-image"; cardImage.src = cardImages[index % cardImages.length]; cardImage.alt = "Credit Card"; cardImage.onerror = () => { const canvas = document.createElement("canvas"); canvas.width = 400; canvas.height = 250; const ctx = canvas.getContext("2d"); const gradient = ctx.createLinearGradient(0, 0, 400, 250); gradient.addColorStop(0, "#667eea"); gradient.addColorStop(1, "#764ba2"); ctx.fillStyle = gradient; ctx.fillRect(0, 0, 400, 250); cardImage.src = canvas.toDataURL(); }; normalCard.appendChild(cardImage); const asciiCard = document.createElement("div"); asciiCard.className = "card card-ascii"; const asciiContent = document.createElement("div"); asciiContent.className = "ascii-content"; const { width, height, fontSize, lineHeight } = this.calculateCodeDimensions(400, 250); asciiContent.style.fontSize = fontSize + "px"; asciiContent.style.lineHeight = lineHeight + "px"; asciiContent.textContent = this.generateCode(width, height); asciiCard.appendChild(asciiContent); wrapper.appendChild(normalCard); wrapper.appendChild(asciiCard); return wrapper; } updateCardClipping() { const scannerX = window.innerWidth / 2; const scannerWidth = 8; const scannerLeft = scannerX - scannerWidth / 2; const scannerRight = scannerX + scannerWidth / 2; let anyScanningActive = false; document.querySelectorAll(".card-wrapper").forEach((wrapper) => { const rect = wrapper.getBoundingClientRect(); const cardLeft = rect.left; const cardRight = rect.right; const cardWidth = rect.width; const normalCard = wrapper.querySelector(".card-normal"); const asciiCard = wrapper.querySelector(".card-ascii"); if (cardLeft < scannerRight && cardRight > scannerLeft) { anyScanningActive = true; const scannerIntersectLeft = Math.max(scannerLeft - cardLeft, 0); const scannerIntersectRight = Math.min( scannerRight - cardLeft, cardWidth ); const normalClipRight = (scannerIntersectLeft / cardWidth) * 100; const asciiClipLeft = (scannerIntersectRight / cardWidth) * 100; normalCard.style.setProperty("--clip-right", `${normalClipRight}%`); asciiCard.style.setProperty("--clip-left", `${asciiClipLeft}%`); if (!wrapper.hasAttribute("data-scanned") && scannerIntersectLeft > 0) { wrapper.setAttribute("data-scanned", "true"); const scanEffect = document.createElement("div"); scanEffect.className = "scan-effect"; wrapper.appendChild(scanEffect); setTimeout(() => { if (scanEffect.parentNode) { scanEffect.parentNode.removeChild(scanEffect); } }, 600); } } else { if (cardRight < scannerLeft) { normalCard.style.setProperty("--clip-right", "100%"); asciiCard.style.setProperty("--clip-left", "100%"); } else if (cardLeft > scannerRight) { normalCard.style.setProperty("--clip-right", "0%"); asciiCard.style.setProperty("--clip-left", "0%"); } wrapper.removeAttribute("data-scanned"); } }); if (window.setScannerScanning) { window.setScannerScanning(anyScanningActive); } } updateAsciiContent() { document.querySelectorAll(".ascii-content").forEach((content) => { if (Math.random() < 0.15) { const { width, height } = this.calculateCodeDimensions(400, 250); content.textContent = this.generateCode(width, height); } }); } populateCardLine() { this.cardLine.innerHTML = ""; const cardsCount = 30; for (let i = 0; i < cardsCount; i++) { const cardWrapper = this.createCardWrapper(i); this.cardLine.appendChild(cardWrapper); } } startPeriodicUpdates() { setInterval(() => { this.updateAsciiContent(); }, 200); const updateClipping = () => { this.updateCardClipping(); requestAnimationFrame(updateClipping); }; updateClipping(); } } let cardStream; function toggleAnimation() { if (cardStream) { cardStream.toggleAnimation(); } } function resetPosition() { if (cardStream) { cardStream.resetPosition(); } } function changeDirection() { if (cardStream) { cardStream.changeDirection(); } } class ParticleSystem { constructor() { this.scene = null; this.camera = null; this.renderer = null; this.particles = null; this.particleCount = 400; this.canvas = document.getElementById("particleCanvas"); this.init(); } init() { this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera( -window.innerWidth / 2, window.innerWidth / 2, 125, -125, 1, 1000 ); this.camera.position.z = 100; this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas, alpha: true, antialias: true, }); this.renderer.setSize(window.innerWidth, 250); this.renderer.setClearColor(0x000000, 0); this.createParticles(); this.animate(); window.addEventListener("resize", () => this.onWindowResize()); } createParticles() { const geometry = new THREE.BufferGeometry(); const positions = new Float32Array(this.particleCount * 3); const colors = new Float32Array(this.particleCount * 3); const sizes = new Float32Array(this.particleCount); const velocities = new Float32Array(this.particleCount); const canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 100; const ctx = canvas.getContext("2d"); const half = canvas.width / 2; const hue = 217; const gradient = ctx.createRadialGradient(half, half, 0, half, half, half); gradient.addColorStop(0.025, "#fff"); gradient.addColorStop(0.1, `hsl(${hue}, 61%, 33%)`); gradient.addColorStop(0.25, `hsl(${hue}, 64%, 6%)`); gradient.addColorStop(1, "transparent"); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(half, half, half, 0, Math.PI * 2); ctx.fill(); const texture = new THREE.CanvasTexture(canvas); for (let i = 0; i < this.particleCount; i++) { positions[i * 3] = (Math.random() - 0.5) * window.innerWidth * 2; positions[i * 3 + 1] = (Math.random() - 0.5) * 250; positions[i * 3 + 2] = 0; colors[i * 3] = 1; colors[i * 3 + 1] = 1; colors[i * 3 + 2] = 1; const orbitRadius = Math.random() * 200 + 100; sizes[i] = (Math.random() * (orbitRadius - 60) + 60) / 8; velocities[i] = Math.random() * 60 + 30; } geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3)); geometry.setAttribute("size", new THREE.BufferAttribute(sizes, 1)); this.velocities = velocities; const alphas = new Float32Array(this.particleCount); for (let i = 0; i < this.particleCount; i++) { alphas[i] = (Math.random() * 8 + 2) / 10; } geometry.setAttribute("alpha", new THREE.BufferAttribute(alphas, 1)); this.alphas = alphas; const material = new THREE.ShaderMaterial({ uniforms: { pointTexture: { value: texture }, size: { value: 15.0 }, }, vertexShader: ` attribute float alpha; varying float vAlpha; varying vec3 vColor; uniform float size; void main() { vAlpha = alpha; vColor = color; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_PointSize = size; gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` uniform sampler2D pointTexture; varying float vAlpha; varying vec3 vColor; void main() { gl_FragColor = vec4(vColor, vAlpha) * texture2D(pointTexture, gl_PointCoord); } `, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, vertexColors: true, }); this.particles = new THREE.Points(geometry, material); this.scene.add(this.particles); } animate() { requestAnimationFrame(() => this.animate()); if (this.particles) { const positions = this.particles.geometry.attributes.position.array; const alphas = this.particles.geometry.attributes.alpha.array; const time = Date.now() * 0.001; for (let i = 0; i < this.particleCount; i++) { positions[i * 3] += this.velocities[i] * 0.016; if (positions[i * 3] > window.innerWidth / 2 + 100) { positions[i * 3] = -window.innerWidth / 2 - 100; positions[i * 3 + 1] = (Math.random() - 0.5) * 250; } positions[i * 3 + 1] += Math.sin(time + i * 0.1) * 0.5; const twinkle = Math.floor(Math.random() * 10); if (twinkle === 1 && alphas[i] > 0) { alphas[i] -= 0.05; } else if (twinkle === 2 && alphas[i] < 1) { alphas[i] += 0.05; } alphas[i] = Math.max(0, Math.min(1, alphas[i])); } this.particles.geometry.attributes.position.needsUpdate = true; this.particles.geometry.attributes.alpha.needsUpdate = true; } this.renderer.render(this.scene, this.camera); } onWindowResize() { this.camera.left = -window.innerWidth / 2; this.camera.right = window.innerWidth / 2; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, 250); } destroy() { if (this.renderer) { this.renderer.dispose(); } if (this.particles) { this.scene.remove(this.particles); this.particles.geometry.dispose(); this.particles.material.dispose(); } } } let particleSystem; class ParticleScanner { constructor() { this.canvas = document.getElementById("scannerCanvas"); this.ctx = this.canvas.getContext("2d"); this.animationId = null; this.w = window.innerWidth; this.h = 300; this.particles = []; this.count = 0; this.maxParticles = 800; this.intensity = 0.8; this.lightBarX = this.w / 2; this.lightBarWidth = 3; this.fadeZone = 60; this.scanTargetIntensity = 1.8; this.scanTargetParticles = 2500; this.scanTargetFadeZone = 35; this.scanningActive = false; this.baseIntensity = this.intensity; this.baseMaxParticles = this.maxParticles; this.baseFadeZone = this.fadeZone; this.currentIntensity = this.intensity; this.currentMaxParticles = this.maxParticles; this.currentFadeZone = this.fadeZone; this.transitionSpeed = 0.05; this.setupCanvas(); this.createGradientCache(); this.initParticles(); this.animate(); window.addEventListener("resize", () => this.onResize()); } setupCanvas() { this.canvas.width = this.w; this.canvas.height = this.h; this.canvas.style.width = this.w + "px"; this.canvas.style.height = this.h + "px"; this.ctx.clearRect(0, 0, this.w, this.h); } onResize() { this.w = window.innerWidth; this.lightBarX = this.w / 2; this.setupCanvas(); } createGradientCache() { this.gradientCanvas = document.createElement("canvas"); this.gradientCtx = this.gradientCanvas.getContext("2d"); this.gradientCanvas.width = 16; this.gradientCanvas.height = 16; const half = this.gradientCanvas.width / 2; const gradient = this.gradientCtx.createRadialGradient( half, half, 0, half, half, half ); gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); gradient.addColorStop(0.3, "rgba(196, 181, 253, 0.8)"); gradient.addColorStop(0.7, "rgba(139, 92, 246, 0.4)"); gradient.addColorStop(1, "transparent"); this.gradientCtx.fillStyle = gradient; this.gradientCtx.beginPath(); this.gradientCtx.arc(half, half, half, 0, Math.PI * 2); this.gradientCtx.fill(); } random(min, max) { if (arguments.length < 2) { max = min; min = 0; } return Math.floor(Math.random() * (max - min + 1)) + min; } randomFloat(min, max) { return Math.random() * (max - min) + min; } createParticle() { const intensityRatio = this.intensity / this.baseIntensity; const speedMultiplier = 1 + (intensityRatio - 1) * 1.2; const sizeMultiplier = 1 + (intensityRatio - 1) * 0.7; return { x: this.lightBarX + this.randomFloat(-this.lightBarWidth / 2, this.lightBarWidth / 2), y: this.randomFloat(0, this.h), vx: this.randomFloat(0.2, 1.0) * speedMultiplier, vy: this.randomFloat(-0.15, 0.15) * speedMultiplier, radius: this.randomFloat(0.4, 1) * sizeMultiplier, alpha: this.randomFloat(0.6, 1), decay: this.randomFloat(0.005, 0.025) * (2 - intensityRatio * 0.5), originalAlpha: 0, life: 1.0, time: 0, startX: 0, twinkleSpeed: this.randomFloat(0.02, 0.08) * speedMultiplier, twinkleAmount: this.randomFloat(0.1, 0.25), }; } initParticles() { for (let i = 0; i < this.maxParticles; i++) { const particle = this.createParticle(); particle.originalAlpha = particle.alpha; particle.startX = particle.x; this.count++; this.particles[this.count] = particle; } } updateParticle(particle) { particle.x += particle.vx; particle.y += particle.vy; particle.time++; particle.alpha = particle.originalAlpha * particle.life + Math.sin(particle.time * particle.twinkleSpeed) * particle.twinkleAmount; particle.life -= particle.decay; if (particle.x > this.w + 10 || particle.life <= 0) { this.resetParticle(particle); } } resetParticle(particle) { particle.x = this.lightBarX + this.randomFloat(-this.lightBarWidth / 2, this.lightBarWidth / 2); particle.y = this.randomFloat(0, this.h); particle.vx = this.randomFloat(0.2, 1.0); particle.vy = this.randomFloat(-0.15, 0.15); particle.alpha = this.randomFloat(0.6, 1); particle.originalAlpha = particle.alpha; particle.life = 1.0; particle.time = 0; particle.startX = particle.x; } drawParticle(particle) { if (particle.life <= 0) return; let fadeAlpha = 1; if (particle.y < this.fadeZone) { fadeAlpha = particle.y / this.fadeZone; } else if (particle.y > this.h - this.fadeZone) { fadeAlpha = (this.h - particle.y) / this.fadeZone; } fadeAlpha = Math.max(0, Math.min(1, fadeAlpha)); this.ctx.globalAlpha = particle.alpha * fadeAlpha; this.ctx.drawImage( this.gradientCanvas, particle.x - particle.radius, particle.y - particle.radius, particle.radius * 2, particle.radius * 2 ); } drawLightBar() { const verticalGradient = this.ctx.createLinearGradient(0, 0, 0, this.h); verticalGradient.addColorStop(0, "rgba(255, 255, 255, 0)"); verticalGradient.addColorStop( this.fadeZone / this.h, "rgba(255, 255, 255, 1)" ); verticalGradient.addColorStop( 1 - this.fadeZone / this.h, "rgba(255, 255, 255, 1)" ); verticalGradient.addColorStop(1, "rgba(255, 255, 255, 0)"); this.ctx.globalCompositeOperation = "lighter"; const targetGlowIntensity = this.scanningActive ? 3.5 : 1; if (!this.currentGlowIntensity) this.currentGlowIntensity = 1; this.currentGlowIntensity += (targetGlowIntensity - this.currentGlowIntensity) * this.transitionSpeed; const glowIntensity = this.currentGlowIntensity; const lineWidth = this.lightBarWidth; const glow1Alpha = this.scanningActive ? 1.0 : 0.8; const glow2Alpha = this.scanningActive ? 0.8 : 0.6; const glow3Alpha = this.scanningActive ? 0.6 : 0.4; const coreGradient = this.ctx.createLinearGradient( this.lightBarX - lineWidth / 2, 0, this.lightBarX + lineWidth / 2, 0 ); coreGradient.addColorStop(0, "rgba(255, 255, 255, 0)"); coreGradient.addColorStop( 0.3, `rgba(255, 255, 255, ${0.9 * glowIntensity})` ); coreGradient.addColorStop(0.5, `rgba(255, 255, 255, ${1 * glowIntensity})`); coreGradient.addColorStop( 0.7, `rgba(255, 255, 255, ${0.9 * glowIntensity})` ); coreGradient.addColorStop(1, "rgba(255, 255, 255, 0)"); this.ctx.globalAlpha = 1; this.ctx.fillStyle = coreGradient; const radius = 15; this.ctx.beginPath(); this.ctx.roundRect( this.lightBarX - lineWidth / 2, 0, lineWidth, this.h, radius ); this.ctx.fill(); const glow1Gradient = this.ctx.createLinearGradient( this.lightBarX - lineWidth * 2, 0, this.lightBarX + lineWidth * 2, 0 ); glow1Gradient.addColorStop(0, "rgba(139, 92, 246, 0)"); glow1Gradient.addColorStop( 0.5, `rgba(196, 181, 253, ${0.8 * glowIntensity})` ); glow1Gradient.addColorStop(1, "rgba(139, 92, 246, 0)"); this.ctx.globalAlpha = glow1Alpha; this.ctx.fillStyle = glow1Gradient; const glow1Radius = 25; this.ctx.beginPath(); this.ctx.roundRect( this.lightBarX - lineWidth * 2, 0, lineWidth * 4, this.h, glow1Radius ); this.ctx.fill(); const glow2Gradient = this.ctx.createLinearGradient( this.lightBarX - lineWidth * 4, 0, this.lightBarX + lineWidth * 4, 0 ); glow2Gradient.addColorStop(0, "rgba(139, 92, 246, 0)"); glow2Gradient.addColorStop( 0.5, `rgba(139, 92, 246, ${0.4 * glowIntensity})` ); glow2Gradient.addColorStop(1, "rgba(139, 92, 246, 0)"); this.ctx.globalAlpha = glow2Alpha; this.ctx.fillStyle = glow2Gradient; const glow2Radius = 35; this.ctx.beginPath(); this.ctx.roundRect( this.lightBarX - lineWidth * 4, 0, lineWidth * 8, this.h, glow2Radius ); this.ctx.fill(); if (this.scanningActive) { const glow3Gradient = this.ctx.createLinearGradient( this.lightBarX - lineWidth * 8, 0, this.lightBarX + lineWidth * 8, 0 ); glow3Gradient.addColorStop(0, "rgba(139, 92, 246, 0)"); glow3Gradient.addColorStop(0.5, "rgba(139, 92, 246, 0.2)"); glow3Gradient.addColorStop(1, "rgba(139, 92, 246, 0)"); this.ctx.globalAlpha = glow3Alpha; this.ctx.fillStyle = glow3Gradient; const glow3Radius = 45; this.ctx.beginPath(); this.ctx.roundRect( this.lightBarX - lineWidth * 8, 0, lineWidth * 16, this.h, glow3Radius ); this.ctx.fill(); } this.ctx.globalCompositeOperation = "destination-in"; this.ctx.globalAlpha = 1; this.ctx.fillStyle = verticalGradient; this.ctx.fillRect(0, 0, this.w, this.h); } render() { const targetIntensity = this.scanningActive ? this.scanTargetIntensity : this.baseIntensity; const targetMaxParticles = this.scanningActive ? this.scanTargetParticles : this.baseMaxParticles; const targetFadeZone = this.scanningActive ? this.scanTargetFadeZone : this.baseFadeZone; this.currentIntensity += (targetIntensity - this.currentIntensity) * this.transitionSpeed; this.currentMaxParticles += (targetMaxParticles - this.currentMaxParticles) * this.transitionSpeed; this.currentFadeZone += (targetFadeZone - this.currentFadeZone) * this.transitionSpeed; this.intensity = this.currentIntensity; this.maxParticles = Math.floor(this.currentMaxParticles); this.fadeZone = this.currentFadeZone; this.ctx.globalCompositeOperation = "source-over"; this.ctx.clearRect(0, 0, this.w, this.h); this.drawLightBar(); this.ctx.globalCompositeOperation = "lighter"; for (let i = 1; i <= this.count; i++) { if (this.particles[i]) { this.updateParticle(this.particles[i]); this.drawParticle(this.particles[i]); } } const currentIntensity = this.intensity; const currentMaxParticles = this.maxParticles; if (Math.random() < currentIntensity && this.count < currentMaxParticles) { const particle = this.createParticle(); particle.originalAlpha = particle.alpha; particle.startX = particle.x; this.count++; this.particles[this.count] = particle; } const intensityRatio = this.intensity / this.baseIntensity; if (intensityRatio > 1.1 && Math.random() < (intensityRatio - 1.0) * 1.2) { const particle = this.createParticle(); particle.originalAlpha = particle.alpha; particle.startX = particle.x; this.count++; this.particles[this.count] = particle; } if (intensityRatio > 1.3 && Math.random() < (intensityRatio - 1.3) * 1.4) { const particle = this.createParticle(); particle.originalAlpha = particle.alpha; particle.startX = particle.x; this.count++; this.particles[this.count] = particle; } if (intensityRatio > 1.5 && Math.random() < (intensityRatio - 1.5) * 1.8) { const particle = this.createParticle(); particle.originalAlpha = particle.alpha; particle.startX = particle.x; this.count++; this.particles[this.count] = particle; } if (intensityRatio > 2.0 && Math.random() < (intensityRatio - 2.0) * 2.0) { const particle = this.createParticle(); particle.originalAlpha = particle.alpha; particle.startX = particle.x; this.count++; this.particles[this.count] = particle; } if (this.count > currentMaxParticles + 200) { const excessCount = Math.min(15, this.count - currentMaxParticles); for (let i = 0; i < excessCount; i++) { delete this.particles[this.count - i]; } this.count -= excessCount; } } animate() { this.render(); this.animationId = requestAnimationFrame(() => this.animate()); } startScanning() { this.scanningActive = true; console.log("Scanning started - intense particle mode activated"); } stopScanning() { this.scanningActive = false; console.log("Scanning stopped - normal particle mode"); } setScanningActive(active) { this.scanningActive = active; console.log("Scanning mode:", active ? "active" : "inactive"); } getStats() { return { intensity: this.intensity, maxParticles: this.maxParticles, currentParticles: this.count, lightBarWidth: this.lightBarWidth, fadeZone: this.fadeZone, scanningActive: this.scanningActive, canvasWidth: this.w, canvasHeight: this.h, }; } destroy() { if (this.animationId) { cancelAnimationFrame(this.animationId); } this.particles = []; this.count = 0; } } let particleScanner; document.addEventListener("DOMContentLoaded", () => { cardStream = new CardStreamController(); particleSystem = new ParticleSystem(); particleScanner = new ParticleScanner(); window.setScannerScanning = (active) => { if (particleScanner) { particleScanner.setScanningActive(active); } }; window.getScannerStats = () => { if (particleScanner) { return particleScanner.getStats(); } return null; }; });
In short, the Cards Beam Animation project using HTML, CSS, and JavaScript adds a cool futuristic vibe to your UI. It’s simple, stylish, and a great way to practice interactive effects while making your designs stand out. ✨
If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!