Skip to content

[css-view-transitions-1] Named elements appear above fixed elements when transitioning #8941

Closed

Description

@danchristofi

Please see the following codepen: https://codepen.io/danchristofi/pen/abQzMLM

<button id="show">Show box</button>
<div id="box"></div>
<footer class="footer"></footer>
#box {
  width: 300px;
  height: 2000px;
  background: #000;
  display: none;
  view-transition-name: box;
}

.footer {
  position: fixed;
  width: 100%;
  height: 100px;
  background: red;
  bottom: 0;
  left: 0;
  z-index: 100;
  view-transition-name: footer;
}

.visible {
  display: block !important;
}

::view-transition-old(box),
::view-transition-new(box) {
  animation-duration: 10s;
}
document.getElementById("show").addEventListener("click", () => {
  document.startViewTransition(() => {
    document.getElementById("box").classList.toggle('visible')
  })
})

When adding a class to make the box visible, it appears over the top of the fixed element. In this example the red bar is fixed to the bottom of the page with the box behind it. After the animation is finished the box returns behind the footer.
image

Removing the view-transition-name from the box stops this happening, but it would be needed if you want a custom animation.

Is this a bug or is there any extra styling needed to get this to work correctly?

Activity

  1. khushalsagar commented on Jun 14, 2023

    @khushalsagar
    Member

    Unfortunately, this is the API working as intended. View Transition has to figure out how to animate multiple aspects of the DOM changing. One of those is the paint order which can't be interpolated so we had to pick some paint order. And the way it works is:

    • First paint all the view-transition-names in the old DOM, in the order in which they are painted in the old DOM.
    • Then paint all the view-transition-names which are only in the new DOM, in the order in which they are painted in the new DOM.

    The paint order here derives from the DOM order of ::view-transition-group pseudo-elements.

    In this case box is only in the new DOM (we ignore elements with a view-transition-name if they are display: none), so it paints on top of all view-transition-names from the old DOM which includes the footer. The fix is simple, you can use z-index to define the paint order of the group pseudos:

    ::view-transition-group(footer) {
      z-index: 100;
    }

    One approach we could try would be something like :

    • Figure out the paint order of elements in the old DOM. Say its [A, B, C].
    • Figure out the paint order of elements in the new DOM. Say its [A, D, B, C].

    Then combine these in the best way possible. For example, in the above case [A, D, B, C] would make sense. But if the 2 lists are [A, B, C] and [A, D] then its not clear what the ordering should be. Current spec would do [A, B, C, D] which seems fine, authors can always use z-index to fix the paint order.

    I'm open to suggestion here, not sure if the pattern in this issue is common enough for us to change the way we order the pseudos vs chalking it up to an edge case where authors should use z-index.

  2. khushalsagar commented on Jun 14, 2023

    @khushalsagar
    Member

    Gonna close this, we'll need a fairly complicated algorithm for deciding the paint order here to get every case right. Since there is an easy way for authors to do this way, with a primitive that's meant for this purpose, it doesn't feel worth it.

  3. tounsoo commented on Mar 5, 2025

    @tounsoo

    Any new development on this topic? I'm having this issue.

  4. bramus commented on Mar 6, 2025

    @bramus
    Contributor

    Scoped View Transitions will allow you to solve this: you’d start a View Transition on an element that excludes the position: fixed element. Scoped View Transitions are currently being prototyped in Chromium (announcement).

    In the mean time, you can work around it by giving the element that is position: fixed a view-transition-name as well.

  5. Splines commented on Sep 22, 2025

    @Splines

    Use case: Animation of old page, not new one

    I have this use case for a View Transition where I'd like to add an animation to the old page, not the new one. At the end of that animation, the old page is fully out of view such that the new page is entirely visible.

    Is that somehow possible? Since due to the paint order, the new element will directly be rendered in front of my old content, making it impossible to see the animation of the old page.

    The fix is simple, you can use z-index to define the paint order of the group pseudos:

    This lets me think I can give a view-transiton-name: body-old and view-transition-name: body-new CSS style to the body of the old and new page respectively, to then be able to set the z-index in the ::view-transition-old(root) and ::view-transition-new(root). However, this doesn't seem to work as the new content is still painted over the old one.


    Edit: The Use case I linked to now has a solution to it. I still leave this comment here, since maybe there are some other scenarios where it'd be nice to show the old page on-top during the view transition.

  6. bramus commented on Sep 23, 2025

    @bramus
    Contributor

    Since due to the paint order, the new element will directly be rendered in front of my old content, making it impossible to see the animation of the old page.

    The ::view-transition-old(x) and ::view-transition-new(x) pseudos are siblings, so you can use z-index to control their layering.

  7. Splines commented on Sep 23, 2025

    @Splines

    The ::view-transition-old(x) and ::view-transition-new(x) pseudos are siblings, so you can use z-index to control their layering.

    @bramus Thanks a lot, it works. However, at the end of the animation, there is a small flickering (tested in Brave and MS Edge). Consider for example the following, very simple code for an annoying animation across two pages (MPA).

    Show code
    <!-- old.html -->
    <html>
    
    <head>
      <link rel="stylesheet" href="view_transition.css">
    </head>
    
    <body style="background-color: #63001F; color: white;">
      <h1>old.html</h1>
      <a href="new.html" style="color: white;">--- Go to new.html ---</a>
    </body>
    
    </html>
    
    
    <!-- new.html -->
    <html>
    
    <head>
      <link rel="stylesheet" href="view_transition.css">
      <link rel="stylesheet" href="new.css">
    </head>
    
    <body style="background-color: #072C57; color: white;">
      <h1>new.html</h1>
      <a href="old.html" style="color: gray;">Go back</a>
    </body>
    
    </html>
    /* view_transition.css */
    @media (prefers-reduced-motion: no-preference) {
      @view-transition {
        navigation: auto;
      }
    }
    
    /* new.css */
    ::view-transition-old(root) {
      animation: translate 1.5s linear;
      z-index: 1;
    }
    
    ::view-transition-new(root) {
      animation: none;
      z-index:0;
    }
    
    @keyframes translate {
      0% {
        transform: translateX(0%);
      }
    
      100% {
        transform: translateX(100%);
      }
    }

    The result can be seen in this video. Unfortunately, GitHub reduces the framerate of the video to 23.98 frames/second, so you don't see the effect. Slowing down the animation speed to 10% in the Browser dev tools doesn't slow down the flickering, so I see no way to show you what is happening, you'd have to run the example on your own 😉

    The flickering appears right after the animation has finished. For a very brief moment, you see the old, red page flashing up at its initial position of translateX(0%), before seeing the new, blue page rendered.

    view-transition-flickering.mp4
  8. bramus commented on Sep 23, 2025

    @bramus
    Contributor

    You need to set the animation-fill-mode to forwards.

    ::view-transition-old(root) {
      animation: translate 1.5s linear forwards;
      z-index: 1;
    }
  9. Splines commented on Sep 23, 2025

    @Splines

    Thanks, works like a charm. I should read more about animation-fill-mode and other CSS animation-related properties ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions