Skip to content

Instantly share code, notes, and snippets.

@ragingwind
Created August 31, 2025 07:23
Show Gist options
  • Save ragingwind/dc6242ed004a103da64822397dfcea6c to your computer and use it in GitHub Desktop.
Save ragingwind/dc6242ed004a103da64822397dfcea6c to your computer and use it in GitHub Desktop.
loading indicator with half-tone dot wave animation
'use client';
import { useEffect, useRef } from 'react';
export function StreamingIndicator() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Set canvas size to match the 24x24px indicator
canvas.width = 24;
canvas.height = 24;
let animationId: number;
let time = 0;
const animate = () => {
ctx.fillStyle = '#ff9729';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const maxRadius = Math.min(canvas.width, canvas.height) / 2;
// Create cleaner halftone pattern with reduced pixels
for (let x = 2; x < canvas.width; x += 4) {
for (let y = 2; y < canvas.height; y += 4) {
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
const normalizedDistance = distance / maxRadius;
// Simple breathing pattern
const breathe = Math.sin(time * 0.08) * 0.3 + 0.7;
const falloff = Math.max(0, 1 - normalizedDistance);
const size = breathe * falloff * 1.2;
if (size > 0.2) {
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
}
}
time += 1;
animationId = requestAnimationFrame(animate);
};
animate();
return () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
};
}, []);
return (
<div className="flex items-start space-x-3 mb-6">
<div className="flex-shrink-0">
<div className="w-6 h-6 bg-[#ff9729] rounded-full flex items-center justify-center relative shadow-inner overflow-hidden">
<canvas
ref={canvasRef}
className="w-full h-full"
style={{ imageRendering: 'pixelated' }}
/>
</div>
</div>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment