According to the HTML Standart in the event loop during updateing the rendering 2 things happen sequentially:
- For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
- For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp.
Therefore I suppose that IF before the event loop run both
- a scroll happens (via
scrollToporscrollLeftproperty of a scrollable element change orscrollToexecution) - a callback scheduled via
requestAnimationFrame
THEN there should be at least one scroll event for each scrolled element in any event loop run,and callbacks execution always happens after scroll event listeners.
I wrote a function that checks this proposition and tried it in Chrome and FireFox:
function Test(iterations, stepTimeout) {
let xOuter = document.createElement("div");
xOuter.style.width = "100px";
xOuter.style.height = "100px";
xOuter.style.overflow = "scroll";
//xOuter.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
xOuter.style.zIndex = "10000";
xOuter.style.position = "absolute";
xOuter.style.top = "0";
xOuter.style.left = "0";
xOuter.style.scrollBehavior = "auto";
let xInner = document.createElement("div");
xInner.style.width = "200%";
xInner.style.height = "200%";
xOuter.appendChild(xInner);
let xState = "What happens during each interation of Step:\n";
let xStepIndex = 0,
xScrollUpdateIndex = 0,
xLastExecutedScrollUpdate = -1;
xOuter.addEventListener("scroll", () => {
xState += `scroll-${xScrollUpdateIndex} `;
xLastExecutedScrollUpdate = xScrollUpdateIndex;
});
document.body.appendChild(xOuter);
function Step() {
if (xStepIndex > iterations) {
xOuter && xOuter.parentNode && xOuter.parentNode.removeChild(xOuter);
console.log(xState);
return;
}
let xScrollProp = Math.random() > 0.5 ? "scrollLeft" : "scrollTop";
let xOldScrollPos = xOuter[xScrollProp],
xNewScrollPos;
do {
xNewScrollPos = Math.round(Math.random() * 200);
} while (xOldScrollPos == xNewScrollPos);
xState += `\n ${xStepIndex++} : [${xNewScrollPos}] `;
xOuter[xScrollProp] = xNewScrollPos; // scroll event scheduled
xScrollUpdateIndex++;
let xCurrentScrollUpdateIndex = xScrollUpdateIndex;
requestAnimationFrame(() => {
if (xCurrentScrollUpdateIndex > xLastExecutedScrollUpdate)
console.log("Scroll failed to fire before rAF");
xState += `rAF-${xCurrentScrollUpdateIndex} `;
});
new Promise((resolve) => setTimeout(() => resolve(), stepTimeout)).then(Step);
}
Step();
}
Test(200, 10);
However it looks like in some event loop runs scroll event doesn't fire.
Is this an expected behavior?