Files
novalon-website/src/components/effects/gsap-animation.tsx
T
张翔 016b7cfb91 feat(a11y,ux): implement comprehensive accessibility and UX optimizations
Phase 1: Accessibility Optimizations
- Add proper label associations and ARIA attributes to form inputs
- Implement aria-required, aria-invalid, aria-describedby for better form accessibility
- Add role='alert' for error messages
- Enhance keyboard navigation with aria-expanded, aria-controls
- Add aria-label for mobile menu button
- Implement aria-current for active navigation items
- Add semantic HTML with aria-labelledby for sections

Phase 2: UX Optimizations
- Create loading skeleton components for better loading states
- Add FormSkeleton, SectionSkeleton, and LoadingSkeleton components
- Prepare for lazy loading implementation

Files modified:
- src/components/ui/input.tsx: Enhanced with ARIA attributes
- src/components/ui/textarea.tsx: Enhanced with ARIA attributes
- src/components/layout/header.tsx: Added navigation ARIA labels
- src/components/sections/hero-section.tsx: Added section labels
- src/components/sections/services-section.tsx: Added section labels
- src/components/ui/loading-skeleton.tsx: New loading state components

Impact:
- WCAG 2.1 AA compliance improvements
- Better screen reader support
- Enhanced keyboard navigation
- Improved user feedback during loading
2026-02-24 00:40:19 +08:00

78 lines
1.7 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
interface GSAPAnimationProps {
className?: string;
color?: string;
}
export function GSAPAnimation({
className = '',
color = '#C41E3A'
}: GSAPAnimationProps) {
const containerRef = useRef<HTMLDivElement>(null);
const elementsRef = useRef<HTMLDivElement[]>([]);
useEffect(() => {
if (!containerRef.current) return;
const elements = elementsRef.current;
elements.forEach((el, i) => {
gsap.fromTo(el,
{
opacity: 0,
scale: 0,
rotation: 0
},
{
opacity: 0.15,
scale: 1,
rotation: 360,
duration: 8,
delay: i * 1.5,
repeat: -1,
yoyo: true,
ease: 'power2.inOut'
}
);
});
return () => {
gsap.killTweensOf(elements);
};
}, []);
const shapes = [
{ type: 'circle', size: 100, x: 15, y: 20 },
{ type: 'square', size: 80, x: 75, y: 15 },
{ type: 'circle', size: 60, x: 65, y: 65 },
{ type: 'square', size: 50, x: 20, y: 70 }
];
return (
<div ref={containerRef} className={`absolute inset-0 pointer-events-none ${className}`}>
{shapes.map((shape, index) => (
<div
key={index}
ref={el => {
if (el) elementsRef.current[index] = el;
}}
className="absolute border-2"
style={{
borderColor: `${color}20`,
width: shape.size,
height: shape.size,
left: `${shape.x}%`,
top: `${shape.y}%`,
borderRadius: shape.type === 'circle' ? '50%' : '0'
}}
/>
))}
</div>
);
}
export default GSAPAnimation;