Search code examples
javascriptdomshuffle

Shuffle a container's DOM elements but not all in javascript


Trying to shuffle a container's (a div) DOM elements (other divs) with a particular class name ("div"). Other divs ("extra") should stay put in the place where they are.

So far I have used the modified shuffle algorithm from link

The code I have almost did the trick but the "Extra" div is appended at the bottom of the stack while it's supposed to stay atop.

let divs       = document.getElementsByClassName("div")
let divsNumber = divs.length;
let divver     = document.getElementsByClassName("divver")[0]

function shuffler() {
  for (var i = divsNumber; i >= 0; i--) {
    if (!divver.children[i].classList.contains("div")) {
      divver.appendChild(divver.children[Math.random() * i | 0]);
    }
  }
}
<div class="divver">
  <div class="extra">  Extra  </div>
  <div class="div">    1  </div>
  <div class="div">    2  </div>
  <div class="div">    3  </div>
  <div class="div">    4  </div>
  <div class="div">    5  </div>
</div>

<button onclick="shuffler()" id="shuffles">Shuffle 'em</button>

Any hints greatly appreciated.


Solution

  • const
      divver      = document.querySelector('div.divver')
    , ShuffleDivs = [...divver.querySelectorAll('div:not(.extra)')]
      ;
    document.querySelector('button#shuffles').onclick = () =>
      {
      let r_Idx; // random Index;
    
      // current index from  ShuffleDivs.length -1 to zero
      for ( let c_Idx = ShuffleDivs.length; c_Idx--; )  
        {
        // Pick one.. in the range[ 0 ... c_Idx]
        r_Idx = Math.floor(Math.random() * c_Idx);      
    
        // swap Array positions
        [ShuffleDivs[c_Idx], ShuffleDivs[r_Idx]] =
        [ShuffleDivs[r_Idx], ShuffleDivs[c_Idx]]; 
        }
    
      // replace them in the new Shuffle order...
      for (let i = 0; i < ShuffleDivs.length; i++)
        divver.appendChild(ShuffleDivs[i]);
      }
    <div class="divver">
      <div class="extra"> Extra </div>
      <div> 1 </div>
      <div> 2 </div>
      <div> 3 </div>
      <div> 4 </div>
      <div> 5 </div>
    </div>
    
    <button id="shuffles">Shuffle 'em</button>

    To avoid moving pointers to DOM elements,
    I just use the indexing of their position on the interface here (aOrder):

    const
      divver      = document.querySelector('div.divver')
    , ShuffleDivs = divver.querySelectorAll('div:not(.extra)')
    , aOrder      = Array.from(ShuffleDivs,(_,i)=>i)  // array of elements position order
      ;
    document.querySelector('button#shuffles').onclick = () =>
      {
      let r; // random Index;
    
      for ( let c = aOrder.length; c--; ) //  current Index; 
        {
        r = 0|(Math.random() * c);        // Pick one range[ 0 ... c]
        [aOrder[c], aOrder[r]] = [aOrder[r], aOrder[c]]; // swap positions
        }
      for (let pos of aOrder)   
        divver.appendChild(ShuffleDivs[pos]);
      }
    <div class="divver">
      <div class="extra">  Extra  </div>
      <div> 1 </div>
      <div> 2 </div>
      <div> 3 </div>
      <div> 4 </div>
      <div> 5 </div>
    </div>
    
    <button id="shuffles">Shuffle 'em</button>

    for the record...

    comment from ViliusL

    "1" is always on bottom.

    Exact! the previous algorithm always leaves the first element at the end. Indeed, the initial collection is always read in the same order.

    I fixed it by changing the collection of divs to Array so I could actually change their order. So any new shuffle will be based on their actual position.

    First "incorrect" answer:

    const
      divver      = document.querySelector('div.divver')
    , ShuffleDivs = divver.querySelectorAll('div.div') // just select this group
      ;
    document.querySelector('button#shuffles').onclick = () =>
      {
      for (let i = ShuffleDivs.length; i >= 0; i--)
        divver.appendChild(ShuffleDivs[Math.random() * i | 0]);
      }