Запуск визуализации всех элементов «видимость содержимого: авто» ДО того, как пользователь перейдет к ним, не блокируя основной поток в течение >50 мс

#javascript #html #css #optimization #layout

Вопрос:

Я хочу, чтобы страница загружалась быстро, но при этом пользователь не видел задержки перед отображением содержимого при прокрутке страницы вниз.

У меня есть большая html — страница со множеством <dl> элементов.

Чтобы ускорить загрузку страницы, я установил content-visibility: auto css. Видишь https://web.dev/content-visibility/

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

Из-за сложности содержимого <dl> s возникает заметная задержка, когда пользователь прокручивает во время <dl> отображения s по мере их появления в окне просмотра.

Таким образом, вскоре после загрузки страницы я хочу <dl> сразу же отобразить все закадровые изображения (до того, как пользователь перейдет к ним), но таким образом, чтобы это не блокировало основной поток, а прокрутка оставалась отзывчивой.

Итак, я хочу установить content-visibility: visible значение <dl> s, начиная с верхнего, и не блокировать основной поток (скажем, более 50 мс). Итак, возможно, разрешить взаимодействие с пользователем после рендеринга каждого <dl> из них .

Итак, мне нужна версия ниже, которая не блокирует основной поток:

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

Мой вариант использования: Моя страница состоит из заметок по математике, которые я хочу разместить на одной странице, чтобы уменьшить трения. Я использую katex, который (на данный момент, прежде чем мы сможем использовать mathml в chrome) создает очень большой и сложный html, который даже на стороне сервера по-прежнему требует много времени для верстки и визуализации в браузере.

Комментарии:

1. Похоже, что видимость контента не поддерживается в Firefox или Safari-это имеет значение или вам нужно поддерживать только Edge/Chrome?

2. На данный момент я рад нацелиться на Edge/Chrome. При необходимости я могу передать mathml в Firefox или Safari.

3. Я обнаружил, что когда dlElement.clientHeight != 1000px «что-то» явно произошло, но я не уверен, что это «что-то» — стиль, макет или краска. Тем не менее, я рад попробовать использовать это в качестве условия, прежде чем переходить к установке следующего <dl> в видимое. Как мне это сделать? Я пробовал различные комбинации setTimeout и requestAnimationFrame, но, похоже, я все еще блокирую основной поток.

4. Я собираюсь попробовать это, но это может занять у меня некоторое время: 75% на странице: Использование requestIdleCallback для внесения изменений в DOM developers.google.com/web/updates/2015/08/…

Ответ №1:

Вместо того, чтобы оставлять это без ответа, позвольте мне вставить мой (незараженный) код, который я тестировал в течение последних нескольких недель.

 // --------- 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 amp;amp; 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();
}