DOM 在无限滑块轨道动画上使用 Element.prepend 的意外排序行为

Unexpected DOM Ordering Behaviour With Element.prepend on Infinite Slider-Track Animation

我正在研究无限滑块,但我在 DOM 排序时遇到了一个奇怪的错误。

在每次动画迭代结束时,最后一个子元素应该添加到 div.slider-track 元素之前,以便它呈现在滑块的开头并在下一个之前将其他卡片向上推迭代。它按预期工作,直到 第 11 次迭代,其中 Card1 预先设置 连续两次迭代 。 Card1 被选为 lastChild 属性 两次。不应该在第 11 次迭代时选择 Card10 吗?给出了什么?

const sliderTrack = document.querySelector(".slider-track")

const newCard = (count) => {
  const card = document.createElement("div");
  card.className = "card";
  
  const label = document.createElement("span")
  label.innerText = `Card ${count}`
  label.className = "label";
  card.append(label)
  
  return card
}

const populateCards = (element) => {
  for (let i = 1; i <= 10; i++) {
  element.append(newCard(i))
  }
}

sliderTrack.addEventListener('animationiteration', () => {
  sliderTrack.prepend(sliderTrack.lastChild);
});

populateCards(sliderTrack)
body {
  background: #f06d06;
  font-family: 'Roboto', sans-serif;
  font-weight: bold;
  padding: 0;
}

.slider {
  overflow: hidden;
}

@keyframes slider {
  to {
    transform: translate(10%);
  }
}

.slider-track {
  display: flex;
  animation: slider 1s linear;
  animation-iteration-count: infinite;
}

.card {
  background: white;
  width: 10vw;
  height: 10vw;
  border-radius: 8px;
  box-shadow: 2px 2px rgba(0, 0, 0, 20%);
  
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="slider">
  <div class="slider-track">
  </div>
</div>

您的问题是由 DOM 的一个稍微微妙且经常令人讨厌的方面引起的。这就是“文本节点”——即作为 HTML 一部分的任何纯文本——也算作节点,因此算作它们的 parent 元素的 children。

在这种情况下,因为您的滑块轨道 HTML 是跨 2 行编写的:

<div class="slider-track">
</div>

然后,不管你信不信,你实际上有一个换行符,字符串 "\n" - 作为 slider-track 元素的 child 注释之一。

所以当你在初始化时用卡片 1 到卡片 10 填充它时,你实际上有 11 children,首先是那个文本节点。动画迭代 10 次后,它以该文本节点作为最后一个元素结束。

然后,sliderTrack.prepend(sliderTrack.lastChild) 只是将无辜的换行文本节点从末尾移动到滑块 DOM children 的前面。这没有明显的效果 - 但因为这是在动画需要 1 秒之后发生的,这意味着在那个特定的“滴答声”上,似乎没有发生任何事情,这是你观察到的奇怪和不需要的行为。

谢天谢地,解决方法很简单,只要您意识到这就是问题所在。您当然可以简单地删除换行文本,将结束标记放在与开始标记相同的行上,甚至没有任何 space 之间:

const sliderTrack = document.querySelector(".slider-track")

const newCard = (count) => {
  const card = document.createElement("div");
  card.className = "card";
  
  const label = document.createElement("span")
  label.innerText = `Card ${count}`
  label.className = "label";
  card.append(label)
  
  return card
}

const populateCards = (element) => {
  for (let i = 1; i <= 10; i++) {
  element.append(newCard(i))
  }
}

sliderTrack.addEventListener('animationiteration', () => {
  sliderTrack.prepend(sliderTrack.lastChild);
});

populateCards(sliderTrack)
body {
  background: #f06d06;
  font-family: 'Roboto', sans-serif;
  font-weight: bold;
  padding: 0;
}

.slider {
  overflow: hidden;
}

@keyframes slider {
  to {
    transform: translate(10%);
  }
}

.slider-track {
  display: flex;
  animation: slider 1s linear;
  animation-iteration-count: infinite;
}

.card {
  background: white;
  width: 10vw;
  height: 10vw;
  border-radius: 8px;
  box-shadow: 2px 2px rgba(0, 0, 0, 20%);
  
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="slider">
  <div class="slider-track"></div>
</div>

但是虽然这在这里并不太痛苦,但在其他情况下肯定会非常烦人,迫使您以 less-than-readable 的方式格式化您的 HTML。

所以对于完全像这样的情况还有另一种解决方案 - 以及 lastChild, there is also the more specific lastElementChild 专门忽略文本节点。这在这里也可以完美地工作,并且通常可能是更好的解决方案:

const sliderTrack = document.querySelector(".slider-track")

const newCard = (count) => {
  const card = document.createElement("div");
  card.className = "card";
  
  const label = document.createElement("span")
  label.innerText = `Card ${count}`
  label.className = "label";
  card.append(label)
  
  return card
}

const populateCards = (element) => {
  for (let i = 1; i <= 10; i++) {
  element.append(newCard(i))
  }
}

sliderTrack.addEventListener('animationiteration', () => {
  sliderTrack.prepend(sliderTrack.lastElementChild);
});

populateCards(sliderTrack)
body {
  background: #f06d06;
  font-family: 'Roboto', sans-serif;
  font-weight: bold;
  padding: 0;
}

.slider {
  overflow: hidden;
}

@keyframes slider {
  to {
    transform: translate(10%);
  }
}

.slider-track {
  display: flex;
  animation: slider 1s linear;
  animation-iteration-count: infinite;
}

.card {
  background: white;
  width: 10vw;
  height: 10vw;
  border-radius: 8px;
  box-shadow: 2px 2px rgba(0, 0, 0, 20%);
  
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="slider">
  <div class="slider-track">
  </div>
</div>