Reduced motion starts the view still instead of locking animation off
Deploy web Studio / deploy (push) Successful in 1m3s
Deploy web Studio / deploy (push) Successful in 1m3s
With Windows "Animation effects" off, browsers report prefers-reduced-motion, and the viewer
turned every animation off and hid the animation button, so presses, belts and robot arms
stood still in Brave and Firefox with no way to start them. Now that preference only picks
the starting mode ("off"); the button stays (its tooltip says why it is off) and the
viewer's choice wins and is remembered. The CSS still-picture rules apply only until the
viewer turns animation on (.iso-anim-on).
Also: switching light -> off -> smooth redraws the scene so the animations paused by script
run again (they ignore CSS play states).
Checks: web 93/93 (reduced motion: starts still, one click runs it, kept after reload),
Studio 109/109, 70 Rust tests. Tried in real Brave (system setting) and Firefox.
This commit is contained in:
1 parent
f49798583e
commit
2442966743
4 files changed
+42
-10
No files matched your search
@@ -206,6 +206,8 @@ default ("light"): running CSS animations repaint the whole drawing every frame,
|
|||||||
which keeps a slow laptop busy. The ▷ button in the view toolbar switches
|
which keeps a slow laptop busy. The ▷ button in the view toolbar switches
|
||||||
between light, smooth and off, and the browser remembers the choice. In
|
between light, smooth and off, and the browser remembers the choice. In
|
||||||
`isokit.toml`, `[build] animation = "smooth"` sets the default for built pages.
|
`isokit.toml`, `[build] animation = "smooth"` sets the default for built pages.
|
||||||
|
When the system asks for less motion (Windows: Settings → Accessibility →
|
||||||
|
Visual effects → Animation effects off), views start still; ▷ turns them on.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
@@ -326,6 +326,26 @@ try:
|
|||||||
check("(from robot-b)" in loads and flips == 32, f"robot arms pass the goods on ({flips} poses): " + loads)
|
check("(from robot-b)" in loads and flips == 32, f"robot arms pass the goods on ({flips} poses): " + loads)
|
||||||
shot(pg, "web-factory.jpg")
|
shot(pg, "web-factory.jpg")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- a system asking for less motion
|
||||||
|
# (Windows: Animation effects off): the view starts still, but the button stays and turns it on
|
||||||
|
rctx = b.new_context(viewport={"width": 1200, "height": 800}, reduced_motion="reduce")
|
||||||
|
rp = rctx.new_page()
|
||||||
|
rp.goto(BASE)
|
||||||
|
ready(rp)
|
||||||
|
rp.wait_for_timeout(500)
|
||||||
|
ride = "() => Array.from(document.querySelectorAll('.iso-svg .iso-a')).map(e => getComputedStyle(e).transform + getComputedStyle(e).opacity).join('|')"
|
||||||
|
moves = lambda: (lambda a: (rp.wait_for_timeout(700), a != rp.evaluate(ride))[1])(rp.evaluate(ride))
|
||||||
|
check(rp.evaluate("() => __studio.view.animation()") == "off" and rp.locator('.iso-bar [data-a="anim"]').is_visible() and not moves(),
|
||||||
|
"reduced motion: the view starts still, with the animation button there")
|
||||||
|
rp.locator('.iso-bar [data-a="anim"]').click()
|
||||||
|
rp.wait_for_timeout(500)
|
||||||
|
check(rp.evaluate("() => __studio.view.animation()") == "light" and moves(), "one click turns animation on despite reduced motion")
|
||||||
|
rp.reload()
|
||||||
|
ready(rp)
|
||||||
|
rp.wait_for_timeout(500)
|
||||||
|
check(rp.evaluate("() => __studio.view.animation()") == "light" and moves(), "and the choice is remembered")
|
||||||
|
rctx.close()
|
||||||
|
|
||||||
# ---------------------------------------------------------------- folder project (OPFS stands in for a picked folder)
|
# ---------------------------------------------------------------- folder project (OPFS stands in for a picked folder)
|
||||||
demo = {f.relative_to(ROOT / "examples" / "demo").as_posix(): f.read_text(encoding="utf-8")
|
demo = {f.relative_to(ROOT / "examples" / "demo").as_posix(): f.read_text(encoding="utf-8")
|
||||||
for f in (ROOT / "examples" / "demo").rglob("*") if f.is_file() and "dist" not in f.parts}
|
for f in (ROOT / "examples" / "demo").rglob("*") if f.is_file() and "dist" not in f.parts}
|
||||||
|
|||||||
@@ -160,10 +160,11 @@ html[data-theme=light] .iso-svg .iso-glint { display: inline; }
|
|||||||
92% { transform: translate(var(--dx), var(--dy)); opacity: 1; }
|
92% { transform: translate(var(--dx), var(--dy)); opacity: 1; }
|
||||||
96%, 100% { transform: translate(var(--dx), var(--dy)); opacity: 0; }
|
96%, 100% { transform: translate(var(--dx), var(--dy)); opacity: 0; }
|
||||||
}
|
}
|
||||||
|
/* a system asking for less motion: still pictures, until the viewer turns animation on (iso-anim-on) */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.iso-svg .iso-a, .iso-svg .iso-ledr, .iso-svg .iso-ledo { animation: none !important; }
|
.iso-wrap:not(.iso-anim-on) .iso-svg .iso-a, .iso-wrap:not(.iso-anim-on) .iso-svg .iso-ledr, .iso-wrap:not(.iso-anim-on) .iso-svg .iso-ledo { animation: none !important; }
|
||||||
.iso-svg .iso-puff, .iso-svg .iso-puff2, .iso-svg .iso-smoke, .iso-svg .iso-disc, .iso-svg .iso-glint, .iso-svg .iso-rip { display: none; }
|
.iso-wrap:not(.iso-anim-on) .iso-svg :is(.iso-puff, .iso-puff2, .iso-smoke, .iso-disc, .iso-glint, .iso-rip) { display: none; }
|
||||||
.iso-svg .iso-park { display: inline; }
|
.iso-wrap:not(.iso-anim-on) .iso-svg .iso-park { display: inline; }
|
||||||
}
|
}
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.iso-stage { aspect-ratio: 1 / 1; min-height: 320px; }
|
.iso-stage { aspect-ratio: 1 / 1; min-height: 320px; }
|
||||||
|
|||||||
+16
-7
@@ -138,24 +138,32 @@
|
|||||||
// CSS animations inside the drawing repaint all of it every frame: a fifth of a fast core,
|
// CSS animations inside the drawing repaint all of it every frame: a fifth of a fast core,
|
||||||
// all of a slow one. "light" (the default) pauses them and steps them on together about ten
|
// all of a slow one. "light" (the default) pauses them and steps them on together about ten
|
||||||
// times a second, "smooth" lets them run, "off" stops them. Pan and zoom gestures pause them.
|
// times a second, "smooth" lets them run, "off" stops them. Pan and zoom gestures pause them.
|
||||||
|
// A system asking for less motion (Windows: Animation effects off) starts them off, but the
|
||||||
|
// button stays: what the viewer picks there wins and is remembered.
|
||||||
|
var reducedMotion = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
||||||
var ANIM = ["light", "smooth", "off"], ANIM_UI = {
|
var ANIM = ["light", "smooth", "off"], ANIM_UI = {
|
||||||
light: ["\u25B7", "Animation: light, about 10 steps a second (click: smooth)"],
|
light: ["\u25B7", "Animation: light, about 10 steps a second (click: smooth)"],
|
||||||
smooth: ["\u25B6", "Animation: smooth (click: off)"],
|
smooth: ["\u25B6", "Animation: smooth (click: off)"],
|
||||||
off: ["\u2016", "Animation: off (click: light)"]
|
off: ["\u2016", reducedMotion ? "Animation: off, as your system asks for less motion (click: on)" : "Animation: off (click: light)"]
|
||||||
};
|
};
|
||||||
var animMode = (function () {
|
var animMode = (function () {
|
||||||
var m = null;
|
var m = null;
|
||||||
try { m = localStorage.getItem("isokit-anim"); } catch (e) { /* storage blocked */ }
|
try { m = localStorage.getItem("isokit-anim"); } catch (e) { /* storage blocked */ }
|
||||||
return ANIM.indexOf(m) >= 0 ? m : ANIM.indexOf(opts.animation) >= 0 ? opts.animation : "light";
|
if (ANIM.indexOf(m) >= 0) return m;
|
||||||
|
if (reducedMotion) return "off";
|
||||||
|
return ANIM.indexOf(opts.animation) >= 0 ? opts.animation : "light";
|
||||||
})();
|
})();
|
||||||
var animTimer = 0, animLast = 0, animList = [], animBtn = bar.querySelector('[data-a="anim"]');
|
// animScripted: the animations of this drawing were paused by script (light mode) and ignore
|
||||||
var reducedMotion = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
// CSS play states from then on, so smooth needs the scene drawn again
|
||||||
|
var animTimer = 0, animLast = 0, animList = [], animScripted = false, animBtn = bar.querySelector('[data-a="anim"]');
|
||||||
// after every new scene and every mode change
|
// after every new scene and every mode change
|
||||||
function animSetup() {
|
function animSetup() {
|
||||||
clearInterval(animTimer); animTimer = 0; animList = [];
|
clearInterval(animTimer); animTimer = 0; animList = [];
|
||||||
wrap.classList.toggle("iso-anim-off", animMode === "off");
|
wrap.classList.toggle("iso-anim-off", animMode === "off");
|
||||||
|
// the viewer turned them on: past the reduced-motion rules in the CSS
|
||||||
|
wrap.classList.toggle("iso-anim-on", animMode !== "off");
|
||||||
if (animBtn) {
|
if (animBtn) {
|
||||||
animBtn.hidden = reducedMotion || !svg.querySelector(".iso-a, .iso-drive, .iso-ledr, .iso-ledo");
|
animBtn.hidden = !svg.querySelector(".iso-a, .iso-drive, .iso-ledr, .iso-ledo");
|
||||||
animBtn.textContent = ANIM_UI[animMode][0]; animBtn.title = ANIM_UI[animMode][1];
|
animBtn.textContent = ANIM_UI[animMode][0]; animBtn.title = ANIM_UI[animMode][1];
|
||||||
animBtn.setAttribute("aria-label", ANIM_UI[animMode][1]);
|
animBtn.setAttribute("aria-label", ANIM_UI[animMode][1]);
|
||||||
}
|
}
|
||||||
@@ -163,6 +171,7 @@
|
|||||||
animList = svg.getAnimations({ subtree: true });
|
animList = svg.getAnimations({ subtree: true });
|
||||||
if (!animList.length) return;
|
if (!animList.length) return;
|
||||||
animList.forEach(function (a) { a.pause(); });
|
animList.forEach(function (a) { a.pause(); });
|
||||||
|
animScripted = true;
|
||||||
animLast = performance.now();
|
animLast = performance.now();
|
||||||
animTimer = setInterval(animStep, 100);
|
animTimer = setInterval(animStep, 100);
|
||||||
}
|
}
|
||||||
@@ -178,7 +187,7 @@
|
|||||||
animMode = m;
|
animMode = m;
|
||||||
try { localStorage.setItem("isokit-anim", m); } catch (e) { /* storage blocked */ }
|
try { localStorage.setItem("isokit-anim", m); } catch (e) { /* storage blocked */ }
|
||||||
// animations paused by script ignore CSS from then on: draw the scene again to let them run
|
// animations paused by script ignore CSS from then on: draw the scene again to let them run
|
||||||
if (was === "light" && m === "smooth" && scn) applyScene(scn); else animSetup();
|
if (m === "smooth" && animScripted && scn) applyScene(scn); else animSetup();
|
||||||
call(opts.onAnimation, m);
|
call(opts.onAnimation, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +224,7 @@
|
|||||||
var hadScene = !!scn;
|
var hadScene = !!scn;
|
||||||
scn = d;
|
scn = d;
|
||||||
svg.innerHTML = d.html;
|
svg.innerHTML = d.html;
|
||||||
lastK = null;
|
lastK = null; animScripted = false;
|
||||||
wrap.classList.toggle("iso-still", st.cfg.running === false);
|
wrap.classList.toggle("iso-still", st.cfg.running === false);
|
||||||
metas = Array.prototype.map.call(svg.querySelectorAll(".iso-tag"), function (g, i) {
|
metas = Array.prototype.map.call(svg.querySelectorAll(".iso-tag"), function (g, i) {
|
||||||
return { el: g, p: +g.getAttribute("data-p"), w: +g.getAttribute("data-w"), x: +g.getAttribute("data-x"), y: +g.getAttribute("data-y"), i: i, hid: false };
|
return { el: g, p: +g.getAttribute("data-p"), w: +g.getAttribute("data-w"), x: +g.getAttribute("data-x"), y: +g.getAttribute("data-y"), i: i, hid: false };
|
||||||
|
|||||||
Reference in new issue
Block a user