Description
Try this test case: https://codepen.io/emattias/pen/OJrGVEO.
<button id="button">foobar</button>
<style>
button {
cursor: pointer;
background: green;
transition: all 0.5s ease-out;
}
button:hover {
background: red;
transition: all 0.5s ease-out;
}
::view-transition {
pointer-events: none;
}
</style>
<script>
let state = true
const button = document.querySelector('button')
button.addEventListener('click', () => {
document.startViewTransition(() => {
const newText = state ? 'baz' : 'foobar'
button.innerText = newText;
state = !state
})
})
</script>
When we change the DOM for the transition (by adding pseudos), a new hit-test is triggered. If you add pointer-events: none, it ignores the pseudos and runs on the underlying DOM. But if some element is participating in a transition, that will be ignored in hit-testing (by design). So right now :hover can't be active on any DOM element participating in the transition. Relevant spec text : https://drafts.csswg.org/css-view-transitions-1/#:~:text=do%20not%20respond%20to%20hit%2Dtesting.
The goal with not hit-testing DOM elements was to avoid incorrect clicks. The DOM element is not painting where its box is. But we didn't realize that a side-effect of this is that activating hover
is delayed until transition end. So if the author is using :hover
to style an element, those styles will abruptly apply when the transition is over.
The principled fix for this (credits to @vmpstr) is to route hit-testing from ::view-transition-new
to its corresponding DOM element. Its the reverse of what we're conceptually doing with the element's painting (routing it from the DOM element to the VT pseudo).