Hey there! Today we’re going to dive into one of my favorite features in NativeScript – shared element transitions. If you’ve ever wanted to create those smooth, polished animations where an element seems to flow from one screen to another, you’re in the right place.
What Exactly Are Shared Element Transitions?
Think of shared element transitions as a way to maintain visual continuity between screens. Instead of elements just appearing or disappearing, they smoothly animate from their position on one screen to their position on the next screen.
A great example is our book app in this video – when you tap on a book cover in the list, that same cover image animates smoothly to its larger position on the detail page. It’s these little touches that make apps feel premium and polished.
TL;DR; Full source code for this project can be found at: https://github.com/NewbieScripterRepo/sharedtransition-example
What You’ll Need
Before we get started, make sure you have:
- A NativeScript-Vue project (version 3+)
@nativescript/core
version 8.5.0 or higher (this is where the shared transition magic happens)- NativeScript CLI installed (
npm install -g nativescript
)
Let’s Get This App Running
Ready to see it in action? Run one of these commands:
# For iOS
ns run ios
# For Android
ns run android
Step 1: Setting Up Our Book Data
First, let’s create some sample data for our books. Create a new file src/data/books.ts
:
export interface Book {
id: number;
title: string;
author: string;
description: string;
coverImage: string;
}
export const books: Book[] = [
{
id: 1,
title: "The Great Adventure",
author: "John Smith",
description:
"An epic journey through unknown lands and mystical creatures. This book will take you on a thrilling ride that you'll never forget.",
coverImage: "~/src/imgs/1.jpg",
},
{
id: 2,
title: "Mystery of the Old House",
author: "Emily Johnson",
description:
"A detective story that unravels the secrets of an abandoned mansion. With twists and turns at every chapter, you'll be guessing until the very end.",
coverImage: "~/src/imgs/2.jpg",
},
{
id: 3,
title: "Journey to the Stars",
author: "Michael Brown",
description:
"A science fiction novel about space exploration and alien encounters. Discover new worlds and meet fascinating creatures from distant galaxies.",
coverImage: "~/src/imgs/3.jpg",
},
{
id: 4,
title: "Romance in Paris",
author: "Sophie Davis",
description:
"A heartwarming love story set in the city of lights. Follow the journey of two souls finding each other in the most unexpected way.",
coverImage: "~/src/imgs/4.jpg",
},
{
id: 5,
title: "The Secret Garden",
author: "Olivia Wilson",
description:
"A tale of discovery and healing in a magical garden. This story will touch your heart and remind you of the beauty of nature.",
coverImage: "~/src/imgs/5.jpg",
},
];
Step 2: Basic Styling
Let’s add some basic styling to make our app look nice. Update your src/app.css
file:
ActionBar {
background-color: #65adf1;
color: white;
}
Step 3: Creating the Book List Screen
Now let’s build our main screen that shows all the books. Create a new component src/components/Home.vue
:
<script lang="ts" setup>
// ... imports and navigation function ...
</script>
<template>
<Frame>
<Page>
<ActionBar>
<Label text="Book Library" class="font-bold text-lg" />
</ActionBar>
<ScrollView>
<StackLayout class="p-4">
<GridLayout
v-for="book in books"
:key="book.id"
rows="auto"
columns="auto, *"
class="p-2 mb-4 bg-white rounded-lg border-b"
@tap="navigateToBookDetail(book)"
>
<!-- Book cover with shared transition tag -->
<Image
:src="book.coverImage"
:sharedTransitionTag="`cover-image-${book.id}`"
class="w-16 h-24 rounded-lg mr-4"
stretch="aspectFill"
/>
<StackLayout col="1" class="justify-center">
<Label :text="book.title" class="text-lg font-bold" />
<Label :text="book.author" class="text-gray-600" />
</StackLayout>
</GridLayout>
</StackLayout>
</ScrollView>
</Page>
</Frame>
</template>
The key thing to notice here is the sharedTransitionTag
attribute on our Image component. We’re giving each book cover a unique tag using cover-image-${book.id}
. This ensures that when we tap on a specific book, NativeScript knows exactly which element to animate.
Step 4: Making It Navigate
We need to add the navigation logic to our script section in Home.vue
:
import { SharedTransition, PageTransition } from "@nativescript/core";
function navigateToBookDetail(book: Book) {
$navigateTo(BookDetail, {
props: {
book: book,
},
transition: SharedTransition.custom(new PageTransition(500)),
});
}
This is where the magic happens! We’re telling NativeScript to use a shared transition when navigating to the detail page. The SharedTransition.custom(new PageTransition(500))
part is what enables the smooth animation with a 500ms duration. Note that the duration parameter is optional – you can omit it to use platform defaults. Interestingly, the default duration for Android is already 500ms, while for iOS it’s CORE_ANIMATION_DEFAULTS.duration which is 350ms. So specifying 500ms makes the transition duration consistent across both platforms!
Step 5: Building the Detail Screen
Create a new component src/components/BookDetail.vue
for our book details:
<script lang="ts" setup>
// ... imports and goBack function ...
</script>
<template>
<Page actionBarHidden="true">
<GridLayout rows="auto, *" class="bg-gray-100">
<!-- Back button -->
<Label
text="← Back"
@tap="goBack"
class="text-lg p-4 font-bold text-blue-500"
/>
<!-- Book detail card -->
<ScrollView row="1">
<StackLayout class="p-4">
<GridLayout rows="auto, auto, auto" class="bg-white rounded-2xl p-6">
<!-- Book cover with shared transition tag -->
<Image
:src="book.coverImage"
:sharedTransitionTag="`cover-image-${book.id}`"
class="w-48 rounded-xl mb-6 self-center"
stretch="aspectFill"
/>
<StackLayout row="1" class="mb-6">
<Label
:text="book.title"
class="text-2xl font-bold text-center mb-2"
/>
<Label
:text="`by ${book.author}`"
class="text-gray-600 text-center text-lg"
/>
</StackLayout>
<StackLayout row="2">
<Label
:text="book.description"
class="text-gray-700"
textWrap="true"
/>
</StackLayout>
</GridLayout>
</StackLayout>
</ScrollView>
</GridLayout>
</Page>
</template>
Notice how we’re using the exact same sharedTransitionTag
value (cover-image-${book.id}
) here? This is crucial – it tells NativeScript which elements should be animated together during the transition.
Step 6: Adding the Back Navigation
Don’t forget to add the back navigation in the script section of BookDetail.vue
:
import { SharedTransition, PageTransition } from "@nativescript/core";
function goBack() {
$navigateBack({
transition: SharedTransition.custom(new PageTransition()),
});
}
How Does This Magic Work?
Here’s what happens when you tap on a book:
- NativeScript sees the Image elements with matching
sharedTransitionTag
values - It calculates the position, size, and other properties of the element on both screens
- It creates a smooth animation that transforms the element from its position in the list view to its position in the detail view
- When you go back, the same process happens in reverse
What’s particularly impressive is how much easier this is in NativeScript compared to implementing shared element transitions natively. In Android, you’d need to write complex transition code with SharedElementCallback and handle multiple edge cases. In iOS, you’d need to work with UINavigationControllerDelegate and create custom transition animations. With NativeScript, it’s just a matter of adding a sharedTransitionTag
attribute and using SharedTransition.custom()
– the framework handles all the complex animation logic for you!
Important Things to Remember
- Unique Tags: Each shared transition tag must be unique within a single page. That’s why we use
cover-image-${book.id}
instead of justcover-image
- Matching Tags: The same tag must be used on both pages for elements you want to transition
- Image Dimensions: Make sure your images have valid dimensions for the best results
- Supported Elements: Shared transitions work best with Image and other visual components (avoid using them on Label components)
The beauty of NativeScript’s implementation is that it handles all the complex coordinate calculations and animation synchronization automatically. In native development, you would need to manually coordinate the positions and transformations between the two views, but NativeScript abstracts all of this complexity away from you.
Troubleshooting Tips
If your transitions aren’t working as expected:
- Make sure both elements have the exact same
sharedTransitionTag
value (e.g., “cover-image-1” for the first book) - Ensure you’re using
SharedTransition.custom(new PageTransition())
in your navigation - Check that your
@nativescript/core
version is 8.5.0 or higher - Verify that both elements are visible and have valid dimensions
- Confirm that each tag is unique within each page
That’s a Wrap!
Shared element transitions are one of those features that can really elevate your app’s user experience. With just a few lines of code, you can create smooth, engaging animations that make your app feel polished and professional.
The key is using the sharedTransitionTag
attribute to mark elements that should be animated together, and using SharedTransition.custom(new PageTransition())
when navigating between pages.
You can find more example usage on NativeSript snacks – there are alot of SharedTransition snack you can explore on. Try implementing this in your own projects and see how it transforms the feel of your app. Once you start using shared transitions, you’ll wonder how you ever lived without them!
Happy coding!