Motion+

Shared view animation

Animate shared elements between states using View Transitions API via Motion's view() function.

JavaScript
Tutorial time
5 min
Difficulty
>Live exampleOpen

Tutorial

Introduction

The Shared view animation example shows how to create smooth animations between different UI states using the browser's View Transitions API through Motion. When clicking between tabs, the icon morphs seamlessly and the underline slides smoothly to the new position.

This example uses press from Motion to listen for clicks on the tabs, and animateView to animate the DOM changes using the browser's native View Transitions API.

The View Transitions API lets browsers automatically animate elements that share the same view-transition-name CSS property. Motion's animateView function wraps this API, making it simple to trigger these animations whenever your UI changes.

Get started

Let's start by creating the basic tab navigation structure:

<div class="container">
    <nav>
        <ul class="tabs">
            <li class="tab selected" data-tab="tomato">🍅 Tomato</li>
            <li class="tab" data-tab="lettuce">🥬 Lettuce</li>
            <li class="tab" data-tab="cheese">🧀 Cheese</li>
        </ul>
    </nav>

    <main class="icon-container">
        <div class="icon">🍅</div>
    </main>
</div>

<style>
    /** Copy styles from example source code */
</style>

We've created a tab navigation with three tabs. The first tab starts with the selected class. Each tab has a data-tab attribute that we'll use to identify which tab was clicked. Below the tabs is a large icon that will change based on the selected tab.

Source code

<div class="container">
    <nav>
        <ul class="tabs">
            <li class="tab selected" data-tab="tomato">🍅 Tomato</li>
            <li class="tab" data-tab="lettuce">🥬 Lettuce</li>
            <li class="tab" data-tab="cheese">🧀 Cheese</li>
        </ul>
    </nav>

    <main class="icon-container">
        <div class="icon">🍅</div>
    </main>
</div>

<script type="module">
    import { press, animateView } from "motion-dom"
    import { spring } from "motion"

    let selectedName = "tomato"

    const icons = {
        tomato: "🍅",
        lettuce: "🥬",
        cheese: "🧀",
    }

    function setTab(tabName) {
        selectedName = tabName

        // Update icon text
        const icon = document.querySelector(".icon")
        icon.textContent = icons[tabName]

        // Remove selected class from current tab
        const selectedTab = document.querySelector(".tab.selected")
        selectedTab.classList.remove("selected")

        // Add selected class to new tab
        const newTab = document.querySelector(`[data-tab="${tabName}"]`)
        newTab.classList.add("selected")
    }

    press(".tab", (element) => {
        const tabName = element.dataset.tab

        if (tabName === selectedName) return

        animateView(() => setTab(tabName), {
            duration: 0.4,
            ease: [0.24, 0.02, 0.13, 1.01],
        })
    })
</script>

<style>
    .container {
        width: 480px;
        height: 60vh;
        max-height: 360px;
        border-radius: 10px;
        background: white;
        overflow: hidden;
        box-shadow: 0 1px 1px hsl(0deg 0% 0% / 0.075),
            0 2px 2px hsl(0deg 0% 0% / 0.075), 0 4px 4px hsl(0deg 0% 0% / 0.075),
            0 8px 8px hsl(0deg 0% 0% / 0.075),
            0 16px 16px hsl(0deg 0% 0% / 0.075);
        display: flex;
        flex-direction: column;
    }

    .container nav {
        background: #fdfdfd;
        padding: 5px 5px 0;
        border-radius: 10px;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        border-bottom: 1px solid #eeeeee;
        height: 44px;
    }

    .container .tabs {
        list-style: none;
        padding: 0;
        margin: 0;
        font-weight: 500;
        font-size: 14px;
        display: flex;
        width: 100%;
    }

    .container .tab {
        color: var(--black);
        border-radius: 5px;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        width: 100%;
        padding: 10px 15px;
        position: relative;
        background: white;
        cursor: pointer;
        height: 24px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        flex: 1;
        min-width: 0;
        user-select: none;
    }

    .tab.selected {
        background: #eee;
    }

    .tab.selected::after {
        content: "";
        position: absolute;
        bottom: -2px;
        left: 0;
        right: 0;
        height: 2px;
        background: var(--hue-4);
        view-transition-name: underline;
    }

    .icon-container {
        display: flex;
        justify-content: center;
        align-items: center;
        flex: 1;
    }

    .icon {
        font-size: 128px;
        view-transition-name: icon;
    }
</style>

Related examples

Latest in JavaScript

Motion+

Unlock all 400+ examples

  • Source code for every Plus example.
  • Provide examples direct to your agent via Motion's MCP.
  • Lifetime access to new examples and APIs.