Как дождаться рендеринга видимых изменений DOM?

Если у нас есть JS-код браузера, который выполняет много вычислений, как обеспечить надежный выход, чтобы позволить браузеру выполнять промежуточный рендеринг, например. мы хотим отображать индикаторы прогресса? Я попробовал обработчик ожидания requestAnimationFrame, но это — по крайней мере в Chromium — не всегда позволяет выполнить рендеринг.

Пример:

...compute...

statusElement.innerText = 'were almost done!';
await new Promise(resolve => window.requestAnimationFrame(resolve));

...compute...

часто, но не всегда показывает «были почти готовы», пока второе вычисление не завершится и код JS не завершится.

Итак, есть ли какой-нибудь канонический способ убедиться, что это изменение DOM видно, прежде чем продолжить?

Используете Web Worker?

Ivar 11.04.2024 14:48

@ivar он спрашивает о видимости изменений DOM

underscore 11.04.2024 15:02

@underscore Да, я знаю. Если вы используете Web Worker и публикуете сообщения для обновления пользовательского интерфейса, то изменения DOM не являются проблемой.

Ivar 11.04.2024 15:09

Имеет ли это какое-либо отношение к принудительному перекомпоновке макета? Проверьте stackoverflow.com/questions/21664940/…

James 11.04.2024 15:12

@James Принудительное перекомпоновывание не обязательно приводит к рендерингу/рисованию экрана

Bergi 11.04.2024 15:26
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
5
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обратный вызов requestAnimationFrame вызывается непосредственно перед следующей перерисовкой. Именно со следующим вызовом requestAnimationFrame, который вы делаете после вызова этого обратного вызова, вы можете запланировать выполнение нового обратного вызова после этой перерисовки и перед следующей. Таким образом, только когда выполняется второй обратный вызов (переданный второму вызову requestAnimationFrame), перерисовка уже выполнена после ваших изменений.

Поэтому я бы посоветовал дождаться этого обещанного requestAnimationFrame в самом начале вашей функции обработки тяжелых данных, а затем сделать это снова позже.

Демо:

const beforePaint = () => new Promise(requestAnimationFrame);

const busyLoop = delay => { 
    const stop = performance.now() + delay; 
    while (performance.now() < stop) {
    }
}

async function heavyTask(statusElement) {
    await beforePaint(); // <-- add one of these at the very start
    // Here the first paint since the call of `heavyTask` has not yet occurred.
    statusElement.innerText = '0%';
    await beforePaint(); // during the awaiting the first paint will happen
    // Here we can be sure that "0%" has rendered
    busyLoop(300);
    statusElement.innerText = '30%';
    await beforePaint();
    busyLoop(100);
    statusElement.innerText = '40%';
    await beforePaint();
    busyLoop(200);
    statusElement.innerText = '60%';
    await beforePaint();
    busyLoop(300);
    statusElement.innerText = '90%';
    await beforePaint();
    busyLoop(100);
    statusElement.innerText = '100%';
}

heavyTask(document.querySelector("div"));
<div></div>

Если вы сначала удалите это await beforePaint();, вы можете не увидеть отображение «0%» по объясненной причине.

Понятно... Итак, чтобы гарантировать, что определенный reportProgress всегда будет работать независимо от того, где он вставлен, он должен ожидать двух кадров анимации - одного до и одного после изменения DOM?

dronus 11.04.2024 16:40

Да, в этом вся суть. Но если вам нужно отрисовать последовательность фрагментов, вам не нужно удваивать ожидание кадров анимации. Вам просто нужно на один больше, чем может подсказать интуиция: и поместите его в самом начале.

trincot 11.04.2024 16:45

Другие вопросы по теме