Lorem Ipsum Dolor Sit Amet
Nisi deserunt exercitation elit officia labore adipisicing. Ex ea culpa in ullamco eu laboris Lorem sit magna quis officia.
This module makes it easier to provide meaningful cross-browser transitions between pages — or other state changes if desired — for Vue projects. It provides a single opinionated transition that can animate between pages in two ways:
An element can be designated for transitioning like this:
<template>
<div>
<img
src="..."
v-shared-element="{ id: post.slug, role: 'image', type: 'post' }"
/>
<!--
The above directive can be used on any descendent of the
`<ContextualTransition>` -- any component or element, but see
the "Limitations & Tips" page.
-->
</div>
</template>
TIP
This functionality should not be confused with the experimental Chrome feature, View Transitions developed along with this draft specification which was once called "shared element transitions." Nuxt has experimental support for View Transitions.
Nisi deserunt exercitation elit officia labore adipisicing. Ex ea culpa in ullamco eu laboris Lorem sit magna quis officia.
A page view can be designated for transitioning like this:
<template>
<div v-relative-slide="{ value: post.sortOrder, type: 'post' }">
<!--
This container is the view that will be transitioning, i.e.,
the entering or exiting child of the
`<ContextualTransition>`. The container's contents go here.
-->
</div>
</template>
In both cases, the animation is determined by simple directives which declare the relationships the page and certain elements on the page have with other pages.
Although we are differentiating these transitions, they are really two parts of the same transition triggered under different conditions. Typically both parts won't be triggered at the same time, but the same content may trigger either part depending on its relationship to the content the user is navigating to or from.
Note that, unlike these demos, the typical use of this module will be for transitioning RouterView
s.
<template>
<div class="simple-demo">
<ContextualTransition group="demo">
<div v-if="selectedPost === undefined" class="thumbnails">
<a
v-for="post in posts"
:key="post.slug"
@click="toggleExpanded(post.slug)"
>
<div
v-shared-element="{
id: post.slug,
role: 'img',
}"
class="thumbnail"
:style="`background-color: ${post.color};`"
/>
<p
v-shared-element="{
id: post.slug,
role: 'title',
}"
>
{{ post.title }}
</p>
</a>
</div>
<div v-else class="post">
<button class="close" @click="toggleExpanded">×</button>
<div
v-shared-element="{
id: selectedPost.slug,
role: 'img',
}"
class="header"
:style="`background-color: ${selectedPost.color};`"
/>
<h3
v-shared-element="{
id: selectedPost.slug,
role: 'title',
}"
>
{{ selectedPost.title }}
</h3>
<div class="text" v-html="selectedPost.content" />
</div>
</ContextualTransition>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import posts from '../data/sampleData';
const expanded = ref<string | false>(false);
function toggleExpanded(slug: string | false) {
if (slug !== false) {
expanded.value = slug;
} else {
expanded.value = false;
}
}
const selectedPost = computed(() => {
return posts.find((p) => p.slug === expanded.value);
});
</script>
<style></style>
<template>
<div class="simple-demo">
<ContextualTransition group="demo2" :duration="500">
<div
:key="selectedPost.slug"
v-relative-slide="{ value: selectedPost.index, type: 'posts' }"
class="post"
>
<div
class="header"
:style="`background-color: ${selectedPost.color};`"
/>
<h3>{{ selectedPost.title }}</h3>
<div class="text" v-html="selectedPost.content" />
</div>
</ContextualTransition>
<button v-if="selectedPost.index > 0" style="float: left" @click="previous">
Previous
</button>
<button
v-if="selectedPost.index < posts.length - 1"
style="float: right"
@click="next"
>
Next
</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import posts from '../data/sampleData';
const expanded = ref<string>(posts[0].slug);
function setActive(slug: string) {
expanded.value = slug;
}
function next() {
const i = posts.findIndex((p) => p.slug === expanded.value);
if (i >= 0 && i < posts.length - 1) {
setActive(posts[i + 1].slug);
}
}
function previous() {
const i = posts.findIndex((p) => p.slug === expanded.value);
if (i >= 1) {
setActive(posts[i - 1].slug);
}
}
const selectedPost = computed(() => {
const index = posts.findIndex((p) => p.slug === expanded.value);
if (index >= 0) {
return {
...posts[index],
index,
};
} else {
return undefined;
}
});
</script>
<style></style>