feat(contact): intro animation
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 145 KiB |
BIN
app/assets/images/contact/bottom-screen/bottom-bar.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
app/assets/images/contact/bottom-screen/buttons.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
app/assets/images/contact/bottom-screen/top-bar.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
BIN
app/assets/images/contact/top-screen/left-bar-things.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/assets/images/contact/top-screen/left-bar.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/assets/images/contact/top-screen/title.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
@@ -5,15 +5,17 @@ const props = withDefaults(
|
|||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
animationSpeed?: number;
|
opacity?: number;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
animationSpeed: 0.25,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const cornerImage = useTemplateRef("cornerImage");
|
const cornerImage = useTemplateRef("cornerImage");
|
||||||
|
|
||||||
|
const ANIMATION_SPEED = 0.25;
|
||||||
|
|
||||||
let currentX = props.x;
|
let currentX = props.x;
|
||||||
let currentY = props.y;
|
let currentY = props.y;
|
||||||
let currentWidth = props.width;
|
let currentWidth = props.width;
|
||||||
@@ -38,10 +40,10 @@ useRender((ctx) => {
|
|||||||
currentWidth = props.width;
|
currentWidth = props.width;
|
||||||
currentHeight = props.height;
|
currentHeight = props.height;
|
||||||
} else {
|
} else {
|
||||||
currentX += dx * props.animationSpeed;
|
currentX += dx * ANIMATION_SPEED;
|
||||||
currentY += dy * props.animationSpeed;
|
currentY += dy * ANIMATION_SPEED;
|
||||||
currentWidth += dw * props.animationSpeed;
|
currentWidth += dw * ANIMATION_SPEED;
|
||||||
currentHeight += dh * props.animationSpeed;
|
currentHeight += dh * ANIMATION_SPEED;
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = Math.floor(currentX);
|
const x = Math.floor(currentX);
|
||||||
@@ -49,6 +51,8 @@ useRender((ctx) => {
|
|||||||
const w = Math.floor(currentWidth);
|
const w = Math.floor(currentWidth);
|
||||||
const h = Math.floor(currentHeight);
|
const h = Math.floor(currentHeight);
|
||||||
|
|
||||||
|
ctx.globalAlpha = props.opacity;
|
||||||
|
|
||||||
ctx.drawImage(cornerImage.value, x, y);
|
ctx.drawImage(cornerImage.value, x, y);
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|||||||
@@ -1,16 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const backgroundImage = useTemplateRef("backgroundImage");
|
const store = useContactStore();
|
||||||
|
|
||||||
|
const homeBackgroundImage = useTemplateRef("homeBackgroundImage");
|
||||||
|
const contactBackgroundImage = useTemplateRef("contactBackgroundImage");
|
||||||
|
|
||||||
|
callOnce("contactIntro", store.animateIntro);
|
||||||
|
|
||||||
useRender((ctx) => {
|
useRender((ctx) => {
|
||||||
if (!backgroundImage.value) return;
|
if (!homeBackgroundImage.value || !contactBackgroundImage.value) return;
|
||||||
|
|
||||||
ctx.drawImage(backgroundImage.value, 0, 0);
|
if (store.isIntro) {
|
||||||
|
ctx.drawImage(homeBackgroundImage.value, 0, 0);
|
||||||
|
ctx.globalAlpha = store.intro.stage2Opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.drawImage(contactBackgroundImage.value, 0, 0);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img
|
<img
|
||||||
ref="backgroundImage"
|
ref="homeBackgroundImage"
|
||||||
|
src="/assets/images/home/bottom-screen/background.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
ref="contactBackgroundImage"
|
||||||
src="/assets/images/contact/bottom-screen/background.png"
|
src="/assets/images/contact/bottom-screen/background.png"
|
||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
|
|||||||
32
app/components/Contact/BottomScreen/Bars.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const store = useContactStore();
|
||||||
|
|
||||||
|
const topBarImage = useTemplateRef("topBarImage");
|
||||||
|
const bottomBarImage = useTemplateRef("bottomBarImage");
|
||||||
|
|
||||||
|
useRender((ctx) => {
|
||||||
|
if (!topBarImage.value || !bottomBarImage.value) return;
|
||||||
|
|
||||||
|
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
|
||||||
|
|
||||||
|
ctx.drawImage(topBarImage.value, 0, store.isIntro ? store.intro.topBarY : 0);
|
||||||
|
ctx.drawImage(
|
||||||
|
bottomBarImage.value,
|
||||||
|
0,
|
||||||
|
store.isIntro ? store.intro.bottomBarY : SCREEN_HEIGHT - 24,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
ref="topBarImage"
|
||||||
|
src="/assets/images/contact/bottom-screen/top-bar.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
ref="bottomBarImage"
|
||||||
|
src="/assets/images/contact/bottom-screen/bottom-bar.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Background from "./Background.vue";
|
import Background from "./Background.vue";
|
||||||
|
import Buttons from "./Buttons.vue";
|
||||||
import ButtonSelector from "~/components/Common/ButtonSelector.vue";
|
import ButtonSelector from "~/components/Common/ButtonSelector.vue";
|
||||||
|
import Bars from "./Bars.vue";
|
||||||
|
|
||||||
|
const store = useContactStore();
|
||||||
|
|
||||||
const { selectorPosition } = useButtonNavigation({
|
const { selectorPosition } = useButtonNavigation({
|
||||||
buttons: {
|
buttons: {
|
||||||
@@ -35,10 +39,14 @@ const { selectorPosition } = useButtonNavigation({
|
|||||||
<template>
|
<template>
|
||||||
<Background />
|
<Background />
|
||||||
|
|
||||||
|
<Buttons />
|
||||||
<ButtonSelector
|
<ButtonSelector
|
||||||
:x="selectorPosition[0]"
|
:x="selectorPosition[0]"
|
||||||
:y="selectorPosition[1]"
|
:y="selectorPosition[1]"
|
||||||
:width="selectorPosition[2]"
|
:width="selectorPosition[2]"
|
||||||
:height="selectorPosition[3]"
|
:height="selectorPosition[3]"
|
||||||
|
:opacity="store.isIntro ? store.intro.stage3Opacity : 1"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Bars />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
20
app/components/Contact/BottomScreen/Buttons.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const store = useContactStore();
|
||||||
|
|
||||||
|
const buttonsImage = useTemplateRef("buttonsImage");
|
||||||
|
|
||||||
|
useRender((ctx) => {
|
||||||
|
if (!buttonsImage.value) return;
|
||||||
|
|
||||||
|
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
|
||||||
|
ctx.drawImage(buttonsImage.value, 31, 32);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
ref="buttonsImage"
|
||||||
|
src="/assets/images/contact/bottom-screen/buttons.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -1,16 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const backgroundImage = useTemplateRef("backgroundImage");
|
const store = useContactStore();
|
||||||
|
|
||||||
|
const homeBackgroundImage = useTemplateRef("homeBackgroundImage");
|
||||||
|
const contactBackgroundImage = useTemplateRef("contactBackgroundImage");
|
||||||
|
|
||||||
useRender((ctx) => {
|
useRender((ctx) => {
|
||||||
if (!backgroundImage.value) return;
|
if (!homeBackgroundImage.value || !contactBackgroundImage.value) return;
|
||||||
|
|
||||||
ctx.drawImage(backgroundImage.value, 0, 0);
|
ctx.drawImage(contactBackgroundImage.value, 0, 0);
|
||||||
|
|
||||||
|
if (store.isIntro) {
|
||||||
|
ctx.drawImage(homeBackgroundImage.value, 0, 0);
|
||||||
|
ctx.globalAlpha = store.intro.stage2Opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.drawImage(contactBackgroundImage.value, 0, 0);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img
|
<img
|
||||||
ref="backgroundImage"
|
ref="homeBackgroundImage"
|
||||||
|
src="/assets/images/home/top-screen/background.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
ref="contactBackgroundImage"
|
||||||
src="/assets/images/contact/top-screen/background.png"
|
src="/assets/images/contact/top-screen/background.png"
|
||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
|
|||||||
29
app/components/Contact/TopScreen/LeftBar.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const store = useContactStore();
|
||||||
|
|
||||||
|
const backgroundImage = useTemplateRef("backgroundImage");
|
||||||
|
const thingsImage = useTemplateRef("thingsImage");
|
||||||
|
|
||||||
|
useRender((ctx) => {
|
||||||
|
if (!backgroundImage.value || !thingsImage.value) return;
|
||||||
|
|
||||||
|
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
|
||||||
|
ctx.drawImage(backgroundImage.value, 0, 0);
|
||||||
|
|
||||||
|
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
|
||||||
|
ctx.drawImage(thingsImage.value, 0, 0);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
ref="backgroundImage"
|
||||||
|
src="/assets/images/contact/top-screen/left-bar.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
ref="thingsImage"
|
||||||
|
src="/assets/images/contact/top-screen/left-bar-things.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
24
app/components/Contact/TopScreen/Title.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const store = useContactStore();
|
||||||
|
|
||||||
|
const titleImage = useTemplateRef("titleImage");
|
||||||
|
|
||||||
|
useRender((ctx) => {
|
||||||
|
if (!titleImage.value) return;
|
||||||
|
|
||||||
|
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
|
||||||
|
ctx.drawImage(
|
||||||
|
titleImage.value,
|
||||||
|
21,
|
||||||
|
store.isIntro ? store.intro.titleY : SCREEN_HEIGHT - 23,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
ref="titleImage"
|
||||||
|
src="/assets/images/contact/top-screen/title.png"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Background from "./Background.vue";
|
import Background from "./Background.vue";
|
||||||
|
import LeftBar from "./LeftBar.vue";
|
||||||
|
import Title from "./Title.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Background />
|
<Background />
|
||||||
|
|
||||||
|
<LeftBar />
|
||||||
|
<Title />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
74
app/stores/contact.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import gsap from "gsap";
|
||||||
|
|
||||||
|
export const useContactStore = defineStore("contact", {
|
||||||
|
state: () => ({
|
||||||
|
intro: {
|
||||||
|
stage1Opacity: 0,
|
||||||
|
stage2Opacity: 0,
|
||||||
|
stage3Opacity: 0,
|
||||||
|
|
||||||
|
titleY: SCREEN_HEIGHT,
|
||||||
|
|
||||||
|
topBarY: -20,
|
||||||
|
bottomBarY: SCREEN_HEIGHT + 20,
|
||||||
|
},
|
||||||
|
|
||||||
|
isIntro: false,
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
animateIntro() {
|
||||||
|
this.isIntro = true;
|
||||||
|
|
||||||
|
const start = 3;
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
this.intro,
|
||||||
|
{
|
||||||
|
stage1Opacity: 0,
|
||||||
|
titleY: SCREEN_HEIGHT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage1Opacity: 1,
|
||||||
|
titleY: SCREEN_HEIGHT - 23,
|
||||||
|
duration: 0.1,
|
||||||
|
delay: start,
|
||||||
|
ease: "none",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
this.intro,
|
||||||
|
{
|
||||||
|
stage2Opacity: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage2Opacity: 1,
|
||||||
|
duration: 0.1,
|
||||||
|
delay: start + 0.15,
|
||||||
|
ease: "none",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
this.intro,
|
||||||
|
{
|
||||||
|
stage3Opacity: 0,
|
||||||
|
topBarY: -20,
|
||||||
|
bottomBarY: SCREEN_HEIGHT - 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stage3Opacity: 1,
|
||||||
|
topBarY: 0,
|
||||||
|
bottomBarY: SCREEN_HEIGHT - 24,
|
||||||
|
duration: 0.1,
|
||||||
|
delay: start + 0.15 + 0.15,
|
||||||
|
ease: "none",
|
||||||
|
onComplete: () => {
|
||||||
|
this.isIntro = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||