1

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?

P5music
  • 3,197
  • 2
  • 32
  • 81

2 Answers2

0

It's about as performant as you're going to get it, because the ordering (and, thus, swapping around) is done in an array of DOM elements that are no longer in the DOM, so not causing repaints/reflows.

As far as the DOM is concerned, the only action is when each, correctly ordered element is later appended, one after the other, back into the DOM.

This should be fine; if you have a LOT of elements you may observe some edge gains by dumping the entire, reordered HTML into the DOM in one go, rather than each element one after the other.

let bd = document.body,
    newHTML = Array.prototype.slice.call(bd.children)
        .map(el => bd.removeChild(el))
        .sort(function (x, y) { return /* your sort logic, compare x and y here */; })
        .map(el => el.outerHTML);
bd.innerHTML += newHTML.join('');
Mitya
  • 33,629
  • 9
  • 60
  • 107
  • I have to stress that the iFrames have already loaded, they could in principles have some latency so the reordering has not to force a reload of them. I need that the DOM elements are somehow "alive" while reordering. I do not know whether the algorithm is destructive or not. – P5music Sep 03 '20 at 10:32
0

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>
Reyno
  • 6,119
  • 18
  • 27
  • I have to stress that the iFrames have already loaded, they could in principles have some latency so the reordering has not to force a reload of them. I need that the DOM elements are somehow "alive" while reordering. I do not know whether the algorithm is destructive. – P5music Sep 03 '20 at 10:32
  • @P5music unfortunately the iframes will reload with this method. I added an extra snippet for a solution that works without reloading the iframes. – Reyno Sep 03 '20 at 10:59
  • Your second solution seems to need no sorting at all. I think the only style properties I have to set on divs are "order" (orderIndex) and "display" (flex). Am I right? And also, how to change all "order" properties of the divs in a single instruction so no partial reordering is done? – P5music Sep 03 '20 at 12:02
  • That is correct, the second solution does the sorting by number (ascending). So you can just enter the `orderIndex` (i changed the snippet to use this too). As far as the CSS you indeed only need `display:flex` on the root element and `order` on all children. If you want to go vertically you can add `flex-direction: column` to the root aswell or `flex-wrap` if you want to continue on the next line. It is unfortunaly not possible to do all reordering in a single instruction since we assign CSS. Then again the change will be almost instant unless you have a lot of elements. – Reyno Sep 03 '20 at 12:48
  • Thanks, my concern is not about the time but I want you to consider that for each setting instruction of the order property of one single div, maybe it triggers "recursive" reordering because it changes another value after the ordering has been just done and this value can break it. – P5music Sep 03 '20 at 17:19