feat(settings/options/language): intro and outro animation
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import gsap from "gsap";
|
||||
|
||||
const { locales, locale, setLocale } = useI18n();
|
||||
const store = useSettingsStore();
|
||||
const confirmationModal = useConfirmationModal();
|
||||
@@ -70,7 +72,65 @@ const { selected, selectorPosition } = useButtonNavigation({
|
||||
},
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
const SLIDE_OFFSET = 48;
|
||||
const SLIDE_DURATION = 0.15;
|
||||
const OUTRO_OFFSET = 96;
|
||||
const OUTRO_DURATION = 0.25;
|
||||
const ROW_STAGGER = SLIDE_DURATION + 0.075;
|
||||
const ROW_COUNT = 3;
|
||||
|
||||
const selectedRow = computed(() => {
|
||||
const index = BUTTON_KEYS.indexOf(selected.value);
|
||||
return Math.floor(index / 2);
|
||||
});
|
||||
|
||||
const animation = reactive({
|
||||
rowOffsetY: Array(ROW_COUNT).fill(SLIDE_OFFSET) as number[],
|
||||
rowOpacity: Array(ROW_COUNT).fill(0) as number[],
|
||||
});
|
||||
|
||||
const animateIntro = async () => {
|
||||
const timeline = gsap.timeline();
|
||||
for (let i = 0; i < ROW_COUNT; i++) {
|
||||
timeline
|
||||
.to(
|
||||
animation.rowOffsetY,
|
||||
{ [i]: 0, duration: SLIDE_DURATION, ease: "none" },
|
||||
i * ROW_STAGGER,
|
||||
)
|
||||
.to(
|
||||
animation.rowOpacity,
|
||||
{ [i]: 1, duration: SLIDE_DURATION, ease: "none" },
|
||||
i * ROW_STAGGER,
|
||||
);
|
||||
}
|
||||
await timeline;
|
||||
};
|
||||
|
||||
const animateOutro = async () => {
|
||||
const timeline = gsap.timeline();
|
||||
for (let i = 0; i < ROW_COUNT; i++) {
|
||||
timeline
|
||||
.to(
|
||||
animation.rowOffsetY,
|
||||
{ [i]: OUTRO_OFFSET, duration: OUTRO_DURATION, ease: "none" },
|
||||
0,
|
||||
)
|
||||
.to(
|
||||
animation.rowOpacity,
|
||||
{ [i]: 0, duration: OUTRO_DURATION, ease: "none" },
|
||||
0,
|
||||
);
|
||||
}
|
||||
await timeline;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
animateIntro();
|
||||
});
|
||||
|
||||
const handleCancel = async () => {
|
||||
await animateOutro();
|
||||
store.closeSubMenu();
|
||||
};
|
||||
|
||||
@@ -92,7 +152,10 @@ const handleConfirm = () => {
|
||||
{},
|
||||
{ locale: selectedLocale.code },
|
||||
),
|
||||
onClosed: () => store.closeSubMenu(),
|
||||
onClosed: async () => {
|
||||
await animateOutro();
|
||||
store.closeSubMenu();
|
||||
},
|
||||
timeout: 2000,
|
||||
});
|
||||
};
|
||||
@@ -102,18 +165,25 @@ onRender((ctx) => {
|
||||
ctx.fillStyle = "#010101";
|
||||
|
||||
for (let i = 0; i < locales.value.length; i += 1) {
|
||||
const row = Math.floor(i / 2);
|
||||
const [x, y] = BUTTON_POSITIONS[i]!;
|
||||
const isSelected = selected.value === BUTTON_KEYS[i];
|
||||
const buttonImage = isSelected
|
||||
? assets.images.settings.bottomScreen.options.languageButtonActive
|
||||
: assets.images.settings.bottomScreen.options.languageButton;
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = animation.rowOpacity[row]!;
|
||||
ctx.translate(0, animation.rowOffsetY[row]!);
|
||||
|
||||
buttonImage.draw(ctx, x, y, isSelected ? { colored: true } : undefined);
|
||||
const name = locales.value[i]?.name;
|
||||
if (!name) {
|
||||
throw new Error(`Missing locale or locale name: ${BUTTON_KEYS[i]}`);
|
||||
}
|
||||
fillTextHCentered(ctx, name, x, y + 20, 96);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,5 +201,13 @@ defineOptions({
|
||||
@activate-a="handleConfirm"
|
||||
/>
|
||||
|
||||
<CommonButtonSelector :rect="selectorPosition" />
|
||||
<CommonButtonSelector
|
||||
:rect="[
|
||||
selectorPosition[0],
|
||||
selectorPosition[1] + animation.rowOffsetY[selectedRow]!,
|
||||
selectorPosition[2],
|
||||
selectorPosition[3],
|
||||
]"
|
||||
:opacity="animation.rowOpacity[selectedRow]!"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user