feat(settings): block interactions while animation is happening

This commit is contained in:
2026-02-08 20:19:40 +01:00
parent 4de10dd309
commit cfaf8c8d29
11 changed files with 83 additions and 2 deletions

View File

@@ -33,6 +33,8 @@ const achievements = useAchievementsStore();
const achievementsScreen = useAchievementsScreen();
const confirmationModal = useConfirmationModal();
const isAnimating = ref(true);
const SLIDE_OFFSET = 96;
const SLIDE_DURATION = 0.25;
const ARROW_SLIDE_DELAY = 0.15;
@@ -50,6 +52,7 @@ const obtainedRef =
const totalRef = useTemplateRef<InstanceType<typeof NumberInput>>("total");
const animateIntro = async () => {
isAnimating.value = true;
await Promise.all([
obtainedRef.value?.animateIntro(),
totalRef.value?.animateIntro(),
@@ -63,9 +66,11 @@ const animateIntro = async () => {
SLIDE_DURATION + ARROW_SLIDE_DELAY,
),
]);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await Promise.all([
obtainedRef.value?.animateOutro(),
totalRef.value?.animateOutro(),
@@ -94,11 +99,13 @@ onMounted(() => {
});
const handleCancel = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleReset = () => {
if (isAnimating.value) return;
confirmationModal.open({
text: $t("settings.clock.achievements.resetConfirmation"),
onConfirm: () => {
@@ -108,10 +115,12 @@ const handleReset = () => {
};
const handleVisitAll = () => {
if (isAnimating.value) return;
achievementsScreen.animateFadeToBlackIntro();
};
onClick((x, y) => {
if (isAnimating.value) return;
const viewAllRect = achievementAssets.viewAllButton.rect;
if (rectContains([127, 2, viewAllRect.width, viewAllRect.height], [x, y])) {
handleVisitAll();
@@ -119,6 +128,7 @@ onClick((x, y) => {
});
useKeyDown((key) => {
if (isAnimating.value) return;
if (key === "NDS_X") {
handleVisitAll();
}

View File

@@ -12,19 +12,24 @@ useIntervalFn(() => {
now.value = new Date();
}, 1000);
const isAnimating = ref(true);
const monthRef = useTemplateRef<InstanceType<typeof NumberInput>>("month");
const dayRef = useTemplateRef<InstanceType<typeof NumberInput>>("day");
const yearRef = useTemplateRef<InstanceType<typeof NumberInput>>("year");
const animateIntro = async () => {
isAnimating.value = true;
await Promise.all([
monthRef.value?.animateIntro(),
dayRef.value?.animateIntro(),
yearRef.value?.animateIntro(),
]);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await Promise.all([
monthRef.value?.animateOutro(),
dayRef.value?.animateOutro(),
@@ -37,11 +42,13 @@ onMounted(() => {
});
const handleCancel = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleConfirm = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};

View File

@@ -21,10 +21,13 @@ const animation = reactive({
opacity: 0,
});
const isAnimating = ref(true);
const hourRef = useTemplateRef<InstanceType<typeof NumberInput>>("hour");
const minuteRef = useTemplateRef<InstanceType<typeof NumberInput>>("minute");
const animateIntro = async () => {
isAnimating.value = true;
await Promise.all([
hourRef.value?.animateIntro(),
minuteRef.value?.animateIntro(),
@@ -33,9 +36,11 @@ const animateIntro = async () => {
.to(animation, { offsetY: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
.to(animation, { opacity: 1, duration: SLIDE_DURATION, ease: "none" }, 0),
]);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await Promise.all([
hourRef.value?.animateOutro(),
minuteRef.value?.animateOutro(),
@@ -55,11 +60,13 @@ onMounted(() => {
});
const handleCancel = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleConfirm = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};

View File

@@ -9,6 +9,7 @@ const { assets } = useAssets();
const { onRender } = useScreen();
const handleActivateB = () => {
if (isAnimating.value) return;
confirmationModal.open({
text: $t("settings.options.2048.quitConfirmation"),
onConfirm: () => {},
@@ -22,6 +23,7 @@ const handleActivateB = () => {
};
const handleActivateA = () => {
if (isAnimating.value) return;
if (isDead()) {
resetBoard();
return;
@@ -70,6 +72,8 @@ const SLIDE_DURATION = 0.25;
const SCORE_OFFSET = -20;
const SCORE_DURATION = 0.15;
const isAnimating = ref(true);
const intro = reactive({
frameOffsetY: SLIDE_OFFSET,
frameOpacity: 0,
@@ -137,6 +141,7 @@ const animateSpawnAll = () => {
};
const animateIntro = async () => {
isAnimating.value = true;
buildTilesFromBoard();
await gsap
@@ -156,9 +161,11 @@ const animateIntro = async () => {
{ scoreOffsetY: 0, duration: SCORE_DURATION, ease: "none" },
SLIDE_DURATION,
);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -530,6 +537,7 @@ const slide = (rowDir: number, colDir: number) => {
};
useKeyDown((key) => {
if (isAnimating.value) return;
switch (key) {
// TODO: remove this, testing only
case "n":

View File

@@ -26,6 +26,8 @@ const BUTTON_POSITIONS = [
[143, 128],
] as const;
const isAnimating = ref(true);
const { selected, selectorPosition } = useButtonNavigation({
buttons: {
english: [10, 27, 106, 41],
@@ -66,6 +68,7 @@ const { selected, selectorPosition } = useButtonNavigation({
left: "italian",
},
},
disabled: isAnimating,
selectorAnimation: {
duration: 0.1,
ease: "power2.out",
@@ -90,6 +93,7 @@ const animation = reactive({
});
const animateIntro = async () => {
isAnimating.value = true;
const timeline = gsap.timeline();
for (let i = 0; i < ROW_COUNT; i++) {
timeline
@@ -105,9 +109,11 @@ const animateIntro = async () => {
);
}
await timeline;
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
const timeline = gsap.timeline();
for (let i = 0; i < ROW_COUNT; i++) {
timeline
@@ -130,11 +136,13 @@ onMounted(() => {
});
const handleCancel = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleConfirm = () => {
if (isAnimating.value) return;
const selectedLocale = locales.value[BUTTON_KEYS.indexOf(selected.value)]!;
setLocale(selectedLocale.code);

View File

@@ -16,6 +16,8 @@ const BUTTON_STAGGER = 0.3;
const SLIDE_OFFSET = 96;
const SLIDE_DURATION = 0.25;
const isAnimating = ref(true);
const animation = reactive({
_3dMode: { headerOffsetY: HEADER_HEIGHT * 3, opacity: 0 },
_2dMode: { headerOffsetY: HEADER_HEIGHT * 3, opacity: 0 },
@@ -24,6 +26,7 @@ const animation = reactive({
});
const animateIntro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -46,9 +49,11 @@ const animateIntro = async () => {
},
BUTTON_STAGGER,
);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -77,6 +82,7 @@ const { selected, selectorPosition } = useButtonNavigation({
_3dMode: { down: "_2dMode" },
_2dMode: { up: "_3dMode" },
},
disabled: isAnimating,
selectorAnimation: {
ease: "none",
duration: 0.065,
@@ -84,11 +90,13 @@ const { selected, selectorPosition } = useButtonNavigation({
});
const handleCancel = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleConfirm = () => {
if (isAnimating.value) return;
const mode = selected.value === "_3dMode" ? "3d" : "2d";
app.setRenderingMode(mode);

View File

@@ -66,6 +66,8 @@ const highScore = useLocalStorage("taptap_high_score", 0);
let score = 0;
let isNewBest = false;
const isAnimating = ref(true);
const AREA_FADE_DURATION = 0.2;
const SCORE_OFFSET = -20;
const SCORE_DURATION = 0.15;
@@ -77,6 +79,7 @@ const animation = reactive({
});
const animateIntro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -89,9 +92,11 @@ const animateIntro = async () => {
{ scoreOffsetY: 0, duration: SCORE_DURATION, ease: "none" },
AREA_FADE_DURATION,
);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
targetX = 0;
targetY = LOGICAL_HEIGHT * 2 - 20;
@@ -119,6 +124,7 @@ onMounted(() => {
});
const handleActivateB = () => {
if (isAnimating.value) return;
if (state.value === "playing") {
state.value = "paused";
confirmationModal.open({
@@ -140,6 +146,7 @@ const handleActivateB = () => {
};
const handleActivateA = async () => {
if (isAnimating.value) return;
if (state.value === "playing") {
state.value = "paused";
confirmationModal.open({

View File

@@ -8,19 +8,24 @@ const BIRTHDAY_DAY = 25;
const BIRTHDAY_MONTH = 4;
const BIRTHDAY_YEAR = 2002;
const isAnimating = ref(true);
const monthRef = useTemplateRef<InstanceType<typeof NumberInput>>("month");
const dayRef = useTemplateRef<InstanceType<typeof NumberInput>>("day");
const yearRef = useTemplateRef<InstanceType<typeof NumberInput>>("year");
const animateIntro = async () => {
isAnimating.value = true;
await Promise.all([
monthRef.value?.animateIntro(),
dayRef.value?.animateIntro(),
yearRef.value?.animateIntro(),
]);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await Promise.all([
monthRef.value?.animateOutro(),
dayRef.value?.animateOutro(),
@@ -33,11 +38,13 @@ onMounted(() => {
});
const handleActivateB = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleActivateA = () => {
if (isAnimating.value) return;
const today = new Date();
const currentYear = today.getFullYear();

View File

@@ -19,6 +19,8 @@ const TEXT_FADE_DURATION = 0.15;
const SCORE_OFFSET = -20;
const SCORE_DURATION = 0.15;
const isAnimating = ref(true);
const intro = reactive({
boardOffsetY: BOARD_SLIDE_OFFSET,
boardOpacity: 0,
@@ -27,6 +29,7 @@ const intro = reactive({
});
const animateIntro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -49,9 +52,11 @@ const animateIntro = async () => {
{ scoreOffsetY: 0, duration: SCORE_DURATION, ease: "none" },
BOARD_SLIDE_DURATION + TEXT_FADE_DURATION,
);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -80,6 +85,7 @@ onMounted(() => {
});
const handleCancel = async () => {
if (isAnimating.value) return;
switch (state.value) {
case "alive": {
state.value = "pause";
@@ -109,6 +115,7 @@ const handleCancel = async () => {
};
const handleConfirm = () => {
if (isAnimating.value) return;
switch (state.value) {
case "alive": {
state.value = "pause";

View File

@@ -17,12 +17,15 @@ const OUTRO_DURATION = 0.25;
const ROW_STAGGER = SLIDE_DURATION + 0.075;
const ROW_COUNT = 2;
const isAnimating = ref(true);
const animation = reactive({
rowOffsetY: new Array<number>(ROW_COUNT).fill(SLIDE_OFFSET),
rowOpacity: new Array<number>(ROW_COUNT).fill(0),
});
const animateIntro = async () => {
isAnimating.value = true;
const timeline = gsap.timeline();
for (let i = 0; i < ROW_COUNT; i++) {
timeline
@@ -38,9 +41,11 @@ const animateIntro = async () => {
);
}
await timeline;
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
const timeline = gsap.timeline();
for (let i = 0; i < ROW_COUNT; i++) {
timeline
@@ -63,11 +68,13 @@ onMounted(() => {
});
const handleCancel = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const handleConfirm = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};

View File

@@ -85,6 +85,8 @@ const SLIDE_DURATION = 0.25;
const ARROW_SLIDE_DELAY = 0.15;
const ARROW_SLIDE_DURATION = 0.15;
const isAnimating = ref(true);
const animation = reactive({
offsetY: SLIDE_OFFSET,
opacity: 0,
@@ -93,6 +95,7 @@ const animation = reactive({
});
const animateIntro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(animation, { offsetY: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
@@ -107,9 +110,11 @@ const animateIntro = async () => {
{ downArrowOffsetY: 0, duration: ARROW_SLIDE_DURATION, ease: "none" },
SLIDE_DURATION + ARROW_SLIDE_DELAY,
);
isAnimating.value = false;
};
const animateOutro = async () => {
isAnimating.value = true;
await gsap
.timeline()
.to(
@@ -208,7 +213,7 @@ onRender((ctx) => {
}, 10);
useKeyDown((key) => {
if (!props.selected || props.disabled) return;
if (isAnimating.value || !props.selected || props.disabled) return;
switch (key) {
case "NDS_UP":
increase();
@@ -220,7 +225,7 @@ useKeyDown((key) => {
});
onClick((x, y) => {
if (props.disabled) return;
if (isAnimating.value || props.disabled) return;
if (
rectContains(
[props.x, Y, upImage.value.rect.width, ARROW_IMAGE_HEIGHT],