Commit 69398078 authored by timel's avatar timel Committed by Chunchi Che

feat: chaining(focus) animation

parent 0f8e3afa
......@@ -102,6 +102,10 @@ export const NewSelectActionsModal: FC = () => {
const [selectedZone, setSelectedZone] = useState(zoneOptions[0]?.value);
useEffect(() => {
setSelectedZone(zoneOptions[0]?.value);
}, [selectables]);
const onSubmit = () => {
const values = mustSelects
.concat(response)
......@@ -184,7 +188,7 @@ export const NewSelectActionsModal: FC = () => {
<CheckCard
cover={<YgoCard code={card.meta.id} />}
style={{
width: 80,
width: 100,
aspectRatio: 5.9 / 8.6,
marginInlineEnd: 0,
marginBlockEnd: 0,
......
......@@ -28,6 +28,9 @@ section#mat {
z-index: 1;
transform: translateZ(0.5px);
}
&.focus .card-cover {
animation: focus 0.6s ease-in-out;
}
.card-back {
z-index: 0;
transform: translateZ(0px);
......@@ -42,6 +45,8 @@ section#mat {
// background-color: #0000005e;
// filter: blur(2px);
}
// 卡片被选中后的流光特效
// ref: https://github.com/Mr-majifu/Animated-Profile-Card02/blob/master/style.css
.card-streamer {
position: absolute;
......@@ -74,9 +79,33 @@ section#mat {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.card-focus {
position: absolute;
width: calc(100% * var(--focus-scale));
height: calc(100% * var(--focus-scale));
left: calc((var(--focus-scale) - 1) * -50%);
top: calc((var(--focus-scale) - 1) * -50%);
outline: 3px solid #ddd;
filter: blur(calc(2px * 1.3 * var(--focus-scale)));
display: var(--focus-display);
opacity: var(--focus-opacity);
}
}
}
.highlight {
box-shadow: 0 0 10px 2px #5db7ff;
}
@keyframes focus {
0% {
filter: brightness(1) contrast(1);
}
50% {
filter: brightness(1.5) contrast(1.1);
}
100% {
filter: brightness(1) contrast(1);
}
}
......@@ -19,6 +19,7 @@ import {
moveToHand,
moveToOutside,
} from "./springs";
import type { SpringApiProps } from "./springs/types";
import { YgoCard } from "@/ui/Shared";
......@@ -31,16 +32,22 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
const state = cardStore.inner[idx];
const snap = useSnapshot(state);
const [styles, api] = useSpring(() => ({
x: 0,
y: 0,
z: 0,
rx: 0,
ry: 0,
rz: 0,
zIndex: 0,
height: 0,
}));
const [styles, api] = useSpring(
() =>
({
x: 0,
y: 0,
z: 0,
rx: 0,
ry: 0,
rz: 0,
zIndex: 0,
height: 0,
focusScale: 1,
focusDisplay: "none",
focusOpacity: 1,
} satisfies SpringApiProps)
);
const move = async (zone: ygopro.CardZone) => {
switch (zone) {
......@@ -72,6 +79,7 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
}, []);
const [highlight, setHighlight] = useState(false);
const [classFocus, setClassFocus] = useState(false);
// const [shadowOpacity, setShadowOpacity] = useState(0); // TODO: 透明度
// >>> 动画 >>>
......@@ -83,32 +91,39 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
animationQueue = animationQueue.then(p).then(rs);
});
eventbus.register(Task.Move, async (uuid: string) => {
if (uuid === state.uuid) {
await addToAnimation(() => move(state.location.zone));
}
});
useEffect(() => {
eventbus.register(Task.Move, async (uuid: string) => {
if (uuid === state.uuid) {
await addToAnimation(() => move(state.location.zone));
}
});
eventbus.register(Task.Focus, async (uuid: string) => {
if (uuid === state.uuid) {
await addToAnimation(() => focus({ card: state, api }));
}
});
eventbus.register(
Task.Attack,
async (
uuid: string,
directAttack: boolean,
target?: ygopro.CardLocation
) => {
eventbus.register(Task.Focus, async (uuid: string) => {
if (uuid === state.uuid) {
await addToAnimation(() =>
attack({ card: state, api, target, directAttack })
);
await addToAnimation(async () => {
setClassFocus(true);
setTimeout(() => setClassFocus(false), 1000);
await focus({ card: state, api });
});
}
}
);
});
eventbus.register(
Task.Attack,
async (
uuid: string,
directAttack: boolean,
target?: ygopro.CardLocation
) => {
if (uuid === state.uuid) {
await addToAnimation(() =>
attack({ card: state, api, target, directAttack })
);
}
}
);
}, []);
// <<< 动画 <<<
const idleInteractivities = snap.idleInteractivities;
......@@ -130,6 +145,9 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
"--ry": styles.ry,
height: styles.height,
zIndex: styles.zIndex,
"--focus-scale": styles.focusScale,
"--focus-display": styles.focusDisplay,
"--focus-opacity": styles.focusOpacity,
} as any as CSSProperties
}
onClick={() => {
......@@ -140,10 +158,11 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
}
}}
>
<div className="card-focus" />
<div className="card-shadow" />
<div className="card-img-wrap">
<div className={classnames("card-img-wrap", { focus: classFocus })}>
<YgoCard
className="card-cover"
className={classnames("card-cover")}
code={snap.code === 0 ? snap.meta.id : snap.code}
/>
<YgoCard className="card-back" isBack />
......@@ -153,17 +172,6 @@ export const Card: FC<{ idx: number }> = React.memo(({ idx }) => {
);
});
function getCardImgUrl(code: number, back = false) {
const ASSETS_BASE =
import.meta.env.BASE_URL == "/"
? NeosConfig.assetsPath
: import.meta.env.BASE_URL + NeosConfig.assetsPath;
if (code === 0 || back) {
return ASSETS_BASE + "/card_back.jpg";
}
return NeosConfig.cardImgUrl + "/" + code + ".jpg";
}
const onCardClick = (card: CardType) => {
// 中央弹窗展示选中卡牌信息
messageStore.cardModal.meta = {
......
......@@ -9,19 +9,10 @@ import { asyncStart } from "./utils";
/** 发动效果的动画 */
export const focus = async (props: { card: CardType; api: SpringApi }) => {
const { card, api } = props;
const current = api.current[0].get();
if (
card.location.zone == ygopro.CardZone.HAND ||
card.location.zone == ygopro.CardZone.DECK
) {
await asyncStart(api)({
y: current.y + (matStore.isMe(card.location.controller) ? -1 : 1) * 200, // TODO: 放到config之中
ry: 0,
rz: 0,
});
await asyncStart(api)({ y: current.y, ry: current.ry, rz: current.rz });
} else {
await asyncStart(api)({ z: 200, config: config.gentle });
await asyncStart(api)({ z: current.z, config: config.default });
}
await asyncStart(api)({
focusScale: 1.5,
focusDisplay: "block",
focusOpacity: 0,
});
api.set({ focusScale: 1, focusOpacity: 1, focusDisplay: "none" });
};
import { type SpringRef } from "@react-spring/web";
export type SpringApi = SpringRef<{
export type SpringApiProps = {
x: number;
y: number;
z: number;
......@@ -9,4 +9,11 @@ export type SpringApi = SpringRef<{
rz: number;
zIndex: number;
height: number;
}>;
// >>> focus
focusScale: number;
focusDisplay: string;
focusOpacity: number;
// <<< focus
};
export type SpringApi = SpringRef<SpringApiProps>;
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