Pan/zoom gestures move the drawing as a CSS-transformed layer, a picture at the scale last drawn. A long touchpad pinch showed an ever more stretched, blurry view (3.6x at the end of a 1.2 s pinch), and every zoom ended with a blur-then-snap 180 ms after its last step. Now the view is drawn anew during the gesture once the picture is 30% off in scale (at most every 120 ms; draw() keeps the gesture state), and at its true scale 90 ms after the last step. Measured in Brave at 1.5x pixel density: sharp ~130 ms instead of ~240 ms after a pinch, crisp on the way. Cost at 6x CPU slowdown: 11.4 -> 14.6 ms per wheel step in the Studio, 9.1 -> 10.5 on a page, still no frame over 50 ms. check_web: a long pinch is drawn anew on the way and sharp within 150 ms.
This commit is contained in:
1 parent
cddf834e56
commit
ccfd619a99
3 files changed
+50
-9
No files matched your search
@@ -101,6 +101,13 @@ the tags only); hover 22 → 15 ms per move (tooltip placed once per frame on it
|
||||
readout ten times a second); rotate 226 → 154 ms, edit 409 → 305 ms until painted (WASM built with
|
||||
opt-level 3: engine render 11.7 → 9.4 ms at full speed).
|
||||
|
||||
Sharp zooming: the moved layer is a picture at the scale last drawn, so a long touchpad pinch showed
|
||||
an ever more stretched, blurry view (3.6x at the end of a 1.2 s pinch) and every zoom ended with a
|
||||
blur-then-snap 180 ms after its last step. Now the view is drawn anew during the gesture once the
|
||||
picture is 30% off in scale (at most every 120 ms), and at its true scale 90 ms after the last step:
|
||||
sharp about 130 instead of 240 ms after a pinch, at a cost of 11.4 → 14.6 ms per wheel step at 6x
|
||||
slowdown (still no frame over 50 ms).
|
||||
|
||||
Production lines: Producer (a press making cartons, crates, drums, sacks or parts at a set rate
|
||||
per minute, its ram going down once per item) and Conveyor (drawn the way it runs, belt width,
|
||||
height, rise, speed; corners join without overlap). A conveyor starting at a producer's output, or
|
||||
|
||||
@@ -359,6 +359,28 @@ try:
|
||||
check(herd == "1 bull, 1 cow, 1 calf" and abs(rot - 45) < 1e-6, f"elephants: {herd}; the lane bends at {rot} degrees")
|
||||
shot(pg, "web-country.jpg")
|
||||
|
||||
# a long pinch (Ctrl + small wheel steps) stays sharp: the view is drawn anew during the
|
||||
# gesture whenever the stretched picture is 30% off, and at its true scale soon after it ends
|
||||
pg.evaluate("""() => { const svg = document.querySelector('.iso-stage .iso-svg'); window.__vbs = [];
|
||||
new MutationObserver(() => __vbs.push(performance.now())).observe(svg, {attributes: true, attributeFilter: ['viewBox']}); }""")
|
||||
sb = pg.locator(".iso-stage").bounding_box()
|
||||
pg.mouse.move(sb["x"] + sb["width"] * 0.6, sb["y"] + sb["height"] * 0.5)
|
||||
pg.keyboard.down("Control")
|
||||
t0 = pg.evaluate("performance.now()")
|
||||
for i in range(40):
|
||||
pg.mouse.wheel(0, -8)
|
||||
pg.wait_for_timeout(12)
|
||||
t1 = pg.evaluate("performance.now()")
|
||||
pg.keyboard.up("Control")
|
||||
pg.wait_for_timeout(400)
|
||||
draws = pg.evaluate("__vbs")
|
||||
during = [t for t in draws if t0 <= t <= t1]
|
||||
after = [t - t1 for t in draws if t > t1]
|
||||
tf = pg.evaluate("document.querySelector('.iso-stage .iso-svg').style.transform")
|
||||
check(len(during) >= 2 and after and after[0] < 150 and tf == "",
|
||||
f"a long pinch is drawn anew {len(during)} times on the way and sharp {round(after[0]) if after else None} ms after it ends")
|
||||
pg.evaluate("__studio.view.fit()")
|
||||
|
||||
# ---------------------------------------------------------------- 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")
|
||||
|
||||
+21
-9
@@ -250,30 +250,42 @@
|
||||
}
|
||||
// Pan and zoom gestures move the drawing as one compositor layer (a CSS transform from the
|
||||
// viewBox last drawn, vbC, to the view asked for, vb): no repaint and no style work per step.
|
||||
// apply() commits vb as the viewBox (one repaint) when the gesture ends, and for every other change.
|
||||
var vbC = null, live = false, commitT = 0, lastK = null;
|
||||
// That layer is a picture at the scale last drawn, so zooming in stretches it (blurry) and
|
||||
// zooming out shrinks it (blank edges): once it is 30% off, draw() paints the view anew in the
|
||||
// middle of the gesture, at most every 120 ms. apply() ends the gesture 90 ms after its last
|
||||
// step and commits every other change. A draw costs one repaint (~10 ms for 2000 nodes).
|
||||
var vbC = null, live = false, commitT = 0, lastK = null, drawnAt = 0;
|
||||
var now = window.performance ? function () { return performance.now(); } : Date.now;
|
||||
function applyLive() {
|
||||
if (!vb) return;
|
||||
if (!vbC) { apply(); return; }
|
||||
if (!live) wrap.classList.add("iso-gesture");
|
||||
live = true;
|
||||
clearTimeout(commitT); commitT = setTimeout(apply, 90);
|
||||
var s = vbC.w / vb.w;
|
||||
if ((s > 1.3 || s < 0.7) && now() - drawnAt > 120) { draw(); return; }
|
||||
var s1 = size()[0] / vb.w, fitNow = vb.w >= fitW * 0.995;
|
||||
// zoomed in, touch drags pan the view instead of the page (as apply() does)
|
||||
// zoomed in, touch drags pan the view instead of the page (as draw() does)
|
||||
if (fitNow !== atFit) { atFit = fitNow; svg.style.touchAction = atFit ? "pan-y" : "none"; }
|
||||
svg.style.transform = "translate(" + ((vbC.x - vb.x) * s1).toFixed(2) + "px," + ((vbC.y - vb.y) * s1).toFixed(2) + "px) scale(" + (vbC.w / vb.w).toFixed(5) + ")";
|
||||
clearTimeout(commitT); commitT = setTimeout(apply, 180);
|
||||
svg.style.transform = "translate(" + ((vbC.x - vb.x) * s1).toFixed(2) + "px," + ((vbC.y - vb.y) * s1).toFixed(2) + "px) scale(" + s.toFixed(5) + ")";
|
||||
}
|
||||
function apply() {
|
||||
if (!vb) return;
|
||||
clearTimeout(commitT); commitT = 0;
|
||||
if (live) { live = false; svg.style.transform = ""; wrap.classList.remove("iso-gesture"); }
|
||||
// paints vb at its true scale: the viewBox, tag size and touch panning follow it
|
||||
function draw() {
|
||||
vbC = { x: vb.x, y: vb.y, w: vb.w };
|
||||
drawnAt = now();
|
||||
svg.style.transform = "";
|
||||
svg.setAttribute("viewBox", r1(vb.x) + " " + r1(vb.y) + " " + r1(vb.w) + " " + r1(vb.h));
|
||||
var sz = size(), k = (vb.w / sz[0]).toFixed(4);
|
||||
// tags keep their screen size by --iso-k; set on their group alone, so the rest is not restyled
|
||||
if (k !== lastK) { lastK = k; var tg = svg.querySelector(".iso-tags"); (tg || svg).style.setProperty("--iso-k", k); }
|
||||
atFit = vb.w >= fitW * 0.995;
|
||||
svg.style.touchAction = atFit ? "pan-y" : "none";
|
||||
}
|
||||
function apply() {
|
||||
if (!vb) return;
|
||||
clearTimeout(commitT); commitT = 0;
|
||||
if (live) { live = false; wrap.classList.remove("iso-gesture"); }
|
||||
draw();
|
||||
wrap.classList.toggle("iso-zoomed", !atFit);
|
||||
schedule();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user