mirror of
https://github.com/tiennm99/gsd-framework.git
synced 2026-05-27 20:25:21 +00:00
feat(06-03): add RippleAnimation class for touch feedback
- Add RippleAnimation class with 300ms duration and 40px max radius - Add rippleAnimations property to Renderer - Add addRipple(x, y) method to trigger ripple effect - Render ripples in main render loop with auto-cleanup - Use selection color (rgba 233, 69, 96) for visual consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
import { GridManager } from '../managers/GridManager';
|
||||
import { Tile } from '../models/Tile';
|
||||
import { TilePosition } from '../types';
|
||||
import { CONFIG } from '../config';
|
||||
|
||||
/**
|
||||
@@ -73,6 +74,46 @@ class ShakeAnimation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RippleAnimation class for touch feedback effect
|
||||
* Creates expanding circle at touch point
|
||||
*/
|
||||
class RippleAnimation {
|
||||
private startTime: number;
|
||||
private readonly x: number;
|
||||
private readonly y: number;
|
||||
private readonly duration: number = 300;
|
||||
private readonly maxRadius: number = 40;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.startTime = performance.now();
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ripple and return whether animation is still active
|
||||
* @returns true if still animating, false if complete
|
||||
*/
|
||||
render(ctx: CanvasRenderingContext2D): boolean {
|
||||
const elapsed = performance.now() - this.startTime;
|
||||
if (elapsed > this.duration) return false;
|
||||
|
||||
const progress = elapsed / this.duration;
|
||||
const radius = this.maxRadius * progress;
|
||||
const alpha = 0.3 * (1 - progress); // Fade out
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(233, 69, 96, ${alpha})`; // Selection color
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Renderer {
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private gridManager: GridManager;
|
||||
@@ -80,6 +121,7 @@ export class Renderer {
|
||||
private fadeAnimationStartTimes: Map<string, number> = new Map();
|
||||
private readonly FADE_DURATION = 100; // ms per CONTEXT.md
|
||||
private shakeAnimations: Map<string, ShakeAnimation> = new Map();
|
||||
private rippleAnimations: RippleAnimation[] = [];
|
||||
private pathAnimation: { path: TilePosition[], startTime: number } | null = null;
|
||||
private readonly PATH_DISPLAY_DURATION = 300; // ms per CONTEXT.md
|
||||
|
||||
@@ -114,6 +156,11 @@ export class Renderer {
|
||||
// Draw path animation if active
|
||||
this.renderPathAnimation();
|
||||
|
||||
// Draw ripple animations
|
||||
this.rippleAnimations = this.rippleAnimations.filter(ripple =>
|
||||
ripple.render(this.ctx)
|
||||
);
|
||||
|
||||
// Get selected tiles for highlight rendering
|
||||
const selectedTiles = this.gridManager.selectedTilesList;
|
||||
const selectedTileIds = new Set(selectedTiles.map(t => t.id));
|
||||
@@ -298,6 +345,15 @@ export class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ripple effect at touch/click coordinates
|
||||
* @param x - Canvas X coordinate
|
||||
* @param y - Canvas Y coordinate
|
||||
*/
|
||||
addRipple(x: number, y: number): void {
|
||||
this.rippleAnimations.push(new RippleAnimation(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shake offset for a tile if it has an active animation
|
||||
* @param tile - Tile to get offset for
|
||||
|
||||
Reference in New Issue
Block a user