Note: The example below uses the View Transitions API which is not yet supported in all browsers. In Firefox this code won’t do the transitions and thus will not show the problem I’m having.
What I’m trying to make
Using the View Transitions API I’m transitioning between 2 elements. This happens when the user clicks on an item, this will open a (sort of) modal over a list of items with a nice transition.
The problem I’m facing
The API doesn’t seem to respect the stacking order very well (z-index
). The biggest problem is that the .overlay.backdrop
is going over the element that is in a transition. Only when the transition is done you can see the item “light up” and go on to of the backdrop. I also cannot figgur out how to make the selected item go over the other items while in transition.
Tip: You can see really clear what is happing when you set the animation speed to 10% in the chrome devtools.
What i tried
- Different ways of changing the structure of the DOM and
z-index
of items. - Have been playing around with the
::view-transition-group
(docs link) pseudo element in my CSS, but since I’m setting the names of my items dynamically i don’t think i can use this?
I really have no clue how to debug this more.
const { ref } = Vue;
const app = Vue.createApp({
setup() {
const items = [
{
id: 1,
title: 'Diablo 4',
description: 'A shit game with a decent story',
},
{
id: 2,
title: 'Elden Ring',
description: 'A good game with a shit story',
},
{
id: 3,
title: 'The Witcher 3',
description: 'A good game with a good story',
},
{
id: 4,
title: 'Cyberpunk 2077',
description: 'A shit game with a shit story',
},
];
const activeItem = ref(null);
const setActiveItem = (item) => {
if (!document.startViewTransition) {
activeItem.value = activeItem.value === item ? null : item;
return;
}
document.startViewTransition(() => {
activeItem.value = activeItem.value === item ? null : item;
});
};
return {
items,
activeItem,
setActiveItem
};
}
});
app.mount('#app');
.box {
background-color: lightblue;
cursor: pointer;
position: relative;
z-index: 1;
}
.box.small {
padding: 1rem;
}
.box.big {
padding: 2rem;
font-size: 1.5rem;
margin: 2rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
padding: 1rem;
}
.overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
z-index: 11;
}
.overlay.backdrop {
view-transition-name: overlay;
background: rgba(0, 0, 0, 0.5);
z-index: 10;
}
body {
margin: 0;
display: grid;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.4/vue.global.min.js"></script>
<div id="app">
<!-- Detail view (overlay) -->
<div>
<div class="overlay backdrop" v-if="activeItem"></div>
<div class="overlay" v-if="activeItem" @click="setActiveItem(null)">
<div class="box big" :style="{ viewTransitionName: `box-${activeItem.id}` }">
<span :style="{ viewTransitionName: `title-${activeItem.id}` }">
{{ activeItem.title }}
</span>
<p>{{ activeItem.description }}</p>
</div>
</div>
<!-- List view -->
<div class="grid">
<div v-for="item in items" :key="item.id" class="box small" @click="setActiveItem(item)"
:style="{
viewTransitionName: activeItem?.id === item.id ? '' : `box-${item.id}`,
visibility: activeItem?.id === item.id ? 'hidden' : 'visible',
}">
<span :style="{ viewTransitionName: activeItem?.id === item.id ? '' : `title-${item.id}` }">
{{ item.title }}
</span>
</div>
</div>
</div>
</div>
Anyone here some ideas on how to fix this?