After I move an element within the DOM using insertBefore
, appendChild()
, or insertAdjacentElement()
CSS transitions don't occur unless some time has passed. It's acting as if there's something asynchronous about these functions. Is there any documented explanation for this?
If there is some aspect of this that is asynchronous, what's the "right" way to do something after it finishes? Is there another event triggered? setTimeout()
? How long? Is it affected by system load on the client side?
The following example can also be found in this pen, but I'll update the pen after I find the solution.
In the example code, there are three promises that should occur in succession. The first one hides an element. The second one moves the element to an appropriate place in the DOM. Then the last one shows the element again. Showing and hiding the element is done by toggling a CSS hidden
class with a max-height: 0
set. There's a transition: max-height 0.5s
set for the element, but this transition doesn't happen immediately after the element is moved. If I either skip the function that moves the element or insert a delay the problem doesn't occur.
console.clear()
window.addEventListener('load', () => {
const infoBlock = document.getElementById('info')
const links = document.getElementsByTagName('a')
for (let link of links) {
link.addEventListener('click', e => {
e.preventDefault()
infoBlock.target = e.target
toggleInfo(infoBlock, 'hide')
.then(infoBlock => positionInfo(infoBlock))
.then(infoBlock => toggleInfo(infoBlock, 'show'))
})
}
})
function toggleInfo(infoBlock, action) {
return new Promise((resolve, reject) => {
const isVisible = () => !infoBlock.classList.contains('hidden')
const transitionHandler = e => {
if (e.propertyName !== 'max-height') return
infoBlock.removeEventListener('transitionend', transitionHandler)
resolve(infoBlock)
}
const correctState = (action === 'hide') ? !isVisible() : isVisible()
if (!correctState) {
infoBlock.classList.toggle('hidden')
infoBlock.addEventListener('transitionend', transitionHandler)
} else {
resolve(infoBlock)
}
})
}
function positionInfo(infoBlock) {
return new Promise((resolve, reject) => {
let target = infoBlock.target
let row = target.parentNode
while (!row.classList.contains('row')) row = row.parentNode
let nextRow = row.nextSibling
if (nextRow !== null) {
nextRow.parentNode.insertBefore(infoBlock, nextRow)
} else {
row.parentNode.appendChild(infoBlock)
}
// row.insertAdjacentElement('afterend', infoBlock)
// Doesn't work in Chrome or Firefox
resolve(infoBlock)
// Doesn't work in Chrome, works in Firefox
// window.requestAnimationFrame(resolve.bind(null, infoBlock))
// Works in Chrome, doesn't work in Firefox
// setTimeout(resolve.bind(null, infoBlock), 10)
// Works in Chrome or Firefox... but why?
// setTimeout(resolve.bind(null, infoBlock), 100)
})
}
.row a {
margin: 10px 0;
}
.info {
background: #111;
color: white;
text-align: center;
height: 240px;
max-height: 240px;
transition: max-height 1s;
}
.info.hidden {
max-height: 0;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.css" rel="stylesheet" />
<div id="info" class="row info hidden">
<p class="col-12">info</p>
</div>
<div class="container">
<div class="row" id="row01">
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
</div>
<div class="row" id="row02">
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
</div>
<div class="row" id="row03">
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
</div>
<div class="row" id="row04">
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
</div>
<div class="row" id="row05">
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
<a href="#" class="col-4"><img src="https://via.placeholder.com/150x100" alt="placeholder"></a>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>