feat(projects): animate buttons on click
This commit is contained in:
BIN
app/assets/images/projects/bottom-screen/circle_big.webp
Normal file
BIN
app/assets/images/projects/bottom-screen/circle_big.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 392 B |
BIN
app/assets/images/projects/bottom-screen/circle_small.webp
Normal file
BIN
app/assets/images/projects/bottom-screen/circle_small.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 376 B |
BIN
app/assets/images/projects/bottom-screen/link-pressed.webp
Normal file
BIN
app/assets/images/projects/bottom-screen/link-pressed.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 B |
BIN
app/assets/images/projects/bottom-screen/next-pressed.webp
Normal file
BIN
app/assets/images/projects/bottom-screen/next-pressed.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 B |
BIN
app/assets/images/projects/bottom-screen/prev-pressed.webp
Normal file
BIN
app/assets/images/projects/bottom-screen/prev-pressed.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
BIN
app/assets/images/projects/bottom-screen/quit-pressed.webp
Normal file
BIN
app/assets/images/projects/bottom-screen/quit-pressed.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
@@ -1,50 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import PREV_PRESSED_IMAGE from "~/assets/images/projects/bottom-screen/prev-pressed.webp";
|
||||
import QUIT_PRESSED_IMAGE from "~/assets/images/projects/bottom-screen/quit-pressed.webp";
|
||||
import LINK_PRESSED_IMAGE from "~/assets/images/projects/bottom-screen/link-pressed.webp";
|
||||
import NEXT_PRESSED_IMAGE from "~/assets/images/projects/bottom-screen/next-pressed.webp";
|
||||
import CIRCLE_SMALL_IMAGE from "~/assets/images/projects/bottom-screen/circle_small.webp";
|
||||
import CIRCLE_BIG_IMAGE from "~/assets/images/projects/bottom-screen/circle_big.webp";
|
||||
import gsap from "gsap";
|
||||
|
||||
const store = useProjectsStore();
|
||||
|
||||
const PREV_BUTTON: Point = [36, 100];
|
||||
const QUIT_BUTTON: Point = [88, 156];
|
||||
const LINK_BUTTON: Point = [168, 156];
|
||||
const NEXT_BUTTON: Point = [220, 100];
|
||||
const [
|
||||
prevPressedImage,
|
||||
quitPressedImage,
|
||||
linkPressedImage,
|
||||
nextPressedImage,
|
||||
circleSmallImagee,
|
||||
circleBigImage,
|
||||
] = useImages(
|
||||
PREV_PRESSED_IMAGE,
|
||||
QUIT_PRESSED_IMAGE,
|
||||
LINK_PRESSED_IMAGE,
|
||||
NEXT_PRESSED_IMAGE,
|
||||
CIRCLE_SMALL_IMAGE,
|
||||
CIRCLE_BIG_IMAGE,
|
||||
);
|
||||
|
||||
const CLICK_RADIUS = 22;
|
||||
|
||||
const BUTTONS = {
|
||||
prev: { position: [36, 100], image: prevPressedImage! },
|
||||
quit: { position: [88, 156], image: quitPressedImage! },
|
||||
link: { position: [168, 156], image: linkPressedImage! },
|
||||
next: { position: [220, 100], image: nextPressedImage! },
|
||||
} as const satisfies Record<
|
||||
string,
|
||||
{ position: Point; image: HTMLImageElement }
|
||||
>;
|
||||
|
||||
type ButtonType = keyof typeof BUTTONS;
|
||||
|
||||
type ButtonAnimation = {
|
||||
type: ButtonType;
|
||||
position: Point;
|
||||
showButton: boolean;
|
||||
showSmallCircle: boolean;
|
||||
showBigCircle: boolean;
|
||||
};
|
||||
|
||||
let currentAnimation: ButtonAnimation | null = null;
|
||||
|
||||
const circleContains = (
|
||||
[cx, cy]: Point,
|
||||
[x, y]: Point,
|
||||
radius: number,
|
||||
): boolean => Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2)) < radius;
|
||||
|
||||
const startButtonAnimation = (type: ButtonType) => {
|
||||
const anim: ButtonAnimation = {
|
||||
type,
|
||||
position: BUTTONS[type].position,
|
||||
showButton: true,
|
||||
showSmallCircle: true,
|
||||
showBigCircle: false,
|
||||
};
|
||||
currentAnimation = anim;
|
||||
|
||||
const SMALL_CIRCLE_DURATION = 0.07;
|
||||
const BIG_CIRCLE_DURATION = 0.09;
|
||||
const POST_DURATION = 0.07;
|
||||
|
||||
gsap
|
||||
.timeline()
|
||||
.to(anim, { duration: SMALL_CIRCLE_DURATION })
|
||||
.call(() => {
|
||||
anim.showSmallCircle = false;
|
||||
anim.showBigCircle = true;
|
||||
})
|
||||
.to(anim, { duration: BIG_CIRCLE_DURATION })
|
||||
.call(() => {
|
||||
anim.showBigCircle = false;
|
||||
})
|
||||
.to(anim, { duration: POST_DURATION })
|
||||
.call(() => {
|
||||
currentAnimation = null;
|
||||
});
|
||||
};
|
||||
|
||||
useScreenClick((x, y) => {
|
||||
if (currentAnimation) return;
|
||||
|
||||
const project = store.projects[store.currentProject];
|
||||
if (circleContains(PREV_BUTTON, [x, y], CLICK_RADIUS)) {
|
||||
if (circleContains(BUTTONS.prev.position, [x, y], CLICK_RADIUS)) {
|
||||
startButtonAnimation("prev");
|
||||
store.scrollProjects("left");
|
||||
} else if (circleContains(NEXT_BUTTON, [x, y], CLICK_RADIUS)) {
|
||||
} else if (circleContains(BUTTONS.next.position, [x, y], CLICK_RADIUS)) {
|
||||
startButtonAnimation("next");
|
||||
store.scrollProjects("right");
|
||||
} else if (circleContains(QUIT_BUTTON, [x, y], CLICK_RADIUS)) {
|
||||
} else if (circleContains(BUTTONS.quit.position, [x, y], CLICK_RADIUS)) {
|
||||
startButtonAnimation("quit");
|
||||
throw new Error("quit");
|
||||
} else if (
|
||||
circleContains(LINK_BUTTON, [x, y], CLICK_RADIUS) &&
|
||||
circleContains(BUTTONS.link.position, [x, y], CLICK_RADIUS) &&
|
||||
project?.link
|
||||
) {
|
||||
startButtonAnimation("link");
|
||||
// TODO: show confirmation popup before opening the link, like "you are about to navigate to [...]"
|
||||
store.visitProject();
|
||||
// store.visitProject();
|
||||
}
|
||||
});
|
||||
|
||||
useScreenMouseWheel((dy) => {
|
||||
if (dy > 0) {
|
||||
store.scrollProjects("right");
|
||||
} else if (dy < 0) {
|
||||
store.scrollProjects("left");
|
||||
useRender((ctx) => {
|
||||
if (!currentAnimation) return;
|
||||
|
||||
if (currentAnimation.showButton) {
|
||||
const image = BUTTONS[currentAnimation.type].image;
|
||||
ctx.drawImage(
|
||||
image!,
|
||||
currentAnimation.position[0] - 14,
|
||||
currentAnimation.position[1] - 14,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentAnimation.showSmallCircle) {
|
||||
ctx.drawImage(
|
||||
circleSmallImagee!,
|
||||
currentAnimation.position[0] - 28,
|
||||
currentAnimation.position[1] - 28,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentAnimation.showBigCircle) {
|
||||
ctx.drawImage(
|
||||
circleBigImage!,
|
||||
currentAnimation.position[0] - 44,
|
||||
currentAnimation.position[1] - 44,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
useKeyDown((key) => {
|
||||
if (currentAnimation) return;
|
||||
switch (key) {
|
||||
case "NDS_LEFT":
|
||||
startButtonAnimation("prev");
|
||||
store.scrollProjects("left");
|
||||
break;
|
||||
case "NDS_RIGHT":
|
||||
startButtonAnimation("next");
|
||||
store.scrollProjects("right");
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user