From 82aef794427092a2d96b3410016dbf0b87b9f31a 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 20fbace262..710729714c 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: { @@ -134,4 +146,35 @@ if (!window.MathJax) { ignoreHtmlClass: 'tex2jax_ignore' } }; + + 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]); + } }