feat: 更新 ESLint 和 Prettier 配置,增强代码质量管理
- 在 .gitignore 中添加 public/audio/* 以忽略音频文件。 - 更新 bun.lock,添加 eslint-config-prettier 和 eslint-plugin-prettier 依赖,确保 ESLint 与 Prettier 的兼容性。 - 修改 eslint.config.ts,整合 Prettier 规则,优化代码风格一致性。 - 在 package.json 中添加 lint:fix 和 format 脚本,简化代码格式化和修复流程。 - 更新 tailwind.config.ts 和其他组件文件,统一代码风格,提升可读性。
This commit is contained in:
1 parent
5d00adc1a5
commit
76c67b492e
216 files changed
+12454
-13976
No files matched your search
@@ -1,64 +1,59 @@
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import { motion } from "framer-motion";
|
||||
import React from "react";
|
||||
import { createRoot, Root } from 'react-dom/client'
|
||||
import { motion } from 'framer-motion'
|
||||
import React from 'react'
|
||||
|
||||
export type FlyToPosition =
|
||||
| "center"
|
||||
| "top-left"
|
||||
| "top-right"
|
||||
| "bottom-left"
|
||||
| "bottom-right";
|
||||
export type FlyToPosition = 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
||||
|
||||
export interface FlyToAnimationOptions {
|
||||
/** 起始元素的 ref */
|
||||
fromRef: React.RefObject<HTMLElement | null>;
|
||||
fromRef: React.RefObject<HTMLElement | null>
|
||||
/** 目标元素的 ref */
|
||||
toRef: React.RefObject<HTMLElement | null>;
|
||||
toRef: React.RefObject<HTMLElement | null>
|
||||
/** 目标位置(相对于目标元素) */
|
||||
targetPosition?: FlyToPosition;
|
||||
targetPosition?: FlyToPosition
|
||||
/** 动画持续时间(毫秒) */
|
||||
duration?: number;
|
||||
duration?: number
|
||||
/** 动画缓动函数 */
|
||||
ease?: string | number[];
|
||||
ease?: string | number[]
|
||||
/** 动画完成回调 */
|
||||
onComplete?: () => void;
|
||||
onComplete?: () => void
|
||||
/** 自定义样式 */
|
||||
className?: string;
|
||||
className?: string
|
||||
/** 缩放比例(从起始到目标) */
|
||||
scale?: { from: number; to: number };
|
||||
scale?: { from: number; to: number }
|
||||
/** 透明度(从起始到目标) */
|
||||
opacity?: number[];
|
||||
opacity?: number[]
|
||||
}
|
||||
|
||||
interface AnimationInstance extends FlyToAnimationOptions {
|
||||
id: string;
|
||||
startPos: { x: number; y: number };
|
||||
targetPos: { x: number; y: number };
|
||||
elementHtml: string;
|
||||
id: string
|
||||
startPos: { x: number; y: number }
|
||||
targetPos: { x: number; y: number }
|
||||
elementHtml: string
|
||||
}
|
||||
|
||||
class FlyToAnimationManagerClass {
|
||||
private animations: Map<string, AnimationInstance> = new Map();
|
||||
private containerRoot: Root | null = null;
|
||||
private animations: Map<string, AnimationInstance> = new Map()
|
||||
private containerRoot: Root | null = null
|
||||
|
||||
// 初始化容器
|
||||
private initContainer() {
|
||||
if (typeof window === "undefined") return;
|
||||
if (this.containerRoot) return;
|
||||
if (typeof window === 'undefined') return
|
||||
if (this.containerRoot) return
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.id = "fly-to-animation-container";
|
||||
document.body.appendChild(container);
|
||||
const container = document.createElement('div')
|
||||
container.id = 'fly-to-animation-container'
|
||||
document.body.appendChild(container)
|
||||
|
||||
this.containerRoot = createRoot(container);
|
||||
this.render();
|
||||
this.containerRoot = createRoot(container)
|
||||
this.render()
|
||||
}
|
||||
|
||||
// 渲染动画容器
|
||||
private render() {
|
||||
if (!this.containerRoot) return;
|
||||
if (!this.containerRoot) return
|
||||
|
||||
const animations = Array.from(this.animations.values());
|
||||
const animations = Array.from(this.animations.values())
|
||||
|
||||
this.containerRoot.render(
|
||||
<>
|
||||
@@ -66,150 +61,140 @@ class FlyToAnimationManagerClass {
|
||||
<FlyToAnimationItem key={animation.id} animation={animation} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// 通知更新
|
||||
private notify() {
|
||||
this.render();
|
||||
this.render()
|
||||
}
|
||||
|
||||
// 创建动画
|
||||
flyTo(options: FlyToAnimationOptions) {
|
||||
const { fromRef, toRef } = options;
|
||||
const { fromRef, toRef } = options
|
||||
|
||||
if (!fromRef?.current || !toRef?.current) {
|
||||
console.warn("FlyToAnimation: fromRef 或 toRef 未设置");
|
||||
return;
|
||||
console.warn('FlyToAnimation: fromRef 或 toRef 未设置')
|
||||
return
|
||||
}
|
||||
|
||||
this.initContainer();
|
||||
this.initContainer()
|
||||
|
||||
// 使用 requestAnimationFrame 确保在下一帧获取准确的位置
|
||||
requestAnimationFrame(() => {
|
||||
const fromRect = fromRef.current!.getBoundingClientRect();
|
||||
const toRect = toRef.current!.getBoundingClientRect();
|
||||
const fromRect = fromRef.current!.getBoundingClientRect()
|
||||
const toRect = toRef.current!.getBoundingClientRect()
|
||||
|
||||
// 计算起始位置(起始元素的中心)
|
||||
const startX = fromRect.left + fromRect.width / 2;
|
||||
const startY = fromRect.top + fromRect.height / 2;
|
||||
const startX = fromRect.left + fromRect.width / 2
|
||||
const startY = fromRect.top + fromRect.height / 2
|
||||
|
||||
// 计算目标位置
|
||||
const targetPosition = options.targetPosition || "center";
|
||||
let targetX = 0;
|
||||
let targetY = 0;
|
||||
const targetPosition = options.targetPosition || 'center'
|
||||
let targetX = 0
|
||||
let targetY = 0
|
||||
|
||||
switch (targetPosition) {
|
||||
case "center":
|
||||
targetX = toRect.left + toRect.width / 2;
|
||||
targetY = toRect.top + toRect.height / 2;
|
||||
break;
|
||||
case "top-left":
|
||||
targetX = toRect.left;
|
||||
targetY = toRect.top;
|
||||
break;
|
||||
case "top-right":
|
||||
targetX = toRect.right;
|
||||
targetY = toRect.top;
|
||||
break;
|
||||
case "bottom-left":
|
||||
targetX = toRect.left;
|
||||
targetY = toRect.bottom;
|
||||
break;
|
||||
case "bottom-right":
|
||||
targetX = toRect.right;
|
||||
targetY = toRect.bottom;
|
||||
break;
|
||||
case 'center':
|
||||
targetX = toRect.left + toRect.width / 2
|
||||
targetY = toRect.top + toRect.height / 2
|
||||
break
|
||||
case 'top-left':
|
||||
targetX = toRect.left
|
||||
targetY = toRect.top
|
||||
break
|
||||
case 'top-right':
|
||||
targetX = toRect.right
|
||||
targetY = toRect.top
|
||||
break
|
||||
case 'bottom-left':
|
||||
targetX = toRect.left
|
||||
targetY = toRect.bottom
|
||||
break
|
||||
case 'bottom-right':
|
||||
targetX = toRect.right
|
||||
targetY = toRect.bottom
|
||||
break
|
||||
}
|
||||
|
||||
// 克隆起始元素的 HTML 内容和样式
|
||||
// 使用 cloneNode 深度克隆,包括所有子元素
|
||||
const clonedElement = fromRef.current!.cloneNode(true) as HTMLElement;
|
||||
const clonedElement = fromRef.current!.cloneNode(true) as HTMLElement
|
||||
|
||||
// 复制计算后的样式到克隆元素,确保尺寸正确
|
||||
const computedStyle = window.getComputedStyle(fromRef.current!);
|
||||
|
||||
const computedStyle = window.getComputedStyle(fromRef.current!)
|
||||
|
||||
// 复制所有样式属性
|
||||
const allStyles = Array.from(computedStyle);
|
||||
const allStyles = Array.from(computedStyle)
|
||||
allStyles.forEach((prop) => {
|
||||
const value = computedStyle.getPropertyValue(prop);
|
||||
const value = computedStyle.getPropertyValue(prop)
|
||||
if (value) {
|
||||
clonedElement.style.setProperty(prop, value);
|
||||
clonedElement.style.setProperty(prop, value)
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
// 确保克隆元素的尺寸与原始元素一致
|
||||
clonedElement.style.width = `${fromRect.width}px`;
|
||||
clonedElement.style.height = `${fromRect.height}px`;
|
||||
clonedElement.style.position = "absolute";
|
||||
clonedElement.style.left = "0";
|
||||
clonedElement.style.top = "0";
|
||||
clonedElement.style.margin = "0";
|
||||
clonedElement.style.padding = "0";
|
||||
clonedElement.style.width = `${fromRect.width}px`
|
||||
clonedElement.style.height = `${fromRect.height}px`
|
||||
clonedElement.style.position = 'absolute'
|
||||
clonedElement.style.left = '0'
|
||||
clonedElement.style.top = '0'
|
||||
clonedElement.style.margin = '0'
|
||||
clonedElement.style.padding = '0'
|
||||
|
||||
// 获取克隆后的 HTML(只获取内部内容,不包含外层元素)
|
||||
const elementHtml = clonedElement.innerHTML;
|
||||
const elementHtml = clonedElement.innerHTML
|
||||
|
||||
// 创建动画实例
|
||||
const id = `fly-to-${Date.now()}-${Math.random()
|
||||
.toString(36)
|
||||
.slice(2, 11)}`;
|
||||
const id = `fly-to-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`
|
||||
const instance: AnimationInstance = {
|
||||
id,
|
||||
...options,
|
||||
startPos: { x: startX, y: startY },
|
||||
targetPos: { x: targetX, y: targetY },
|
||||
elementHtml,
|
||||
};
|
||||
}
|
||||
|
||||
this.animations.set(id, instance);
|
||||
this.notify();
|
||||
this.animations.set(id, instance)
|
||||
this.notify()
|
||||
|
||||
// 动画完成后自动清理
|
||||
const duration = options.duration || 1200;
|
||||
const duration = options.duration || 1200
|
||||
setTimeout(() => {
|
||||
this.remove(id);
|
||||
options.onComplete?.();
|
||||
}, duration);
|
||||
});
|
||||
this.remove(id)
|
||||
options.onComplete?.()
|
||||
}, duration)
|
||||
})
|
||||
}
|
||||
|
||||
// 移除动画
|
||||
remove(id: string) {
|
||||
this.animations.delete(id);
|
||||
this.notify();
|
||||
this.animations.delete(id)
|
||||
this.notify()
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局单例
|
||||
export const flyToAnimationManager = new FlyToAnimationManagerClass();
|
||||
export const flyToAnimationManager = new FlyToAnimationManagerClass()
|
||||
|
||||
// 单个动画项组件
|
||||
const FlyToAnimationItem = ({
|
||||
animation,
|
||||
}: {
|
||||
animation: AnimationInstance;
|
||||
}) => {
|
||||
const FlyToAnimationItem = ({ animation }: { animation: AnimationInstance }) => {
|
||||
const {
|
||||
startPos,
|
||||
targetPos,
|
||||
elementHtml,
|
||||
duration = 1200,
|
||||
ease = "easeInOut",
|
||||
className = "",
|
||||
ease = 'easeInOut',
|
||||
className = '',
|
||||
scale = { from: 1, to: 0.3 },
|
||||
opacity = [1, 0.8, 0],
|
||||
} = animation;
|
||||
} = animation
|
||||
|
||||
// 计算 opacity 的 times 数组
|
||||
const opacityTimes =
|
||||
opacity.length > 1
|
||||
? opacity.map((_, i) => i / (opacity.length - 1))
|
||||
: [0, 1];
|
||||
const opacityTimes = opacity.length > 1 ? opacity.map((_, i) => i / (opacity.length - 1)) : [0, 1]
|
||||
|
||||
// 如果 opacity 是数组,使用整个数组;否则使用单个值
|
||||
const animateOpacity =
|
||||
opacity.length > 1 ? opacity : opacity[opacity.length - 1] ?? 0;
|
||||
const animateOpacity = opacity.length > 1 ? opacity : (opacity[opacity.length - 1] ?? 0)
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -247,10 +232,7 @@ const FlyToAnimationItem = ({
|
||||
`translate(-50%, -50%) translateX(${x}) translateY(${y}) scale(${scale})`
|
||||
}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: elementHtml }}
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
<div dangerouslySetInnerHTML={{ __html: elementHtml }} style={{ pointerEvents: 'none' }} />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user