Web Compatibility Support • Re: aueb.gr - text layout/scrolling issues
Thanks for the marquee info. And thanks to sinfulosd for massively improving Greasemonkey for UXP.
Took me an insane amount of time (don't ask), but got AI to fix the layout and restore scrolling with a userscript. The result is satisfying but I don't know if it was worth the time.
If I got the whole thing correctly, the only drawback was not being able to use the "proper" method (CSS Animations). It also wasn't able to identify why it failed to work and move the text.
requestAnimationFrame(). was used instead. Can't say how "heavier" or inappropriate this is. Τhe fully commented userscript:
// ==UserScript==
// @name AUEB Marquee Compatibility
// @namespace aueb-marquee-compatibility
// @version 0.5
// @description Restore scrolling news ticker on the AUEB website
// @author ChatGPT (GPT-6)
// @include https://www.aueb.gr/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function () {
"use strict";
/*
* AUEB Marquee Compatibility
*
* Site-specific compatibility workaround for the scrolling news
* ticker on the Athens University of Economics and Business website.
* This is NOT a general-purpose HTML <marquee> polyfill.
*
* The script addresses three separate requirements:
*
* 1. Layout: Keep the news ticker within its intended area instead
* of allowing its contents to overflow the surrounding page.
*
* 2. Scrolling: Reproduce the marquee's horizontal movement using
* requestAnimationFrame(), with hover/focus pausing and automatic
* resumption.
*
* 3. Content width: Ensure that the movable container spans the
* entire news sequence, preventing premature text clipping.
*
* The original news content, links, and their order are preserved.
* Animation is suspended in background tabs.
*
* No external libraries or network requests are required.
*/
// Locate the ticker and prevent duplicate initialization.
var marquee = document.querySelector("marquee");
if (!marquee || marquee.hasAttribute("data-aueb-fixed")) return;
marquee.setAttribute("data-aueb-fixed", "true");
/*
* Group the original news items into one movable container.
* Existing DOM nodes are moved rather than copied or recreated,
* preserving their links, content, and order.
*/
var content = document.createElement("span");
content.className = "aueb-ticker-content";
while (marquee.firstChild)
content.appendChild(marquee.firstChild);
marquee.appendChild(content);
/*
* Remove inline handlers intended for the native marquee.
* The replacement animation manages pausing independently.
*/
["onmouseover", "onmouseout", "onfocus", "onblur"].forEach(function (attr) {
marquee.removeAttribute(attr);
});
/*
* Establish the ticker's visible area.
*
* The outer element occupies the available width, keeps the news
* on one line, and clips content outside its boundaries.
* This corrects the original layout overflow independently
* of whether scrolling is active.
*/
marquee.style.setProperty("display", "block", "important");
marquee.style.setProperty("width", "100%", "important");
marquee.style.setProperty("max-width", "100%", "important");
marquee.style.setProperty("min-width", "0", "important");
marquee.style.setProperty("overflow", "hidden", "important");
marquee.style.setProperty("white-space", "nowrap", "important");
/*
* Prepare the inner container for horizontal movement.
*
* During testing, CSS keyframe animations (using 'transform'
* or 'left') did not produce visible scrolling, even though
* the animation was reported as running and computed transform
* values changed.
*
* Direct JavaScript updates to 'left' worked correctly, so
* this implementation uses requestAnimationFrame() for smooth,
* rendering-synchronized movement.
*
* The underlying cause of the CSS animation behavior was not
* conclusively identified; it may involve the site's layout
* or its interaction with the browser's rendering engine.
*/
content.style.setProperty("display", "inline-block", "important");
content.style.setProperty("position", "relative", "important");
content.style.setProperty("white-space", "nowrap", "important");
content.style.setProperty("animation", "none", "important");
content.style.setProperty("transform", "none", "important");
/*
* Ensure the movable container spans the entire news sequence.
*
* Its automatically calculated width may be limited to the
* visible ticker width, even though its children extend much
* farther horizontally. Moving such a narrow container can
* cause the overflowing text to be clipped prematurely.
*
* scrollWidth measures the full content extent. Assigning it
* explicitly ensures that all news items remain available
* throughout the animation.
*/
content.style.setProperty("width", content.scrollWidth + "px", "important");
/*
* Animation state.
*
* Speed is expressed in pixels per second, independent of the
* display refresh rate. Starting at zero makes the first
* news item visible immediately.
*
* requestAnimationFrame() was selected instead of a fixed
* setInterval() timer because it synchronizes position updates
* with the browser's rendering cycle, producing smoother movement.
*
* Direct 'left' updates may involve layout/paint work, so
* animation is suspended when the document is hidden.
*/
var speed = 50;
var x = 0;
var previous = null;
var frameId = null;
function frame(time) {
frameId = null;
// No animation updates are needed in background tabs.
if (document.hidden) {
previous = null;
return;
}
/*
* Pause during mouse hover or keyboard focus.
*
* Checking the current state on every frame allows the
* animation to resume without depending on mouseleave
* or other event notifications.
*/
var paused = marquee.matches(":hover") ||
marquee.contains(document.activeElement);
if (!paused && previous !== null) {
/*
* Calculate movement using elapsed time.
* Cap individual steps at 10 pixels to avoid sudden
* jumps after rendering delays.
*/
x -= Math.min((time - previous) / 1000 * speed, 10);
/*
* Restart at the right edge after the complete
* news sequence has moved out of view.
*/
if (x < -content.scrollWidth)
x = marquee.clientWidth;
content.style.setProperty("left", x + "px", "important");
}
// Refresh the time reference even while paused.
previous = time;
frameId = requestAnimationFrame(frame);
}
/*
* Stop and restart frame scheduling when tab visibility changes.
* Resetting the time reference prevents a positional jump
* when returning to the page.
*/
function stop() {
if (frameId !== null)
cancelAnimationFrame(frameId);
frameId = null;
previous = null;
}
function start() {
if (frameId === null && !document.hidden) {
previous = null;
frameId = requestAnimationFrame(frame);
}
}
document.addEventListener("visibilitychange", function () {
if (document.hidden) stop();
else start();
});
// Display the first news item immediately and start scrolling.
content.style.setProperty("left", "0px", "important");
start();
})();
* * *