Search code examples
javascripthtmlsortingdomchildren

Javascript: Optimized re-ordering of iFrames like DOM nodes


My app has a WebView control with an HTML page inside.

That page is made of divs which contain one iFrame each, so when many divs are children of document.body all iFrames are like HTML pages themselves, one after other.

The divs have a attribute called orderIndex, because they have to be ordered according to an array of indexes.

(The iFrames have already loaded at the sorting time)

These indexes are the new positions, so for examples orderIndexesArray[0] contains the new position that the first child has to attain.

I found some sorting methods at:

https://stackoverflow.com/a/39569822/930835

I adapted this one

var bd = document.body;
Array.prototype.slice.call(bd.children)
.map(function (x) { return bd.removeChild(x); })
.sort(function (x, y) { return /* your sort logic, compare x and y here */; })
.forEach(function (x) { bd.appendChild(x); });

I want that the ordering process is not much like "blinking parts of the HTML page being swiftly reordered", instead I would like that the redrawing starts from the first so the visible part of the page is sort of replaced with no much blinking.

My concern is also for performance. HTML and Javascript are not my main programming environment.

So, is that algorithm doing a nice job redrawing from the top and well performing, or does it contain numberous swaps and it is slow?


Solution

  • The snippet seems fine. Though the map is not really needed since HTML elements are passed down by reference. You are not creating new ones so deleting them and then adding them back in is unnecessary work.

    As for the amount of swaps, that depends on the amount of elements you have. Though each element only gets reordered once in the DOM. (this happens in the forEach)

    The snippet below demonstrates a small working example.

    const ul = document.querySelector("ul");
    const btn = document.querySelector("button");
    
    btn.addEventListener("click", () => {
      [...ul.children]
      .sort((x, y) => Math.random() - 0.5) // Create a random order
      .forEach((x) => ul.appendChild(x));
    });
    <ul>
      <li>0</li>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
      <li>6</li>
      <li>7</li>
      <li>8</li>
      <li>9</li>
    </ul>
    
    <button>Reorder</button>

    EDIT: Iframes will rerender when you change their order so their current state will be lost. An option to fix this is to use the CSS flex + order attributes to reorder the frames. The downfall is that all the frames must have the same root.

    (iframes don't work on stackoverflow so i made some fake ones)

    const frames = document.querySelectorAll(".fake-iframe");
    const btn = document.querySelector("button");
    
    btn.addEventListener("click", () => {
      frames.forEach((frame) => (frame.style.order = frame.dataset.orderindex));
    });
    .frames {
      display: flex;
      flex-wrap: wrap;
    }
     
    /* Just for demonstration purposes */
    .fake-iframe {
      position: relative;
      width: 200px;
      height: 200px;
    }
    
    .fake-iframe span {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      z-index: 9999;
      text-align: center;
      font-size: 3rem;
    }
    <div class="frames">
      <div class="fake-iframe" data-orderIndex="3">
        <span>Frame 3</span>
      </div>
      <div class="fake-iframe" data-orderIndex="2">
        <span>Frame 2</span>
      </div>
      <div class="fake-iframe" data-orderIndex="1">
        <span>Frame 1</span>
      </div>
    </div>
    
    <button>Reorder</button>