-
Notifications
You must be signed in to change notification settings - Fork 314
Open
Description
When using insertBefore
with transitions and the FLIP technique, I get very strange results across browsers.
The only difference is the way the DOM nodes are rearranged, which is what confuses me about this.
Code for each
insertBefore
:
<!doctype html>
<ul id="animate"><li>0</li><li>1</li><li>2</li><li>3</li></ul>
<script>
// Factored out so it's clearer what elements are added and removed
const animateRoot = document.getElementById("animate")
const elem0 = animateRoot.childNodes[0]
const elem1 = animateRoot.childNodes[1]
const elem2 = animateRoot.childNodes[2]
const elem3 = animateRoot.childNodes[3]
// ordered: [0, 1, 2, 3]
// mixed: [2, 0, 3, 1]
function mix() { // ordered -> mixed
setTimeout(order, 500)
// First
const first0 = elem0.getBoundingClientRect().top
const first1 = elem1.getBoundingClientRect().top
const first2 = elem2.getBoundingClientRect().top
const first3 = elem3.getBoundingClientRect().top
// Update [0, 1, 2, 3] -> [2, 0, 3, 1]
animateRoot.insertBefore(elem2, elem0)
animateRoot.appendChild(elem1)
// Last + Invert
// Note: earlier `transitionDuration` must be set before later
// `transform`, or even more weirdness occurs (just try it and see
// what happens).
elem0.style.transitionDuration =
elem1.style.transitionDuration =
elem2.style.transitionDuration =
elem3.style.transitionDuration = "0s"
elem0.style.transform = `translateY(${first0 - elem0.getBoundingClientRect().top}px)`
elem1.style.transform = `translateY(${first1 - elem1.getBoundingClientRect().top}px)`
elem2.style.transform = `translateY(${first2 - elem2.getBoundingClientRect().top}px)`
elem3.style.transform = `translateY(${first3 - elem3.getBoundingClientRect().top}px)`
// Force re-layout
document.body.offsetHeight
// Play
elem0.classList.add("transition")
elem1.classList.add("transition")
elem2.classList.add("transition")
elem3.classList.add("transition")
elem0.style.transform = elem0.style.transitionDuration =
elem1.style.transform = elem1.style.transitionDuration =
elem2.style.transform = elem2.style.transitionDuration =
elem3.style.transform = elem3.style.transitionDuration = ""
}
function order() { // mixed -> ordered
setTimeout(mix, 500)
// First
const first0 = elem0.getBoundingClientRect().top
const first1 = elem1.getBoundingClientRect().top
const first2 = elem2.getBoundingClientRect().top
const first3 = elem3.getBoundingClientRect().top
// Update [2, 0, 3, 1] -> [0, 1, 2, 3]
animateRoot.insertBefore(elem1, elem3)
animateRoot.insertBefore(elem2, elem3)
// Last + Invert
// Note: earlier `transitionDuration` must be set before later
// `transform`, or even more weirdness occurs (just try it and see
// what happens).
elem0.style.transitionDuration =
elem1.style.transitionDuration =
elem2.style.transitionDuration =
elem3.style.transitionDuration = "0s"
elem0.style.transform = `translateY(${first0 - elem0.getBoundingClientRect().top}px)`
elem1.style.transform = `translateY(${first1 - elem1.getBoundingClientRect().top}px)`
elem2.style.transform = `translateY(${first2 - elem2.getBoundingClientRect().top}px)`
elem3.style.transform = `translateY(${first3 - elem3.getBoundingClientRect().top}px)`
// Force re-layout
document.body.offsetHeight
// Play
elem0.classList.add("transition")
elem1.classList.add("transition")
elem2.classList.add("transition")
elem3.classList.add("transition")
elem0.style.transform = elem0.style.transitionDuration =
elem1.style.transform = elem1.style.transitionDuration =
elem2.style.transform = elem2.style.transitionDuration =
elem3.style.transform = elem3.style.transitionDuration = ""
}
setTimeout(mix, 500)
</script>
appendChild
:
<!doctype html>
<ul id="animate"><li>0</li><li>1</li><li>2</li><li>3</li></ul>
<script>
// Factored out so it's clearer what elements are added and removed
const animateRoot = document.getElementById("animate")
const elem0 = animateRoot.childNodes[0]
const elem1 = animateRoot.childNodes[1]
const elem2 = animateRoot.childNodes[2]
const elem3 = animateRoot.childNodes[3]
// ordered: [0, 1, 2, 3]
// mixed: [2, 0, 3, 1]
function mix() { // ordered -> mixed
setTimeout(order, 500)
// First
const first0 = elem0.getBoundingClientRect().top
const first1 = elem1.getBoundingClientRect().top
const first2 = elem2.getBoundingClientRect().top
const first3 = elem3.getBoundingClientRect().top
// Update [0, 1, 2, 3] -> [2, 0, 3, 1]
animateRoot.appendChild(elem2)
animateRoot.appendChild(elem0)
animateRoot.appendChild(elem3)
animateRoot.appendChild(elem1)
// Last + Invert
// Note: earlier `transitionDuration` must be set before later
// `transform`, or even more weirdness occurs (just try it and see
// what happens).
elem0.style.transitionDuration =
elem1.style.transitionDuration =
elem2.style.transitionDuration =
elem3.style.transitionDuration = "0s"
elem0.style.transform = `translateY(${first0 - elem0.getBoundingClientRect().top}px)`
elem1.style.transform = `translateY(${first1 - elem1.getBoundingClientRect().top}px)`
elem2.style.transform = `translateY(${first2 - elem2.getBoundingClientRect().top}px)`
elem3.style.transform = `translateY(${first3 - elem3.getBoundingClientRect().top}px)`
// Force re-layout
document.body.offsetHeight
// Play
elem0.classList.add("transition")
elem1.classList.add("transition")
elem2.classList.add("transition")
elem3.classList.add("transition")
elem0.style.transform = elem0.style.transitionDuration =
elem1.style.transform = elem1.style.transitionDuration =
elem2.style.transform = elem2.style.transitionDuration =
elem3.style.transform = elem3.style.transitionDuration = ""
}
function order() { // mixed -> ordered
setTimeout(mix, 500)
// First
const first0 = elem0.getBoundingClientRect().top
const first1 = elem1.getBoundingClientRect().top
const first2 = elem2.getBoundingClientRect().top
const first3 = elem3.getBoundingClientRect().top
// Update [2, 0, 3, 1] -> [0, 1, 2, 3]
animateRoot.appendChild(elem0)
animateRoot.appendChild(elem1)
animateRoot.appendChild(elem2)
animateRoot.appendChild(elem3)
// Last + Invert
// Note: earlier `transitionDuration` must be set before later
// `transform`, or even more weirdness occurs (just try it and see
// what happens).
elem0.style.transitionDuration =
elem1.style.transitionDuration =
elem2.style.transitionDuration =
elem3.style.transitionDuration = "0s"
elem0.style.transform = `translateY(${first0 - elem0.getBoundingClientRect().top}px)`
elem1.style.transform = `translateY(${first1 - elem1.getBoundingClientRect().top}px)`
elem2.style.transform = `translateY(${first2 - elem2.getBoundingClientRect().top}px)`
elem3.style.transform = `translateY(${first3 - elem3.getBoundingClientRect().top}px)`
// Force re-layout
document.body.offsetHeight
// Play
elem0.classList.add("transition")
elem1.classList.add("transition")
elem2.classList.add("transition")
elem3.classList.add("transition")
elem0.style.transform = elem0.style.transitionDuration =
elem1.style.transform = elem1.style.transitionDuration =
elem2.style.transform = elem2.style.transitionDuration =
elem3.style.transform = elem3.style.transitionDuration = ""
}
setTimeout(mix, 500)
</script>
I've reproduced this behavior explained above on each of the following platforms:
- Chrome 83.0.4103.116 on Windows 10 64-bit
- Safari 13.1 on iOS 13
- Firefox 78.0.2 on Windows 10 64-bit
Relevant framework bugs I've filed, before I narrowed it down further to this:
- Updates to keyed lists break FLIP animations when they occur mid-animation MithrilJS/mithril.js#2612
- Bug: Updates to keyed lists break FLIP animations when they occur mid-animation facebook/react#19406
- Updates to keyed lists break FLIP animations when they occur mid-animation preactjs/preact#2637
- Updates to keyed lists break FLIP animations when they occur mid-animation infernojs/inferno#1519
I suspect it's a bug in all three of those listed browsers, but I'm filing an issue here in case it's actually a spec issue or if a spec note is necessary for this.
Metadata
Metadata
Assignees
Labels
No labels