Commit 4af2045b authored by timel's avatar timel Committed by Chunchi Che

feat: add animation

parent dcf03b35
......@@ -14,7 +14,6 @@ import {
YesNoModal,
} from "./Message";
import Mat from "./PlayMat";
import { Test } from "./Test";
import { Mat as NewMat } from "./NewPlayMat";
import { Menu } from "./NewPlayMat/Menu";
......
section#mat {
.mat-card {
position: absolute;
left: 0;
top: 0;
// left: 50%;
// top: 50%;
--card-height: 100px;
height: var(--card-height);
aspect-ratio: var(--card-ratio);
......@@ -12,7 +12,8 @@ section#mat {
position: relative;
height: 100%;
width: 100%;
transform: rotateY(calc(var(--ry) * 1deg));
transform: translateZ(calc(var(--z) * 1px))
rotateY(calc(var(--ry) * 1deg));
.card-cover,
.card-back {
width: 100%;
......
import React, { type CSSProperties, type FC } from "react";
import classnames from "classnames";
import { CardType, cardStore, isMe } from "@/stores";
import React, { useEffect, type CSSProperties, type FC } from "react";
import { cardStore, messageStore, CardType } from "@/stores";
import "./index.scss";
import { useSnapshot, INTERNAL_Snapshot as Snapshot } from "valtio";
import { useSnapshot } from "valtio";
import { watch } from "valtio/utils";
import { useSpringRef, useSpring, animated, to, CSS } from "@react-spring/web";
import { matConfig } from "../utils";
import { useSpring, animated, to } from "@react-spring/web";
import { ygopro } from "@/api";
import { useConfig } from "@/config";
import { moveToDeck, moveToField, moveToHand, moveToOutside } from "./springs";
import { ReportEnum } from "./springs/types";
import { interactTypeToString } from "../../utils";
const NeosConfig = useConfig();
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE, OVERLAY } =
ygopro.CardZone;
const {
PLANE_ROTATE_X,
BLOCK_WIDTH,
BLOCK_HEIGHT_M,
BLOCK_HEIGHT_S,
CARD_RATIO,
COL_GAP,
ROW_GAP,
HAND_MARGIN_TOP,
HAND_CARD_HEIGHT,
HAND_CIRCLE_CENTER_OFFSET_Y,
DECK_OFFSET_X,
DECK_OFFSET_Y,
DECK_ROTATE_Z,
} = matConfig;
export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
const state = cardStore.inner[idx];
const snap = useSnapshot(state);
const inintialCoord = calcCoordinate(state, false);
const api = useSpringRef();
const props = useSpring({
ref: api,
from: {
x: inintialCoord.translateX,
y: inintialCoord.translateY,
z: inintialCoord.translateZ,
rotateX: inintialCoord.rotateX,
rotateY: inintialCoord.rotateY,
rotateZ: inintialCoord.rotateZ,
height: inintialCoord.height,
},
});
const reloadPosition = (report: boolean) => {
const coord = calcCoordinate(state, report);
api.start({
to: {
x: coord.translateX,
y: coord.translateY,
z: coord.translateZ,
rotateX: coord.rotateX,
rotateY: coord.rotateY,
rotateZ: coord.rotateZ,
height: coord.height,
},
});
const [styles, api] = useSpring(() => ({
x: 0,
y: 0,
z: 0,
rx: 0,
ry: 0,
rz: 0,
zIndex: 0,
height: 0,
}));
const reload = (zone: ygopro.CardZone, report: boolean) => {
switch (zone) {
case MZONE:
case SZONE:
case OVERLAY:
moveToField({ card: state, api, report });
break;
case HAND:
moveToHand({ card: state, api, report });
break;
case DECK:
case EXTRA:
moveToDeck({ card: state, api, report });
break;
case GRAVE:
case REMOVED:
moveToOutside({ card: state, api, report });
break;
}
};
useEffect(() => {
reload(state.zone, false);
}, []);
watch((get) => {
const { zone, sequence, controller, xyzMonster } = get(state);
reloadPosition(true);
reload(zone, true);
});
// 在别的手卡更改时候,刷新这张手卡
eventBus.on(
"reload-hands",
ReportEnum.ReloadHand,
({ sequence, controller }: { sequence: number; controller: number }) => {
if (state.sequence !== sequence && state.controller === controller) {
reloadPosition(false);
// console.warn('reload')
reload(state.zone, false);
}
}
);
......@@ -83,193 +75,28 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
style={
{
transform: to(
[
props.x,
props.y,
props.z,
props.rotateX,
props.rotateY,
props.rotateZ,
],
[styles.x, styles.y, styles.z, styles.rx, styles.ry, styles.rz],
(x, y, z, rx, ry, rz) =>
`translate3d(${x}px, ${y}px, ${z}px) rotateX(${rx}deg) rotateZ(${rz}deg)`
`translate(${x}px, ${y}px) rotateX(${rx}deg) rotateZ(${rz}deg)`
),
"--ry": props.rotateY,
height: props.height,
"--z": styles.z,
"--ry": styles.ry,
height: styles.height,
} as any as CSSProperties
}
onClick={() =>
[MZONE, SZONE, HAND].includes(state.zone) && onCardClick(state)
}
>
<div className="card-img-wrap">
<img className="card-cover" src={getCardImgUrl(snap.code)} alt="" />
<img className="card-back" src={getCardImgUrl(0, true)} alt="" />
</div>
<div className="card-shadow" />
</animated.div>
);
});
function calcCoordinate(
{ zone, sequence, position, xyzMonster, controller }: CardType,
report: boolean
) {
const opponent = !isMe(controller);
const res = {
translateX: -BLOCK_WIDTH.value,
translateY: BLOCK_HEIGHT_M.value + BLOCK_HEIGHT_S.value + ROW_GAP.value * 2,
translateZ: 0,
rotateX: 0,
rotateY: 0,
rotateZ: 0,
height: 0,
};
let row = -1,
col = -1;
if ([MZONE, SZONE].includes(zone)) {
row =
zone === MZONE ? (sequence > 4 ? 2 : opponent ? 1 : 3) : opponent ? 0 : 4;
col = sequence > 4 ? (sequence > 5 ? 3 : 1) : sequence;
if (opponent) col = posHelper[col];
}
if (zone === OVERLAY && xyzMonster) {
const { zone, sequence } = xyzMonster;
row =
zone === MZONE ? (sequence > 4 ? 2 : opponent ? 1 : 3) : opponent ? 0 : 4;
col = sequence > 4 ? (sequence > 5 ? 3 : 1) : sequence;
if (opponent) col = posHelper[col];
}
const isField = zone === SZONE && sequence === 5;
if (isField) {
row = opponent ? 1 : 3;
col = opponent ? 5 : -1;
}
const _position =
zone === OVERLAY && xyzMonster ? xyzMonster.position : position;
const defense = [
ygopro.CardPosition.DEFENSE,
ygopro.CardPosition.FACEDOWN_DEFENSE,
ygopro.CardPosition.FACEUP_DEFENSE,
].includes(_position ?? 5);
res.rotateZ = opponent ? 180 : 0;
res.rotateZ += defense ? 90 : 0;
res.rotateY = [
ygopro.CardPosition.FACEDOWN,
ygopro.CardPosition.FACEDOWN_ATTACK,
ygopro.CardPosition.FACEDOWN_DEFENSE,
].includes(_position ?? 5)
? 180
: res.rotateY;
res.rotateY = [DECK, EXTRA].includes(zone) ? 180 : 0;
zone === HAND && (res.rotateY = opponent ? 180 : 0);
res.height = defense
? BLOCK_WIDTH.value
: zone === MZONE
? BLOCK_HEIGHT_M.value
: BLOCK_HEIGHT_S.value;
const blockPaddingX = (BLOCK_WIDTH.value - res.height * CARD_RATIO.value) / 2;
if (row > -1) {
// 说明是场上的卡
res.translateX = (BLOCK_WIDTH.value + COL_GAP.value) * col + blockPaddingX;
res.translateY =
ROW_GAP.value * row +
BLOCK_HEIGHT_M.value * Math.min(Math.max(0, row - 1), 3) +
BLOCK_HEIGHT_S.value * Math.ceil(row / 4);
}
if (zone === HAND) {
// 得刷新除了这个卡以外所有的自己的手卡
if (report) {
eventBus.emit("reload-hands", {
controller,
sequence,
});
}
// 手卡会有很复杂的计算...
const hand_circle_center_x =
(5 * BLOCK_WIDTH.value + 4 * COL_GAP.value) / 2;
const hand_circle_center_y =
(3 * BLOCK_HEIGHT_M.value +
2 * BLOCK_HEIGHT_S.value +
4 * ROW_GAP.value) *
Number(!opponent) +
(HAND_MARGIN_TOP.value +
HAND_CARD_HEIGHT.value +
HAND_CIRCLE_CENTER_OFFSET_Y.value) *
(opponent ? -1 : 1);
const hand_card_width = CARD_RATIO.value * HAND_CARD_HEIGHT.value;
const THETA =
2 *
Math.atan(
hand_card_width /
2 /
(HAND_CIRCLE_CENTER_OFFSET_Y.value + HAND_CARD_HEIGHT.value)
) *
0.9;
// 接下来计算每一张手卡
const hands_length = cardStore.at(HAND, controller).length;
const angle = (sequence - (hands_length - 1) / 2) * THETA;
const r = HAND_CIRCLE_CENTER_OFFSET_Y.value + HAND_CARD_HEIGHT.value / 2;
const negativeX = Math.sin(angle) * r - hand_card_width / 2;
const negativeY = Math.cos(angle) * r + HAND_CARD_HEIGHT.value / 2;
const x = hand_circle_center_x + negativeX;
const y =
hand_circle_center_y -
negativeY * (opponent ? -1 : 1) -
Number(opponent) * HAND_CARD_HEIGHT.value;
res.translateX = x;
res.translateY = y;
res.translateZ = 50;
res.rotateZ =
((angle * 180) / Math.PI) * (opponent ? -1 : 1) + (opponent ? 180 : 0);
res.rotateX = -PLANE_ROTATE_X.value;
}
if (zone === DECK || zone === EXTRA) {
const leftX = -DECK_OFFSET_X.value;
const rightX =
DECK_OFFSET_X.value +
5 * BLOCK_WIDTH.value +
4 * COL_GAP.value -
CARD_RATIO.value * res.height;
const topY = -DECK_OFFSET_Y.value;
const bottomY =
DECK_OFFSET_Y.value +
3 * BLOCK_HEIGHT_M.value +
2 * BLOCK_HEIGHT_S.value +
4 * ROW_GAP.value -
BLOCK_HEIGHT_S.value;
res.translateX = opponent ? leftX : rightX;
res.translateY = opponent ? topY : bottomY;
if (zone === EXTRA) {
res.translateX = opponent ? rightX : leftX;
}
res.rotateZ = opponent ? -DECK_ROTATE_Z.value : 180 - DECK_ROTATE_Z.value;
if (zone === EXTRA) {
res.rotateZ = opponent ? DECK_ROTATE_Z.value : DECK_ROTATE_Z.value;
}
res.translateZ = sequence;
}
return res;
}
const posHelper: Record<number, number> = {
0: 4,
1: 3,
2: 2,
3: 1,
4: 0,
5: 6,
6: 5,
};
function getCardImgUrl(code: number, back = false) {
const ASSETS_BASE =
import.meta.env.BASE_URL == "/"
......@@ -280,3 +107,34 @@ function getCardImgUrl(code: number, back = false) {
}
return NeosConfig.cardImgUrl + "/" + code + ".jpg";
}
const onCardClick = (card: CardType) => () => {
// 中央弹窗展示选中卡牌信息
messageStore.cardModal.meta = {
id: card.code,
text: card.text,
data: card.data,
};
messageStore.cardModal.interactivies = card.idleInteractivities.map(
(interactivity) => ({
desc: interactTypeToString(interactivity.interactType),
response: interactivity.response,
})
);
messageStore.cardModal.counters = card.counters;
messageStore.cardModal.isOpen = true;
// 侧边栏展示超量素材信息
if (card.overlayMaterials.length > 0) {
messageStore.cardListModal.list =
card.overlayMaterials.map((overlay) => ({
meta: {
id: overlay.code,
text: overlay.text,
data: overlay.data,
},
interactivies: [],
})) || [];
messageStore.cardListModal.isOpen = true;
}
};
export * from "./toField";
export * from "./toHand";
export * from "./toDeck";
export * from "./toOutside";
import { isMe, type CardType } from "@/stores";
import { SpringApi } from "./types";
import { matConfig } from "../../utils";
import { ygopro } from "@/api";
import { easings } from "@react-spring/web";
import { asyncStart } from "./utils";
const {
PLANE_ROTATE_X,
BLOCK_WIDTH,
BLOCK_HEIGHT_M,
BLOCK_HEIGHT_S,
CARD_RATIO,
COL_GAP,
ROW_GAP,
HAND_MARGIN_TOP,
HAND_CARD_HEIGHT,
HAND_CIRCLE_CENTER_OFFSET_Y,
DECK_OFFSET_X,
DECK_OFFSET_Y,
DECK_ROTATE_Z,
DECK_CARD_HEIGHT,
} = matConfig;
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE, OVERLAY } =
ygopro.CardZone;
export const moveToDeck = async (props: {
card: CardType;
api: SpringApi;
report: boolean;
}) => {
const { card, api, report } = props;
// report
const { zone, sequence, controller, xyzMonster, position } = card;
const rightX = DECK_OFFSET_X.value + 2 * (BLOCK_WIDTH.value + COL_GAP.value);
const leftX = -rightX;
const bottomY =
DECK_OFFSET_Y.value +
2 * BLOCK_HEIGHT_M.value +
BLOCK_HEIGHT_S.value +
2 * ROW_GAP.value -
BLOCK_HEIGHT_S.value;
const topY = -bottomY;
let x = isMe(controller) ? rightX : leftX;
let y = isMe(controller) ? bottomY : topY;
if (zone === EXTRA) {
x = isMe(controller) ? leftX : rightX;
}
let rz = isMe(controller) ? 180 - DECK_ROTATE_Z.value : -DECK_ROTATE_Z.value;
if (zone === EXTRA) {
rz = isMe(controller) ? DECK_ROTATE_Z.value : DECK_ROTATE_Z.value;
}
const z = sequence;
api.start({
x,
y,
z,
rz,
zIndex: z,
height: DECK_CARD_HEIGHT.value,
});
};
import { isMe, type CardType } from "@/stores";
import { SpringApi } from "./types";
import { matConfig } from "../../utils";
import { ygopro } from "@/api";
import { easings } from "@react-spring/web";
import { asyncStart } from "./utils";
const {
PLANE_ROTATE_X,
BLOCK_WIDTH,
BLOCK_HEIGHT_M,
BLOCK_HEIGHT_S,
CARD_RATIO,
COL_GAP,
ROW_GAP,
HAND_MARGIN_TOP,
HAND_CARD_HEIGHT,
HAND_CIRCLE_CENTER_OFFSET_Y,
DECK_OFFSET_X,
DECK_OFFSET_Y,
DECK_ROTATE_Z,
} = matConfig;
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE, OVERLAY } =
ygopro.CardZone;
export const moveToField = async (props: {
card: CardType;
api: SpringApi;
report: boolean;
}) => {
const { card, api, report } = props;
// report
const { zone, sequence, controller, xyzMonster, position, overlayMaterials } =
card;
// 根据zone计算卡片的宽度
const cardWidth =
zone === SZONE
? BLOCK_HEIGHT_S.value * CARD_RATIO.value
: BLOCK_HEIGHT_M.value * CARD_RATIO.value;
const height = zone === SZONE ? BLOCK_HEIGHT_S.value : BLOCK_HEIGHT_M.value;
// 首先计算 x 和 y
let x = 0,
y = 0;
switch (zone) {
case SZONE: {
if (sequence === 5) {
// 场地魔法
x = -(
3 * (BLOCK_WIDTH.value + COL_GAP.value) -
(BLOCK_WIDTH.value - cardWidth) / 2
);
y = BLOCK_HEIGHT_M.value + ROW_GAP.value;
} else {
x = (sequence - 2) * (BLOCK_WIDTH.value + COL_GAP.value);
y =
2 * (BLOCK_HEIGHT_M.value + ROW_GAP.value) -
(BLOCK_HEIGHT_M.value - BLOCK_HEIGHT_S.value) / 2;
}
break;
}
case MZONE: {
if (sequence > 4) {
// 额外怪兽区
x = (sequence > 5 ? 1 : -1) * (BLOCK_WIDTH.value + COL_GAP.value);
y = 0;
} else {
x = (sequence - 2) * (BLOCK_WIDTH.value + COL_GAP.value);
y = BLOCK_HEIGHT_M.value + ROW_GAP.value;
}
break;
}
case OVERLAY: {
if (xyzMonster) {
const { sequence } = xyzMonster;
if (sequence > 4) {
// 额外怪兽区
x = (sequence > 5 ? 1 : -1) * (BLOCK_WIDTH.value + COL_GAP.value);
y = 0;
} else {
x = (sequence - 2) * (BLOCK_WIDTH.value + COL_GAP.value);
y = BLOCK_HEIGHT_M.value + ROW_GAP.value;
}
}
break;
}
}
if (!isMe(controller)) {
x = -x;
y = -y;
}
await asyncStart(api)({
x,
y,
height,
z: overlayMaterials.length ? 200 : 120,
ry: [
ygopro.CardPosition.FACEDOWN,
ygopro.CardPosition.FACEDOWN_ATTACK,
ygopro.CardPosition.FACEDOWN_DEFENSE,
].includes(position ?? 5)
? 180
: 0,
rz: isMe(controller) ? 0 : 180,
config: {
// mass: 0.5,
easing: easings.easeOutSine,
},
});
await asyncStart(api)({
z: 0,
zIndex: overlayMaterials.length ? 3 : 1,
config: {
easing: easings.easeInSine,
mass: 5,
tension: 300, // 170
friction: 12, // 26
clamp: true,
},
});
};
import { isMe, type CardType, cardStore } from "@/stores";
import { SpringApi, ReportEnum } from "./types";
import { matConfig } from "../../utils";
import { ygopro } from "@/api";
import { easings } from "@react-spring/web";
import { asyncStart } from "./utils";
const {
PLANE_ROTATE_X,
BLOCK_WIDTH,
BLOCK_HEIGHT_M,
BLOCK_HEIGHT_S,
CARD_RATIO,
COL_GAP,
ROW_GAP,
HAND_MARGIN_TOP,
HAND_CARD_HEIGHT,
HAND_CIRCLE_CENTER_OFFSET_Y,
DECK_OFFSET_X,
DECK_OFFSET_Y,
DECK_ROTATE_Z,
} = matConfig;
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE, OVERLAY } =
ygopro.CardZone;
export const moveToHand = async (props: {
card: CardType;
api: SpringApi;
report: boolean;
}) => {
const { card, api, report } = props;
const { zone, sequence, controller } = card;
// 得刷新除了这个卡以外所有的自己的手卡
if (report) {
eventBus.emit(ReportEnum.ReloadHand, {
controller,
sequence,
});
}
// 手卡会有很复杂的计算...
const hand_circle_center_x = 0;
const hand_circle_center_y =
1 * BLOCK_HEIGHT_M.value +
1 * BLOCK_HEIGHT_S.value +
2 * ROW_GAP.value +
(HAND_MARGIN_TOP.value +
HAND_CARD_HEIGHT.value +
HAND_CIRCLE_CENTER_OFFSET_Y.value);
const hand_card_width = CARD_RATIO.value * HAND_CARD_HEIGHT.value;
const THETA =
2 *
Math.atan(
hand_card_width /
2 /
(HAND_CIRCLE_CENTER_OFFSET_Y.value + HAND_CARD_HEIGHT.value)
) *
0.9;
// 接下来计算每一张手卡
const hands_length = cardStore.at(HAND, controller).length;
const angle = (sequence - (hands_length - 1) / 2) * THETA;
const r = HAND_CIRCLE_CENTER_OFFSET_Y.value + HAND_CARD_HEIGHT.value / 2;
const negativeX = Math.sin(angle) * r;
const negativeY = Math.cos(angle) * r + HAND_CARD_HEIGHT.value / 2;
const x = hand_circle_center_x + negativeX * (isMe(controller) ? 1 : -1);
const y = hand_circle_center_y - negativeY + 130; // 常量 是手动调的 这里肯定有问题 有空来修
const _rz = (angle * 180) / Math.PI;
api.start({
x: isMe(controller) ? x : -x,
y: isMe(controller) ? y : -y,
rz: isMe(controller) ? _rz : 180 - _rz,
height: HAND_CARD_HEIGHT.value,
// rx: -PLANE_ROTATE_X.value,
});
};
import { isMe, type CardType } from "@/stores";
import { SpringApi } from "./types";
import { matConfig } from "../../utils";
import { ygopro } from "@/api";
import { easings } from "@react-spring/web";
import { asyncStart } from "./utils";
const {
PLANE_ROTATE_X,
BLOCK_WIDTH,
BLOCK_HEIGHT_M,
BLOCK_HEIGHT_S,
CARD_RATIO,
COL_GAP,
ROW_GAP,
HAND_MARGIN_TOP,
HAND_CARD_HEIGHT,
HAND_CIRCLE_CENTER_OFFSET_Y,
DECK_OFFSET_X,
DECK_OFFSET_Y,
DECK_ROTATE_Z,
} = matConfig;
const { HAND, GRAVE, REMOVED, DECK, EXTRA, MZONE, SZONE, TZONE, OVERLAY } =
ygopro.CardZone;
export const moveToOutside = async (props: {
card: CardType;
api: SpringApi;
report: boolean;
}) => {
const { card, api, report } = props;
// report
const { zone, sequence, controller, xyzMonster, position, overlayMaterials } =
card;
let x = 0,
y = 0;
if (zone === GRAVE) {
x = (BLOCK_WIDTH.value + COL_GAP.value) * 3;
y = BLOCK_HEIGHT_M.value + ROW_GAP.value;
} else if (zone === REMOVED) {
x = (BLOCK_WIDTH.value + COL_GAP.value) * 2;
}
if (!isMe(controller)) {
x = -x;
y = -y;
}
api.start({
x,
y,
z: 0,
rz: isMe(controller) ? 0 : 180,
});
};
import { type SpringValue, type SpringRef } from "@react-spring/web";
export type SpringApi = SpringRef<{
x: number;
y: number;
z: number;
rx: number;
ry: number;
rz: number;
zIndex: number;
height: number;
}>;
export enum ReportEnum {
ReloadHand = "reload-hand",
}
import { type SpringRef, type SpringConfig } from "@react-spring/web";
export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
return (p: Partial<T> & { config: SpringConfig }) =>
new Promise((resolve) => {
api.start({
...p,
onRest: resolve,
});
});
};
section#mat {
margin-top: 200px;
// margin-top: 200px;
// padding-top: 50px; // 先不管 后面调整
position: relative;
#camera {
......@@ -8,12 +8,26 @@ section#mat {
flex-direction: column;
justify-content: center;
align-items: center;
perspective: var(--perspective);
// perspective: var(--perspective);
}
#plane {
transform: translateX(0) translateY(0) translateZ(0)
rotateX(var(--plane-rotate-x));
width: fit-content;
transform-style: preserve-3d;
perspective: var(--perspective);
}
}
.mat-card-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
transform-style: preserve-3d;
}
......@@ -22,9 +22,11 @@ export const Mat: FC = () => {
>
<Plane>
<Bg />
<CardContainer>
{snap.map((cardSnap, i) => (
<Card key={i} idx={i} />
))}
</CardContainer>
</Plane>
</section>
);
......@@ -35,3 +37,7 @@ const Plane: FC<PropsWithChildren> = ({ children }) => (
<div id="plane">{children}</div>
</div>
);
const CardContainer: FC<PropsWithChildren> = ({ children }) => (
<div className="mat-card-container">{children}</div>
);
......@@ -76,4 +76,8 @@ export const matConfig = {
value: 30,
unit: UNIT.DEG,
},
DECK_CARD_HEIGHT: {
value: 120,
unit: UNIT.PX,
},
};
import { cardStore } from "@/stores";
import { useSnapshot } from "valtio";
import { subscribeKey, watch } from "valtio/utils";
import { FC, memo, useEffect, useState } from "react";
import { ygopro } from "@/api";
import {
useSpring,
SpringValue,
animated,
useSpringRef,
} from "@react-spring/web";
export const Test = () => {
const snap = useSnapshot(cardStore.inner);
return (
<div
style={{
background: "white",
position: "fixed",
left: 0,
top: 0,
color: "black",
zIndex: 9999,
fontSize: 12,
}}
>
{snap.map((cardState, i) => (
<Card
idx={i}
key={i}
show={[
ygopro.CardZone.HAND,
ygopro.CardZone.MZONE,
ygopro.CardZone.SZONE,
ygopro.CardZone.GRAVE,
].includes(cardState.zone)}
/>
))}
</div>
);
};
export const Card: FC<{ idx: number; show: boolean }> = memo(
({ idx, show }) => {
const snap = useSnapshot(cardStore.inner[idx]);
const api = useSpringRef();
const props = useSpring({
ref: api,
from: { x: 0 },
});
// subscribeKey(cardStore.inner[idx], "zone", (value) => {
// api.start({
// to: {
// x: value * 100,
// },
// });
// });
watch((get) => {
get(cardStore.inner[idx]);
const zone = get(cardStore.inner[idx]).zone;
api.start({
to: {
x: zone * 100,
},
});
});
return show ? (
<animated.div
style={{
transform: props.x.to((value) => `translateX(${value}px)`),
background: "white",
}}
>
<div>code: {snap.code}</div>
<div>{(Math.random() * 100).toFixed(0)}</div>
</animated.div>
) : (
<></>
);
},
(prev, next) => prev.show === next.show // 只有 show 变化时才会重新渲染
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment