dragosp33 Posted October 11, 2025 Posted October 11, 2025 So I'm trying to create a sweep like animation, but with a circular path to use as background for the whole screen. In the example codepen I did it on a 500x500 canvas, but now I want to make it responsive. I know this is not really related to gsap but 'm not familiar with working on canvas and I thought someone could help. At first I tried just scaling it for the viewport's width and height but that resulted in blurry pluses: See the Pen PwZjgoo by vwjvvsoy-the-bashful (@vwjvvsoy-the-bashful) on CodePen. And then I tried to listen for resizes and redraw the canvas on resize, but this way the sweep doesn't go from end to end, only a little bit in the center: See the Pen emJRxeq by vwjvvsoy-the-bashful (@vwjvvsoy-the-bashful) on CodePen. What's the way to do this? See the Pen GgoEeeb by vwjvvsoy-the-bashful (@vwjvvsoy-the-bashful) on CodePen.
Rodrigo Posted October 11, 2025 Posted October 11, 2025 Hi, This demo has a resize callback in line 65: See the Pen MWJQovm by rhernando (@rhernando) on CodePen. Hopefully this helps Happy Tweening! 1
dragosp33 Posted November 5, 2025 Author Posted November 5, 2025 Hi, sorry for the late reply I solved this using a resize listener to create the grid dimensions dynamically with this resize logic: const resizeCanvas = () => { const dpr = Math.min(window.devicePixelRatio || 1, 2); const rect = c.getBoundingClientRect(); c.width = rect.width * dpr; c.height = rect.height * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); createGrid(rect.width, rect.height); // use logical (CSS) size for layout }; However using gsap ticker seemed to make it very laggy and resource eating - any tips on that? I switched to simple canvas drawing and it now runs smoothly. This is the final js logic: 'use client'; import React, { useRef, useEffect } from 'react'; class Plus { x = 0; y = 0; left = 0; top = 0; width = 0; height = 0; scale = 0; opacity = 1; draw(ctx: CanvasRenderingContext2D) { ctx.save(); ctx.translate(this.left, this.top); ctx.scale(this.scale, this.scale); ctx.globalAlpha = this.opacity; ctx.beginPath(); ctx.moveTo(0, -this.height / 2); ctx.lineTo(0, this.height / 2); ctx.moveTo(-this.width / 2, 0); ctx.lineTo(this.width / 2, 0); ctx.stroke(); ctx.restore(); } } const AnimatedPlusGrid: React.FC = () => { const canvasRef = useRef<HTMLCanvasElement | null>(null); const ctxRef = useRef<CanvasRenderingContext2D | null>(null); const signsRef = useRef<Plus[][]>([]); const gridSizeRef = useRef<{ width: number; height: number }>({ width: 0, height: 0, }); const tRef = useRef<number>(0); const animationRef = useRef<number | null>(null); useEffect(() => { const c = canvasRef.current; if (!c) return; const ctx = c.getContext('2d'); if (!ctx) return; ctxRef.current = ctx; const gridSpacing = 60; const baseSize = 10; const influenceRadius = 250; const radius = 350; const createGrid = (logicalWidth: number, logicalHeight: number) => { const signs: Plus[][] = []; const gridWidth = Math.floor(logicalWidth / gridSpacing); const gridHeight = Math.floor(logicalHeight / gridSpacing); gridSizeRef.current = { width: gridWidth, height: gridHeight }; for (let i = 0; i < gridWidth; i++) { signs[i] = []; for (let j = 0; j < gridHeight; j++) { const sign = new Plus(); sign.left = (logicalWidth / (gridWidth + 1)) * (i + 1); sign.top = (logicalHeight / (gridHeight + 1)) * (j + 1); sign.width = baseSize; sign.height = baseSize; sign.scale = 0.3; signs[i][j] = sign; } } signsRef.current = signs; }; const resizeCanvas = () => { const dpr = Math.min(window.devicePixelRatio || 1, 2); const rect = c.getBoundingClientRect(); c.width = rect.width * dpr; c.height = rect.height * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); createGrid(rect.width, rect.height); // use logical (CSS) size for layout }; const draw = () => { if (!ctxRef.current) return; const ctx = ctxRef.current; ctx.clearRect(0, 0, c.width, c.height); ctx.strokeStyle = 'grey'; ctx.lineWidth = 1.2; const { width: gw, height: gh } = gridSizeRef.current; const signs = signsRef.current; for (let i = 0; i < gw; i++) { for (let j = 0; j < gh; j++) { signs[i][j].draw(ctx); } } }; const animate = () => { tRef.current += 0.02; const t = tRef.current; const rect = c.getBoundingClientRect(); // true visible size const { width: gw, height: gh } = gridSizeRef.current; const signs = signsRef.current; const centerX = rect.width / 2; const centerY = rect.height / 2; const mouseX = centerX + Math.cos(t) * radius; const mouseY = centerY + Math.sin(t) * radius; for (let i = 0; i < gw; i++) { for (let j = 0; j < gh; j++) { const sign = signs[i][j]; const dx = mouseX - sign.left; const dy = mouseY - sign.top; const dist = Math.sqrt(dx * dx + dy * dy) || 1; const intensity = Math.max( 0, (influenceRadius - dist) / influenceRadius ); sign.scale = 0.3 + intensity * 1.7; sign.opacity = 0.3 + intensity * 0.7; } } draw(); animationRef.current = requestAnimationFrame(animate); }; resizeCanvas(); window.addEventListener('resize', resizeCanvas); animationRef.current = requestAnimationFrame(animate); return () => { window.removeEventListener('resize', resizeCanvas); if (animationRef.current) cancelAnimationFrame(animationRef.current); }; }, []); return ( <canvas ref={canvasRef} className='absolute top-0 left-0 w-full h-full pointer-events-none' /> ); }; export default AnimatedPlusGrid;
Rodrigo Posted November 5, 2025 Posted November 5, 2025 28 minutes ago, dragosp33 said: However using gsap ticker seemed to make it very laggy and resource eating - any tips on that? Mhh... without a minimal demo that illustrates the issue is hard to say honestly. Maybe you were adding the callback to the ticker over and over again? When using the request animation frame method, you have to add the callback everytime so the callback is called on the next RAF execution. GSAP's internal Ticker doesn't work like the request animation frame method, you only have to add the callback once and when you don't want it to run again, remove it. But again I'm just guessing here.
dragosp33 Posted November 5, 2025 Author Posted November 5, 2025 34 minutes ago, Rodrigo said: Mhh... without a minimal demo that illustrates the issue is hard to say honestly. Maybe you were adding the callback to the ticker over and over again? When using the request animation frame method, you have to add the callback everytime so the callback is called on the next RAF execution. GSAP's internal Ticker doesn't work like the request animation frame method, you only have to add the callback once and when you don't want it to run again, remove it. But again I'm just guessing here. I think you're right. I don't remember exactly what I did, I don't have the code right now but I tried reproducing it quickly now with the code I posted, and I noticed that there's some logic error on resizing. Here's the reproduction: stackblitz If you try to resize the window let's say 3 times in a row you can see that the canvas gets bigger every time. I think that could've also produce that laggy feeling, it wasn't visible as I had overflow hidden on my project. EDIT: I recovered an old version of the code, so if you go to /second page in the stackblitz reproduction, you can see how my animation acted. So again, I think the resize logic was bad here
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now