简体   繁体   中英

Trigger rendering of all `content-visibility: auto` elements BEFORE a user scrolls to them without blocking main thread for >50ms

I want to have a fast page-load but also not have the user see a delay before content is rendered while scrolling down the page.

I have a large html page with many <dl> elements.

To speed up page load, I have set content-visibility: auto in css. See https://web.dev/content-visibility/

dl {
  content-visibility: auto;
  contain-intrinsic-size: 1000px;
}

Due to the complexity of the contents of the <dl> s there is a noticeable delay when a user scrolls while the <dl> s are rendered as they come into the viewport.

Thus, soon after page-load I want to render all the offscreen <dl> straight away (before a user scrolls to them) but in such a way that it does not block the main thread and scrolling remains responsive.

So, I want to set content-visibility: visible on the <dl> s starting from the top one, and not blocking the main thread (for more than say 50ms). So, maybe allowing user interaction after rendering each <dl> .

So, I need a version of the below, that doesn't block the main thread:

document.querySelectorAll('dl').forEach(function(dlElement, currentIndex, listObj) { dlElement.style['content-visibility'] = 'visible' });

My use case: My page is of math notes, which I want all on one page to reduce friction. I use katex which (for now, before we can use mathml on chrome) produces very large and complex html, which even server-side rendered still takes a lot of time for layout and rendering on the browser.

Rather than leave this unanswered, let me paste my (unperfected) code I have been testing for the last few weeks.

// --------- Shim requestIdleCallback if not supported in browser ----------------------------------

window.requestIdleCallback =
  window.requestIdleCallback ||
  function (cb) {
    var start = Date.now();
    return setTimeout(function () {
      cb({
        didTimeout: false,
        timeRemaining: function () {
          return Math.max(0, 50 - (Date.now() - start));
        }
      });
    }, 1);
  }

window.cancelIdleCallback =
  window.cancelIdleCallback ||
  function (id) {
    clearTimeout(id);
  }

// Global
let isRequestIdleCallbackScheduled = false;
let nodeToRemove = null;
let isVisualUpdateScheduled = false;
let totalDlElementsLeftToShow = 0;

function startIfNeeded() {

  totalDlElementsLeftToShow = document.querySelectorAll('dl:not([style*="content-visibility: visible;"]), .ra:not([style*="content-visibility: visible;"]').length;

  if (totalDlElementsLeftToShow > 0) {
    console.log('Not a mobile - Let\'s make things visible when we are next idle');
    scheduleVisibilityChanges();
  }
  else {
    console.log('Apparently, all visible');
  }
}

function scheduleVisibilityChanges() {

  // Only schedule the rIC if one has not already been set.
  if (isRequestIdleCallbackScheduled) {
    //console.log('returning because idle callback scheduled');
    startIfNeeded();
  }

  isRequestIdleCallbackScheduled = true;

  //console.log('scheduling visibility changes when next idle or in 30 seconds at the latest');

  requestIdleCallback(processHiddenElements, { timeout: 30000 });
}

function processHiddenElements(deadline) {

  // Since our operation takes a while, we only want to go ahead if we have at least 50ms.
  while (deadline.timeRemaining() > 49 && totalDlElementsLeftToShow > 0) {

    // console.log('time remaining is ', deadline.timeRemaining(), '- scheduling next visual update');

    // Don't set content-visibility immediately wait for the next
    // requestAnimationFrame callback.
    scheduleVisualUpdateIfNeeded();
  }

  // console.log('Deadline reached, will check again the next time the user is idle if there are more events still to send');

  if (totalDlElementsLeftToShow > 0) {
    requestIdleCallback(processHiddenElements, { timeout: 30000 });
  }
}

function scheduleVisualUpdateIfNeeded() {

  if (isVisualUpdateScheduled) {
    // console.log('returning - visual update already scheduled')
    return;
  };

  isVisualUpdateScheduled = true;

  // console.log('requesting animation frame');

  requestAnimationFrame(setContentToVisible);
}

function setContentToVisible() {
  // console.log('changing visibility of element ');

  let completeHiddenNodeList = document.querySelectorAll('dl:not([style*="content-visibility: visible;"]), .ra:not([style*="content-visibility: visible;"]');

  // We chunk the layout changes
  let i;
  let numberToChunk = 20;

  if (completeHiddenNodeList.length < 20) {
    numberToChunk = completeHiddenNodeList.length
  }

  for (i = 0; i < numberToChunk; ++i) {
    completeHiddenNodeList[i].style.contentVisibility = 'visible';
  }

  isVisualUpdateScheduled = false;
  isRequestIdleCallbackScheduled = false;
  totalDlElementsLeftToShow = totalDlElementsLeftToShow - numberToChunk;
}

if (!navigator.userAgentData.mobile) {
  startIfNeeded();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM