mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-13 18:46:17 +08:00
90 lines
4.3 KiB
JavaScript
90 lines
4.3 KiB
JavaScript
// 只覆盖命名空间内的 CSS 变量,不移动或复制真实卡牌。
|
|
export function fanSlots(count, width, cardWidth = 112, cardHeight = 156) {
|
|
if (!Number.isSafeInteger(count) || count < 0 || !Number.isFinite(width) || width <= 0) return [];
|
|
const step = count > 1 ? Math.min(cardWidth * 0.68,
|
|
Math.max(0, width - cardWidth - 32) / (count - 1)) : 0;
|
|
const half = (count - 1) / 2;
|
|
const angleStep = count > 1 ? Math.min(4, 13 / half) : 0;
|
|
return Array.from({ length: count }, (_, index) => {
|
|
const offset = index - half;
|
|
return { x: width / 2 + offset * step - cardWidth / 2,
|
|
y: -cardHeight + 28 + (half ? Math.pow(offset / half, 2) * 20 : 0),
|
|
angle: offset * angleStep, z: index + 1 };
|
|
});
|
|
}
|
|
|
|
export function createHandFan(adapter, env) {
|
|
const { window } = env;
|
|
const owned = new Map();
|
|
let enabled = false, hover = null, dragging = null;
|
|
function variable(card, key, value) {
|
|
let record = owned.get(card);
|
|
if (!record) { record = new Map(); owned.set(card, record); }
|
|
if (!record.has(key)) record.set(key, {
|
|
before: card.style.getPropertyValue(key), priority: card.style.getPropertyPriority(key), last: null,
|
|
});
|
|
const entry = record.get(key);
|
|
entry.last = value;
|
|
if (card.style.getPropertyValue(key) !== value) card.style.setProperty(key, value);
|
|
}
|
|
function release(card) {
|
|
for (const name of ["mengsan-hand-hover-shuying", "mengsan-hand-drag-shuying", "mengsan-hand-free-drag-shuying"]) card.classList.remove(name);
|
|
for (const [key, record] of owned.get(card) || []) {
|
|
if (card.style.getPropertyValue(key) !== record.last) continue;
|
|
if (record.before) card.style.setProperty(key, record.before, record.priority);
|
|
else card.style.removeProperty(key);
|
|
}
|
|
owned.delete(card);
|
|
}
|
|
function setHover(card) {
|
|
if (hover === card) return;
|
|
hover?.classList.remove("mengsan-hand-hover-shuying");
|
|
hover = enabled && !dragging ? card : null;
|
|
hover?.classList.add("mengsan-hand-hover-shuying");
|
|
}
|
|
function sync(active) {
|
|
enabled = active;
|
|
const cards = adapter.cards().filter(card => !card.classList.contains("display-none"));
|
|
const current = new Set(cards);
|
|
for (const card of owned.keys()) if (!active || !current.has(card)) release(card);
|
|
if (!active) { setHover(null); return; }
|
|
const small = window.innerWidth <= 640 || window.innerHeight <= 500;
|
|
const width = adapter.containers[0]?.clientWidth || adapter.root.clientWidth;
|
|
const slots = fanSlots(cards.length, width, small ? 82 : 112, small ? 114 : 156);
|
|
cards.forEach((card, index) => {
|
|
const slot = slots[index];
|
|
if (!slot) return;
|
|
variable(card, "--mengsan-fan-x", `${slot.x}px`);
|
|
variable(card, "--mengsan-fan-y", `${slot.y}px`);
|
|
variable(card, "--mengsan-fan-angle", `${slot.angle}deg`);
|
|
variable(card, "--mengsan-fan-z", `${slot.z}`);
|
|
});
|
|
if (hover && !current.has(hover)) setHover(null);
|
|
}
|
|
function drag(card, point, targeted) {
|
|
if (dragging && dragging !== card) stopDrag();
|
|
setHover(null);
|
|
dragging = card;
|
|
card.classList.add("mengsan-hand-drag-shuying");
|
|
card.classList.toggle("mengsan-hand-free-drag-shuying", !targeted);
|
|
const zone = card.parentNode;
|
|
const rect = zone.getBoundingClientRect();
|
|
// CSS 像素相对于真实父容器:兼容 documentZoom/缩放,不能直接拿屏幕坐标当布局坐标。
|
|
const scaleX = rect.width / zone.offsetWidth || 1;
|
|
const scaleY = rect.height / zone.offsetHeight || scaleX;
|
|
const rootRect = adapter.root.getBoundingClientRect();
|
|
const small = window.innerWidth <= 640 || window.innerHeight <= 500;
|
|
const width = small ? 82 : 112, height = small ? 114 : 156;
|
|
const x = targeted ? rootRect.left + rootRect.width / 2 : point.x;
|
|
const y = targeted ? rootRect.bottom - height * scaleY / 2 - 6 : point.y;
|
|
variable(card, "--mengsan-drag-x", `${(x - rect.left) / scaleX - width / 2}px`);
|
|
variable(card, "--mengsan-drag-y", `${(y - rect.top) / scaleY - height / 2}px`);
|
|
}
|
|
function stopDrag() {
|
|
dragging?.classList.remove("mengsan-hand-drag-shuying", "mengsan-hand-free-drag-shuying");
|
|
dragging = null;
|
|
}
|
|
return { sync, setHover, drag, stopDrag,
|
|
dispose() { stopDrag(); setHover(null); for (const card of owned.keys()) release(card); } };
|
|
}
|