- 개발
스벨트로 페이지 트랜지션 구현하기 - 3
2023년 04월 27일 좋아요 0 댓글 0 조회수 399
벌써 스벨트로 페이지 트랜지션 구현하기! 마지막 단계입니다.
이전 글에서 페이지 이동과 히어로 트랜지션에 구현방법에 대해 설명했는데요. 이번에는 그 둘을 섞어서 활용하는 방법을 설명하겠습니다.
먼저 예시 화면을 보시죠!
위 화면을 보시면, 기존 페이지 이동에는 슬라이드 애니메이션을, 히어로 애니메이션이 적용되는 heros → heros/[color]
에는 히어로 애니메이션이 적용되었음을 알 수 있습니다.
히어로 애니메이션이 적용될때는 슬라이드 애니메이션이 동작하지 않아야 합니다. 따라서 +layout.svelte
에서 다음과 같은 코드를 작성해 줍시다.
참고로 폴더 구조는 이렇습니다..
+layout.svelte
<script lang="ts">
import { beforeNavigate } from '$app/navigation';
import { page } from '$app/stores';
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
const isPageTransitionDisabled = writable(true);
setContext('transition-disabled', isPageTransitionDisabled);
beforeNavigate(({ from, to }) => {
if (from == null || to == null) return;
// hero 에니메이션 동작할때는 트랜지션 적용하지 않음
if (from.url.pathname.includes('heros') && to.url.pathname.includes('heros')) {
isPageTransitionDisabled.set(true);
} else {
isPageTransitionDisabled.set(false);
}
});
</script>
<header>
<a href="/">/</a>
<a href="/a">a</a>
<a href="/b">b</a>
<a href="/heros">heros</a>
</header>
<div>
current path: {$page.url.pathname}
</div>
<div class="transition-root">
<slot />
</div>
<style>
.transition-root {
position: relative;
overflow: hidden;
}
header {
display: flex;
font-size: 24px;
gap: 8px;
}
</style>
isPageTranstionDisabled
란 store를 통해, /hero → /hero/[color] 일때는 슬라이드 애니메이션을 disabled 합니다. Transition.svelte
를 보면 해당 값을 활용함을 알 수 있습니다.
Transition.svelte
<script lang="ts">
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import type { TransitionConfig } from 'svelte/types/runtime/transition';
const disabled = getContext<Writable<boolean>>('transition-disabled');
export function fadeIn(node: Element): TransitionConfig {
const offset = window.innerWidth;
if ($disabled) {
return {
duration: 0,
css: () => ``
};
}
return {
duration: 600,
css: (t: number, u: number) => `transform: translateX(${offset * u}px);`
};
}
export function fadeOut(node: Element): TransitionConfig {
const offset = window.innerWidth;
if ($disabled) {
return {
duration: 0,
css: () => `opacity: 0; position:absolute; top:0; left:0; width:100%; z-index: -1;`
};
}
return {
duration: $disabled ? 0 : 600,
css: (t: number, u: number) =>
`transform: translateX(${
-offset * u
}px); position:absolute; top:0; left:0; width:100%; z-index: -1;`
};
}
</script>
<div in:fadeIn out:fadeOut>
<slot />
</div>
Transition.svelte는 다음과 같이 사용합니다.
/rouets/a/+page.svelte
<Transition>
<div class="font container">A</div>
</Transition>
/rouets/heros/+page.svelte
<script lang="ts">
import { heroIn, heroOut } from '$lib/hero';
import Transition from '$lib/Transition.svelte';
const colors = ['red', 'green', 'blue', 'coral', 'orange', 'black'];
</script>
<Transition >
<div class="container">
{#each colors as color}
<a
href="/heros/{color}"
style="background-color: {color};"
class="box"
in:heroIn={{ key: color }}
out:heroOut={{ key: color }}
>
{color}
</a>
{/each}
</div>
</Transition>
/rouets/heros/[color]/+page.svelte
<script lang="ts">
import { page } from '$app/stores';
import { heroIn, heroOut } from '$lib/hero';
import Transition from '$lib/Transition.svelte';
const color = $page.params.color;
</script>
<Transition>
<div class="container">
<div
in:heroIn={{ key: color }}
out:heroOut={{ key: color }}
class="box"
style="background-color: {color};"
>
<a href="/heros"> Back </a>
</div>
</div>
</Transition>
스벨트로 페이지 트랜지션 구현하기 - 2
에서 설명한 heroRoot가 여기에서는 쓰이지 않았음을 유심히 봐주세요.
Transition.svelte의 pageOut 덕분에 heroRoot를 따로 설정하지 않습니다.
코드 전문은 아래 깃헙 소스를 통해 확인해주세요
글을 마치며
사실 저걸 구현하기까지 정말 많은 우여곡절이 있었습니다. 스벨트킷 버전이 낮았을때에는 위의 코드는 버그 투성이었습니다. (애니메이션이 끝났는데도 돔이 사라지지 않는 등..)
하지만 이제! 스벨트킷 1.0.0 이 등장했기 때문에 저런 tricky한 코드도 이제 실제 제품에 적용할 수 있지 않을까 기대해 봅니다. ㅎㅎ
현재 코지 블로그
도 이와 같은 방법을 이용해 페이지 트랜지션을 구현했는데요. 위 예시보다 더 다양한 페이지 애니메이션 조건을 사용했습니다! (모바일과 웹의 트랜지션 애니메이션 구분, posts → posts/{post-title} 로 이동 할때 슬라이드 애니메이션 적용 등)
그럼.. 스벨트 붐은 온다 스붐온!