Change element attributes (datasets) in one operation - avoid Reflows

128 Views Asked by At

I'm trying to avoid CSS Reflows, usually DocumentFragment is enough for my needs.

I have cases when I modify/add/remove datasets, The problem is that each dataset require one operation which causes reflow..

element.dataset is a read only object, so I wondered how can I do it with only one reflow instead of 3 in this example?

element.dataset.a='1'
delete element.b
element.dataset.c='2'

Does replacing the element completely is the only way to achieve this?

1

There are 1 best solutions below

0
Kaiido On

Changing the dataset of an element will not cause a synchronous reflow. You can do it as many times as you'd like during the same task.

To test if something causes a reflow or not, you can use a CSS transition.
Going from a known state, then setting an intermediary state, triggering what should cause the reflow and finally setting back the original state.
If the tested action did trigger a reflow, then a new transition from the intermediary state to the final one will happen. If no reflow did happen, then no transition will happen.

function testReflow(func) {
  return new Promise( (res, rej) => {
    const elem = document.querySelector(".reflow-tester");
    // set "intermediary" values
    elem.style.opacity = 1;
    elem.style.transition = "none";
    try { func(elem); } catch(err) { rej(err) }
    elem.style.opacity = 0;
    elem.style.transition = "opacity 0.01s";

    // if the tested func does trigger a reflow
    // the transition will start from 1 to 0
    // otherwise it won't happen (from 0 to 0)    
    elem.addEventListener("transitionstart", (evt) => {
      res(true); // let the caller know the result
    }, { once: true });
    // if the transition didn't start in 100ms, it didn't cause a reflow
    setTimeout(() => res(false), 100);
  });
}

(async () => {
  // wait 1s before executing the tests to be sure we're not in weird first paint
  await new Promise((res) => setTimeout(res, 1000));
  // first testing with a well known reflow trigger
  const offsetWidth = await
  testReflow((elem)=>elem.offsetWidth);
  console.log("offsetWidth getter:", offsetWidth);
  // now with dataset
  const dataset = await testReflow((elem) => {
    elem.dataset.foo = "bar";
    elem.dataset.bar = "baz";
    elem.dataset.baz = "bla";
  });
  console.log("dataset:", dataset);
})().catch(console.error);
.reflow-tester {
  opacity: 0;
}
<div class="reflow-tester">Tester</div>