JRJurman
JRJurman

Reputation: 1728

How can I group Javascript actions that force reflow?

I have a project which is responsible for managing the rendering of elements, but I'm running into a performance issue replacing elements and then focusing on whatever had focus before.

Below is a minimal example that replicates the performance issue:

const renderPage = () => {
  // get the old section element
  const oldSection = document.querySelector('section')

  // create a new section element (we'll replaceWith later)
  const newSection = document.createElement('section')
  
  // create the render button
  const newButton = document.createElement('button')
  newButton.innerHTML = 'Render Page'
  newButton.onclick = renderPage
  newSection.appendChild(newButton)
  
  // create a bunch of elements
  const dummyDivs = [...new Array(100000)].forEach(() => {
    const dummy = document.createElement('div')
    dummy.innerHTML = 'dummy'
    newSection.appendChild(dummy)
  })
  
  // replace the old page with the new one (causes forced reflow)
  oldSection.replaceWith(newSection)
  // reattach focus on the button (causes forced reflow)
  newButton.focus()
}

window.renderPage = renderPage
<section>
  <button onclick="renderPage()">Render</button>
</section>

When running this locally, I see the following in the performance report in Chrome/Edge Performance Report, forced reflow is highlighted under both a replaceWith and focus event

Both replaceWith and focus are triggering forced reflow. Is there a way to batch or group these actions so that only a single reflow occurs? I realize that there's no way to really get around this happening at all, but if I can batch them, I think that might improve my performance.

Upvotes: 3

Views: 698

Answers (1)

coyer
coyer

Reputation: 4367

Indeed, focus always causes a reflow: What forces layout / reflow

So what you may do, is to reduce the reflowtime by inserting the new button standalone, initiate focus and after that you can append other childs:

Working example: Example

const renderPage = () => {
  // get the old section element
  const oldSection = document.querySelector('section')

  // create a new section element (we'll replaceWith later)
  const newSection = document.createElement('section')
  
  // create the render button
  const newButton = document.createElement('button')
  newButton.innerHTML = 'Render Page'
  newButton.onclick = renderPage
  newSection.appendChild(newButton)
  
  // create a bunch of elements
  const dummies = []; //  store in seperate array
  const dummyDivs = [...new Array(100000)].forEach(() => {
    const dummy = document.createElement('div')
    dummy.innerHTML = 'dummy';
    dummies.push(dummy)
  })
  //  insert new section only with new button
  oldSection.replaceWith(newSection)
  newButton.focus(); // always causes reflow; but fast because it's only one element
  //  store all other nodes after focus
  newSection.append(...dummies)
}

window.renderPage = renderPage

Upvotes: 2

Related Questions