【问题标题】:How to randomly re-render the DOM nodes' order of precedence from a node-list?如何从节点列表中随机重新渲染 DOM 节点的优先顺序?
【发布时间】:2021-10-11 10:05:59
【问题描述】:

我正在尝试通过重新排序我的 NodeList 中的节点索引来洗牌。

如何实现删除和附加我拥有的类属性的子级?

HTML:

<div class="card" data-card="1" onclick="cardClicked(this)">
    <img src="img/cards/1.png" alt="">
    <img class="back" src="img/cards/back.png" alt="">
</div>

<div class="card" data-card="7" onclick="cardClicked(this)">
    <img src="img/cards/7.png" alt="">
    <img class="back" src="img/cards/back.png" alt="">
</div>

<div class="card" data-card="1" onclick="cardClicked(this)">
    <img src="img/cards/1.png" alt="">
    <img class="back" src="img/cards/back.png" alt="">
</div>

JavaScript:

function shuffleCards() {
    let cards = document.querySelectorAll('.card');
    let cardsArray = Array.from(cards);
    // reorder the nodes of the nodelist (cards)
}

【问题讨论】:

    标签: javascript arrays dom shuffle nodelist


    【解决方案1】:

    您可以通过多种方式“洗牌”数组。我在一个实验性的“战争”纸牌游戏中选择了 Fisher-Yates 方法。 https://github.com/scottm85/war/blob/master/src/Game/Deck.js#L80

    shuffle()
    {
        /* Use a Fisher-Yates shuffle...If provides a much more reliable shuffle than using variations of a sort method https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle */
        let cards = this.cards;
        for (let i = cards.length - 1; i > 0; i--)
        {
            let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i
            [cards[i], cards[j]] = [cards[j], cards[i]];
        }
        this.cards = cards;
    
        console.log("----------------- Deck Shuffled -----------------");
    }
    

    https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

    我提供的示例与您的需求略有不同,因为我在 react 中执行此操作,构建了一个 Deck 数组,并且没有使用 JS 进行直接 DOM 操作。但理论上,您可以修改它以使用您的方法。像这样的:

    function shuffleCards
    {
        let cards = document.querySelectorAll('.card');
        let cardsArray = Array.from(cards);
    
        for (let i = cardsArray.length - 1; i > 0; i--)
        {
            let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i
            [cardsArray[i], cardsArray[j]] = [cardsArray[j], cardsArray[i]];
            cards[i].remove();
        }
    
        cardsArray.forEach(t => document.body.appendChild(t));
    }
    

    【讨论】:

    • 最后,我学习并使用了Fisher-Yates 谢谢,1部分我不明白,为什么我们在循环中做i > 0。
    • @Alwayslearning 因为每次迭代我们都会从 i 中减去 1。 (i--;) 由于您无法访问负索引的项目,我们希望它在零处停止。但从技术上讲,它应该是 i >= 0,我刚刚在您的评论中注意到了这一点。我很高兴它有帮助!祝你的项目好运。如果您觉得它有帮助,请随时接受这个作为答案。谢谢!
    【解决方案2】:

    您可以像这样使用sortMath.random() 方法:

     function shuffleCards() {
                var parent = document.getElementById("parent");
    
                let cards = document.querySelectorAll('.card');
                let cardsArray = Array.from(cards);
    
                //console.log(cardsArray)
    
                cardsArray.sort(() => Math.random() - 0.5);
    
                //console.log(cardsArray)
    
                cardsArray.forEach(t => parent.appendChild(t));
                // reorder the nodes of the nodelist (cards)
            }
    <h3> Click on card to Shuffle at the same postion</h3>
    
        <div id="parent">
            <div class="card" data-card="1" onclick="shuffleCards(this)">
                card 1
                <img src="img/cards/1.png" alt="">
                <img class="back" src="img/cards/back.png" alt="">
            </div>
    
            <div class="card" data-card="7" onclick="shuffleCards(this)">
                card 2
                <img src="img/cards/7.png" alt="">
                <img class="back" src="img/cards/back.png" alt="">
            </div>
    
            <div class="card" data-card="1" onclick="shuffleCards(this)">
                card 3
                <img src="img/cards/1.png" alt="">
                <img class="back" src="img/cards/back.png" alt="">
            </div>
        </div>

    【讨论】:

    • 这行得通,即使我试图在每场比赛后或加载页面时实现洗牌并将卡片保持在 DOM 中的相同位置,我可以将卡片附加回同一个位置在 DOM 中?目前,它似乎在我在 HTML 中的所有 div 之后显示它们
    • @Alwayslearning 是的,你可以。您需要将它们全部放在父元素中,然后清空父元素的innerHTML,并将附加的洗牌节点列表作为父元素的子元素
    • @Alwayslearning 我更新了答案。现在打乱后的元素被附加在同一个地方
    • 使用cardsArray.sort(() =&gt; Math.random() - 0.5); 洗牌是个坏主意。该标准要求排序功能是一致的。如果不是,则结果由实现定义。在最坏的情况下,只有阵列的一部分被打乱。 @Alwayslearning
    • 感谢代码和信息,这是一个坏主意,我最终在这里从不同的答案中提出了一个解决方案,并使用了 Fisher-Yates 算法,我也不允许在此使用箭头函数锻炼。
    【解决方案3】:

    其他人已经为随机化部分提供了算法,但这里是您可以如何处理分离和重新附加部分的方法。 removeChild() 将让您从 DOM 中分离节点,appendChild() 将允许您添加它。请小心连接和拆卸。事件可能会变得有点混乱,尤其是如果您最终将节点克隆到某处而不是再次附加它。

    (function shuffleCards() {
      let container = document.querySelector('#container');
      let cards = container.querySelectorAll('.card');
    
      [...cards].map(
        node => container.removeChild(node)
      ).sort(
        () => Math.random() - 0.5 // shamelessly stolen from Alireza Ahmadi
      ).forEach(
        node => container.appendChild(node)
      );
    })()
    <div id="container">
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 1
      </div>
      <div class="card" data-card="7" onclick="cardClicked(this)">
          Card 2
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 3
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 4
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 5
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 6
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 7
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 8
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 9
      </div>
      <div class="card" data-card="1" onclick="cardClicked(this)">
          Card 10
      </div>
    </div>

    【讨论】:

    • 因为它的随机性Math.random() - 0.5永远无法提供给稳定的sort算法。
    • @PeterSeliger 这是真的。排序算法可以是任何东西。通过我的回答,我想解决 OP 的问题,他们询问如何分离和重新连接节点。
    • 仅用于 DOM 节点演示目的没有问题。为了不只是盲目地复制 poor mans shuffle 方法,该评论也更多地针对 OP。
    【解决方案4】:

    以下方法不知道需要从哪里删除查询的节点,并且在洗牌后必须再次插入。

    因此,除了元素查询之外,不需要了解有关 DOM 结构的任何信息。

    基本上...

    1. 仅使用可靠/经过验证的随机播放函数,例如 _.shuffle
    2. 从查询的节点列表中创建一个数组。
    3. 将其与节点项进行映射,使每个(子)节点都从 DOM 中移除,但不仅保留其自己的引用,还保留其父节点的引用。
    4. 随机播放映射数组。
    5. 以一种可以从每个项目中恢复以前的父节点子节点关系的方式迭代打乱的项目...

    function shuffleArray(arr) {
      let idx;
      let count = arr.length;
    
      while (--count) {
        idx = Math.floor(Math.random() * (count + 1));
        [arr[idx], arr[count]] = [arr[count], arr[idx]];
      }
      return arr;
    }
    
    function shuffleCards() {
      const removedNodeDataList = Array
        .from(document.querySelectorAll('.card'))
        .map(elmNode => ({
          parentNode: elmNode.parentNode,
          elementNode: elmNode.parentNode.removeChild(elmNode),
        }));
    
      shuffleArray(removedNodeDataList)
        .forEach(({ parentNode, elementNode }) =>
          parentNode.appendChild(elementNode)
        );
    }
    
    function init() {
      document
        .querySelector('button')
        .addEventListener('click', shuffleCards);
    }
    init();
    img { background-color: #eee; }
    button { display: block; margin: 0 0 10px 0; }
    <button>Shuffle Cards</button>
    
    <div class="card" data-card="1">
      <img src="https://picsum.photos/140/50?grayscale" alt="">
      <img class="back" src="https://picsum.photos/120/50?grayscale" alt="">
    </div>
    <div class="card" data-card="2">
      <img src="https://picsum.photos/100/50?grayscale" alt="">
      <img class="back" src="https://picsum.photos/160/50?grayscale" alt="">
    </div>
    <div class="card" data-card="3">
      <img src="https://picsum.photos/180/50?grayscale" alt="">
      <img class="back" src="https://picsum.photos/80/50?grayscale" alt="">
    </div>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-26
      • 1970-01-01
      • 1970-01-01
      • 2018-01-13
      • 1970-01-01
      相关资源
      最近更新 更多