为什么在DOM中移动元素会阻止CSS转换发生?

时间:2018-04-24 09:35:02

标签: javascript css dom css-transitions

使用insertBeforeappendChild()insertAdjacentElement()移动DOM中的元素后,除非经过一段时间,否则不会发生CSS转换。它的表现就好像有关于这些功能的异步。对此有任何记录的解释吗?

如果这方面的某些方面是异步的,那么" 正确"完成后做某事的方法?是否还有其他事件被触发? setTimeout()?多久?它是否受到客户端系统负载的影响?

以下示例也可以在this pen中找到,但我会在找到解决方案后更新笔。

在示例代码中,有三个承诺应该连续发生。第一个隐藏了一个元素。第二个将元素移动到DOM中的适当位置。然后最后一个再次显示该元素。显示和隐藏元素是通过切换带有hidden集的CSS max-height: 0类来完成的。为元素设置了transition: max-height 0.5s,但是在移动元素后不会立即发生这种转换。如果我跳过移动元素的函数或插入延迟,则问题不会发生。



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>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

为了过渡到工作,元素必须从一个状态转到另一个状态 浏览器通常会等到他们必须实际绘制您的页面,然后重新计算您通过js应用的所有新样式。
复杂代码中,不同的状态是同步设置的,因此转换不会发生。

为什么等待一段时间可能工作,或者可能不会:当没有指定重新计算时,你不能确定特定浏览器何时会这样做它。

  • requestAnimationFrame 应该在之前触发下一次屏幕刷新,所以如果浏览器实现决定在重绘之前触发重新计算,那么< em>可以在此rAF回调之后 要等待下一帧,您必须嵌套您的rAF呼叫(requestAnimationFrame(()=>requestAnimationFrame(callback)))。
  • setTimeout(fn,10)就是这方面的完全随机性,因为一帧最多持续16.6ms。
  • setTimeout(fn,100)会起作用,因为它超过一帧。

为了解决这个问题,在所有浏览器中,并且同步,您可以告诉浏览器在positionInfo()之前设置最终状态之前,triggering a reflow重新计算元素上应用的当前样式。解决承诺:

&#13;
&#13;
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)
    }
    // trigger a reflow
    row.offsetWidth;
    resolve(infoBlock)
  })
}
&#13;
.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;
}
&#13;
<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>
&#13;
&#13;
&#13;

请注意,您的代码似乎还有其他问题,但我会帮您解决。