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,16 +1,16 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { getBlockingDivs, type Rect } from "./utils";
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { getBlockingDivs, type Rect } from './utils'
|
||||
|
||||
interface SpotlightProps {
|
||||
/** 是否可见 */
|
||||
visible?: boolean;
|
||||
visible?: boolean
|
||||
/** 目标元素选择器(支持单个或多个) */
|
||||
targetSelector?: string | string[] | null;
|
||||
targetSelector?: string | string[] | null
|
||||
/** 额外内边距 */
|
||||
padding?: number;
|
||||
padding?: number
|
||||
/** 圆角 */
|
||||
borderRadius?: number;
|
||||
borderRadius?: number
|
||||
}
|
||||
|
||||
const Spotlight: React.FC<SpotlightProps> = ({
|
||||
@@ -19,108 +19,104 @@ const Spotlight: React.FC<SpotlightProps> = ({
|
||||
padding = 8,
|
||||
borderRadius = 16,
|
||||
}) => {
|
||||
const [rects, setRects] = useState<Rect[]>([]);
|
||||
const [rects, setRects] = useState<Rect[]>([])
|
||||
|
||||
const measure = useCallback(() => {
|
||||
if (!visible || !targetSelector) {
|
||||
setRects([]);
|
||||
return;
|
||||
setRects([])
|
||||
return
|
||||
}
|
||||
|
||||
const selectors = Array.isArray(targetSelector)
|
||||
? targetSelector
|
||||
: [targetSelector];
|
||||
const pad = Math.max(0, padding);
|
||||
const newRects: Rect[] = [];
|
||||
const selectors = Array.isArray(targetSelector) ? targetSelector : [targetSelector]
|
||||
const pad = Math.max(0, padding)
|
||||
const newRects: Rect[] = []
|
||||
|
||||
selectors.forEach((selector) => {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
const elements = document.querySelectorAll(selector)
|
||||
elements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const r = htmlEl.getBoundingClientRect();
|
||||
const htmlEl = el as HTMLElement
|
||||
const r = htmlEl.getBoundingClientRect()
|
||||
// 使用视口坐标(fixed层),不叠加滚动偏移
|
||||
newRects.push({
|
||||
top: r.top - pad,
|
||||
left: r.left - pad,
|
||||
width: r.width + pad * 2,
|
||||
height: r.height + pad * 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
setRects(newRects);
|
||||
}, [visible, targetSelector, padding]);
|
||||
setRects(newRects)
|
||||
}, [visible, targetSelector, padding])
|
||||
|
||||
useEffect(() => {
|
||||
// 初始测量
|
||||
requestAnimationFrame(measure);
|
||||
}, [visible, targetSelector, padding, measure]);
|
||||
requestAnimationFrame(measure)
|
||||
}, [visible, targetSelector, padding, measure])
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => measure();
|
||||
const onScroll = () => measure();
|
||||
const onResize = () => measure()
|
||||
const onScroll = () => measure()
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
window.addEventListener("scroll", onScroll, {
|
||||
window.addEventListener('resize', onResize)
|
||||
window.addEventListener('scroll', onScroll, {
|
||||
passive: true,
|
||||
} as AddEventListenerOptions);
|
||||
} as AddEventListenerOptions)
|
||||
|
||||
// 使用 ResizeObserver 监听目标元素尺寸变化
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
measure();
|
||||
});
|
||||
measure()
|
||||
})
|
||||
|
||||
// 观察所有目标元素
|
||||
if (visible && targetSelector) {
|
||||
const selectors = Array.isArray(targetSelector)
|
||||
? targetSelector
|
||||
: [targetSelector];
|
||||
const selectors = Array.isArray(targetSelector) ? targetSelector : [targetSelector]
|
||||
|
||||
selectors.forEach((selector) => {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
const elements = document.querySelectorAll(selector)
|
||||
elements.forEach((el) => {
|
||||
resizeObserver.observe(el);
|
||||
});
|
||||
});
|
||||
resizeObserver.observe(el)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 初始测量(延迟一帧确保 DOM 已渲染)
|
||||
requestAnimationFrame(measure);
|
||||
requestAnimationFrame(measure)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize);
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [measure, visible, targetSelector]);
|
||||
window.removeEventListener('resize', onResize)
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [measure, visible, targetSelector])
|
||||
|
||||
if (!visible || rects.length === 0) return null;
|
||||
if (!visible || rects.length === 0) return null
|
||||
|
||||
// 生成唯一ID用于SVG mask
|
||||
const maskId = `spotlight-mask-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const maskId = `spotlight-mask-${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
// 计算阻止点击的区域(使用共享工具函数)
|
||||
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 0;
|
||||
const viewportHeight = typeof window !== "undefined" ? window.innerHeight : 0;
|
||||
const blockingDivs = getBlockingDivs(rects, viewportWidth, viewportHeight);
|
||||
const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 0
|
||||
const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : 0
|
||||
const blockingDivs = getBlockingDivs(rects, viewportWidth, viewportHeight)
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
pointerEvents: "none",
|
||||
pointerEvents: 'none',
|
||||
zIndex: 100000,
|
||||
}}
|
||||
>
|
||||
{/* SVG 视觉遮罩 */}
|
||||
<svg
|
||||
style={{
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
pointerEvents: "none",
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
@@ -155,19 +151,12 @@ const Spotlight: React.FC<SpotlightProps> = ({
|
||||
<div
|
||||
key={`block-${index}`}
|
||||
style={{
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: `${block.top}px`,
|
||||
left:
|
||||
typeof block.left === "number" ? `${block.left}px` : block.left,
|
||||
width:
|
||||
typeof block.width === "number"
|
||||
? `${block.width}px`
|
||||
: block.width,
|
||||
height:
|
||||
typeof block.height === "number"
|
||||
? `${block.height}px`
|
||||
: block.height,
|
||||
pointerEvents: "auto",
|
||||
left: typeof block.left === 'number' ? `${block.left}px` : block.left,
|
||||
width: typeof block.width === 'number' ? `${block.width}px` : block.width,
|
||||
height: typeof block.height === 'number' ? `${block.height}px` : block.height,
|
||||
pointerEvents: 'auto',
|
||||
// backgroundColor: "rgba(255, 0, 0, 0.1)", // 调试用
|
||||
}}
|
||||
/>
|
||||
@@ -178,20 +167,20 @@ const Spotlight: React.FC<SpotlightProps> = ({
|
||||
<div
|
||||
key={`border-${index}`}
|
||||
style={{
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: `${rect.top}px`,
|
||||
left: `${rect.left}px`,
|
||||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
borderRadius: `${borderRadius}px`,
|
||||
border: "2px solid rgba(255, 255, 255, 0.3)",
|
||||
pointerEvents: "none",
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Spotlight;
|
||||
export default Spotlight
|
||||
Reference in new issue
Block a user