From 3ab0c5cf79d698877e64e4ee92af88757b47f4a8 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 7 Jun 2026 13:06:51 -0500 Subject: [PATCH] Alternate approach to delaying showing problem content until after MathJax content has rendered. In addition to setting the `.problem-content` div not visible, this injects an overlay div that is position absolutely over the `.problem-content` div with the same size. It also gets that `.problem-content` class so that it appears like an empty problem. It also pulsatess slowly using essentially bootstrap's `placeholder-glow` animation (although implemented with a JavaScript animation instead of css). In addition a height transition is set on the div so that it grows a bit slower than just the immediate jump in size. Perhaps this will allay the appearance of the content below jumping down in some cases since now there is something shown instead of just an empty space. This is an alternate approach to #3003 that maybe some will like better? --- htdocs/js/MathJaxConfig/mathjax-config.js | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/htdocs/js/MathJaxConfig/mathjax-config.js b/htdocs/js/MathJaxConfig/mathjax-config.js index 4d0e15e139..e89889aa93 100644 --- a/htdocs/js/MathJaxConfig/mathjax-config.js +++ b/htdocs/js/MathJaxConfig/mathjax-config.js @@ -1,4 +1,6 @@ if (!window.MathJax) { + const problems = []; + window.MathJax = { tex: { packages: { '[+]': webworkConfig?.showMathJaxErrors ? [] : ['noerrors'] } }, loader: { @@ -103,6 +105,16 @@ if (!window.MathJax) { MathJax.startup.defaultReady(); MathJax.startup.document.constructor.ProcessBits.allocate('findScripts'); + }, + pageReady() { + return MathJax.startup.defaultPageReady().then(() => { + for (const [problemContent, loaderOverlay, resizeObserver] of problems) { + resizeObserver.disconnect(); + loaderOverlay.remove(); + problemContent.style.visibility = ''; + } + problems.length = 0; + }); } }, options: { @@ -143,4 +155,35 @@ if (!window.MathJax) { } } }; + + for (const problemContent of document.querySelectorAll('.problem-content')) { + problemContent.style.visibility = 'hidden'; + const loaderOverlay = document.createElement('div'); + loaderOverlay.classList.add('problem-content'); + const bodyRectangle = problemContent.getBoundingClientRect(); + loaderOverlay.style.position = 'absolute'; + loaderOverlay.style.top = `${bodyRectangle.y}px`; + loaderOverlay.style.left = `${bodyRectangle.x}px`; + loaderOverlay.style.width = `${bodyRectangle.width}px`; + loaderOverlay.style.height = `${bodyRectangle.height}px`; + loaderOverlay.style.overflow = 'clip'; + loaderOverlay.style.transition = 'height 0.3s ease'; + loaderOverlay.animate([{ opacity: 1 }, { opacity: 0.2 }, { opacity: 1 }], { + duration: 2000, + iterations: Infinity, + easing: 'ease-in-out' + }); + loaderOverlay.style.cursor = 'wait'; + + problemContent.after(loaderOverlay); + const resizeObserver = new ResizeObserver(() => { + const bodyRectangle = problemContent.getBoundingClientRect(); + loaderOverlay.style.top = `${bodyRectangle.top}px`; + loaderOverlay.style.left = `${bodyRectangle.left}px`; + loaderOverlay.style.width = `${bodyRectangle.width}px`; + loaderOverlay.style.height = `${bodyRectangle.height}px`; + }); + resizeObserver.observe(problemContent); + problems.push([problemContent, loaderOverlay, resizeObserver]); + } }