Search code examples
javascripthtmlcss

View Transition API: Tab Animation Scaling Issue


I'm experimenting a smooth tab switch animation using the View Transition API.

In my CodePen example (https://codepen.io/moorthy-g/pen/ByaQZBv), switching from 'Introduction' to 'Technology' is smooth because the tab widths are almost equal. However, switching to 'Storytelling in a Small Village' causes the active element to scale. How can I prevent this scaling behavior and maintain a smooth slide animation for all tab width transitions?

The issue occurs only if the tab widths are different

<div class="content">
    <div class="tabs">
        <div class="tab active" data-tab="1">Introduction</div>
        <div class="tab" data-tab="2">Storytelling in a Small Village</div>
        <div class="tab" data-tab="3">Technology</div>
    </div>
    <div class="tab-content">
        <div data-tab-content="1" style="display: block">
            <p>Lorem ipsum dolor sit amet.</p>
        </div>
        <div data-tab-content="2" style="display: none">
            <p>In a faraway land, there was a small village.</p>
        </div>
        <div data-tab-content="3" style="display: none">
            <p>Technology has transformed the world.</p>
        </div>
    </div>
</div>
.tabs {
    position: relative;
}

.tab {
    padding: 10px;
    background-color: #f0f0f0;
    cursor: pointer;
    position: relative;
    z-index: 0;
    overflow: hidden;
    display: inline-block;
}

.tab:hover {
    background-color: #fff;
}

.tab.active {
    color: #fff;
}

.tab.active::after {
    view-transition-name: tab;
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    background-color: #007bff;
    z-index: -1;
}

.tab-content {
    padding: 20px;
}

::view-transition-group(tab) {
    animation-duration: 0.5s;
}
const tabs = document.querySelectorAll(".tab");
const tabContents = document.querySelectorAll(".tab-content > div");
let currentTab = 0;
tabs.forEach((tab, index) => {
    tab.addEventListener("click", () => {
        currentTab = index;
        document.startViewTransition(() => {
            tabs.forEach((t, i) => {
                if (i === currentTab) {
                    t.classList.add("active");
                } else {
                    t.classList.remove("active");
                }
            });
        });
        tabContents.forEach((tc, i) => {
            if (i === currentTab) {
                tc.style.display = "block";
            } else {
                tc.style.display = "none";
            }
        });
    });
});

// Set the first tab as default
document.querySelector(".tab").click();

Solution

  • When view transitions are applied, apart from default cross-fade animation this is what happens:

    • height and width are transitioned using a smooth scaling animation
    • position and transform are transitioned using a smooth movement animation

    So in your case you need to disable the height animation by setting a fixed height in both old and new views:

    ::view-transition-old(tab),
    ::view-transition-new(tab) {
      height: 100%;
    }
    

    So only the width is scaled up/down. And the transition looks smooth like this:

    View transition - disabled height animation