Not sure if there is a solution to this problem. I want to know if there is a way to detect when the browser actually updates the UI.
Lets say I want add a class to an element just before I run a long-running, synchronous block of code, after the synchronous function runs, the class would be removed.
el.classList.add('waiting');
longRunningSynchronousTask();
el.classList.remove('waiting');
If I run this code, the long running task will get started, and block the UI before the class is added. I tried using MutationObserver
's, and checking the element with getComputedStyle
, but that doesn't work because those don't reflect when the realtime updates the UI.
Using a worker might be the best solution (drop the long running code in a worker and post a message when it's complete to remove the class). This isn't really an option me, due to needing to support older browsers.
The only solution that works outside of implementing a worker is using setTimeout
. I'd love to know if anyone has run into this problem and if there is any way to detect a repaint/reflow of the UI in the browser.
See my example for a better illustration of the problem.
const el = document.querySelector('.target');
const isRed = c => c === 'red' || 'rgb(255, 0, 0)';
let classActuallyApplied = false;
let requestId;
const longRunningTask = (mil, onComplete) => {
if (classActuallyApplied) {
cancelAnimationFrame(requestId);
var start = new Date().getTime();
while (new Date().getTime() < start + mil);
onComplete();
} else {
requestId = requestAnimationFrame(longRunningTask.bind(null, mil, onComplete));
}
}
const observer = new MutationObserver(function(mutations) {
classActuallyApplied = mutations
.filter(mutation => {
return mutation.type === 'attributes' &&
mutation.attributeName === 'class' &&
mutation.target.classList.contains('waiting');
}).length > 0;
});
const observerConfig = {
attributes: true,
childList: false,
characterData: false
};
observer.observe(el, observerConfig);
el.addEventListener('click', e => {
el.classList.add('waiting');
longRunningTask(1000 * 5, () => {
// el.classList.remove('waiting');
});
});
.target {
height: 100px;
width: 100px;
background-color: gray;
}
.waiting {
background-color: red;
}
<div class="target">
target
</div>
I believe requestIdleCallback is what you are looking for.
From developers.google.com 2016:
In the same way that adopting requestAnimationFrame allowed us to schedule animations properly and maximize our chances of hitting 60fps, requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.
It is an experimental feature but (as of March 2021) is supported in most modern browsers. See MDN Compatibility Table and caniuse.com.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With