Files
novalon-website/src/components/effects/subtle-dots.tsx
T
张翔 1f591fe2b4 feat: 完成网站功能开发与优化
- 完善产品页面布局与交互
- 优化服务详情页用户体验
- 增强新闻模块内容展示
- 改进团队页面设计
- 优化全局样式和响应式布局
- 添加分页组件支持
- 提升性能与SEO优化
- 修复已知问题与改进代码质量
2026-04-27 20:53:39 +08:00

74 lines
1.7 KiB
TypeScript

'use client';
import { motion } from 'framer-motion';
import { useState, useEffect } from 'react';
interface SubtleDotsProps {
className?: string;
color?: string;
count?: number;
}
export function SubtleDots({
className = '',
color = 'var(--color-brand-primary)',
count = 12
}: SubtleDotsProps) {
const [dots, setDots] = useState<Array<{
id: number;
x: number;
y: number;
size: number;
delay: number;
}>>([]);
/* eslint-disable react-hooks/set-state-in-effect */
useEffect(() => {
const newDots = Array.from({ length: count }, (_, i) => ({
id: i,
x: 10 + Math.random() * 80,
y: 10 + Math.random() * 80,
size: 2 + Math.random() * 3,
delay: i * 0.3
}));
setDots(newDots);
}, [count]);
/* eslint-enable react-hooks/set-state-in-effect */
if (dots.length === 0) {
return <div className={`absolute inset-0 pointer-events-none ${className}`} />;
}
return (
<div className={`absolute inset-0 pointer-events-none ${className}`}>
{dots.map((dot) => (
<motion.div
key={dot.id}
className="absolute rounded-full"
style={{
width: dot.size,
height: dot.size,
backgroundColor: color,
left: `${dot.x}%`,
top: `${dot.y}%`
}}
initial={{ opacity: 0, scale: 0 }}
animate={{
opacity: [0, 0.2, 0.2, 0],
scale: [0, 1, 1, 0]
}}
transition={{
duration: 4,
delay: dot.delay,
repeat: Infinity,
ease: 'easeInOut',
times: [0, 0.3, 0.7, 1]
}}
/>
))}
</div>
);
}
export default SubtleDots;