YES canvas: size off its own container rect, not window.innerHeight
Deploy / deploy (push) Successful in 34s
Deploy / deploy (push) Successful in 34s
setupCanvas() measured window.innerWidth/innerHeight directly, then a resize listener gated on innerWidth changing (guessing which resize events were "real" vs mobile Safari's address-bar animation). That measurement had nothing to do with .hero-yes's actual CSS height, so the canvas and its box could end up disagreeing - which is what was producing the observed jump in content below the hero on scroll, not the choice of viewport unit on its own. Switched to ResizeObserver on .hero-canvas (which tracks .hero-yes via inset:0), using its contentRect directly. This also drops the innerWidth-gating heuristic entirely: a fixed-height box never fires a ResizeObserver callback during the toolbar animation in the first place, so there's nothing to gate. Also updated the mouse/touch position normalization to use the canvas's own displayWidth/displayHeight instead of window.innerWidth/innerHeight, for the same reason - and fixed the .hero-yes comment, which still described the lvh reasoning from a prior attempt after the height value itself had been changed back to svh directly on origin/main.
This commit is contained in:
+11
-11
@@ -153,17 +153,17 @@ main.loading, main.not-found {
|
|||||||
position: relative;
|
position: relative;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/* lvh (large viewport height), not svh: svh pins to the *smallest*
|
/* svh (small viewport height): a fixed, stable length - computed
|
||||||
viewport (toolbar visible), which never grows once the browser
|
once, never recalculated mid-scroll as mobile Safari's address
|
||||||
auto-collapses its toolbar on the first scroll - so the actual
|
bar hides/shows, unlike plain vh/dvh on some engines. Pinned to
|
||||||
visible screen becomes taller than this box, and the content
|
the *smallest* possible viewport (toolbar visible) so the box
|
||||||
below appears to snap into the newly-revealed space the moment
|
never needs to grow into space the toolbar might reclaim.
|
||||||
that happens. lvh pins to the toolbar-collapsed size instead,
|
yes.js's canvas measures this box's own rendered rect (not
|
||||||
matching the settled state scrolling actually lands on, so
|
window.innerWidth/innerHeight) via ResizeObserver, so the canvas
|
||||||
there's no gap to reveal. (svh is the right choice for the
|
always matches whatever height this resolves to - the two can't
|
||||||
opposite case - a fixed element that must never be hidden behind
|
disagree, which is what was actually causing the content below to
|
||||||
the toolbar - not a hero at the top of a scrolling page.)
|
visibly jump.
|
||||||
Plain-vh fallback declared first - an engine without lvh support
|
Plain-vh fallback declared first - an engine without svh support
|
||||||
ignores the invalid second line rather than falling through to
|
ignores the invalid second line rather than falling through to
|
||||||
auto height, which would collapse this to the height of its
|
auto height, which would collapse this to the height of its
|
||||||
in-flow content and clip the canvas via overflow:hidden below. */
|
in-flow content and clip the canvas via overflow:hidden below. */
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export class RasterizedYES {
|
|||||||
this.wiggleAmount = 0.5;
|
this.wiggleAmount = 0.5;
|
||||||
|
|
||||||
this.setupCanvas();
|
this.setupCanvas();
|
||||||
|
this.setupResizeObserver();
|
||||||
this.setupMouseTracking();
|
this.setupMouseTracking();
|
||||||
this.setupClickHandler();
|
this.setupClickHandler();
|
||||||
this.rasterizeText();
|
this.rasterizeText();
|
||||||
@@ -51,15 +52,24 @@ export class RasterizedYES {
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
if (this._resizeHandler) {
|
if (this._resizeObserver) {
|
||||||
window.removeEventListener('resize', this._resizeHandler);
|
this._resizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Measures the canvases' own containing box (.hero-canvas, which
|
||||||
|
// tracks .hero-yes via inset:0) rather than window.innerWidth/
|
||||||
|
// innerHeight. window.innerHeight is the live viewport - it moves
|
||||||
|
// as mobile Safari's address bar hides/shows on scroll, which
|
||||||
|
// doesn't itself match whatever fixed value .hero-yes's CSS height
|
||||||
|
// is pinned to. Sizing the canvas off the box's own rect instead
|
||||||
|
// means the two can never disagree, whichever viewport unit ends
|
||||||
|
// up governing that box.
|
||||||
setupCanvas() {
|
setupCanvas() {
|
||||||
const pixelRatio = window.devicePixelRatio || 1;
|
const pixelRatio = window.devicePixelRatio || 1;
|
||||||
const width = window.innerWidth;
|
const rect = this.rasterCanvas.parentElement.getBoundingClientRect();
|
||||||
const height = window.innerHeight;
|
const width = rect.width;
|
||||||
|
const height = rect.height;
|
||||||
|
|
||||||
this.rasterCanvas.style.width = width + 'px';
|
this.rasterCanvas.style.width = width + 'px';
|
||||||
this.rasterCanvas.style.height = height + 'px';
|
this.rasterCanvas.style.height = height + 'px';
|
||||||
@@ -77,27 +87,27 @@ export class RasterizedYES {
|
|||||||
this.displayWidth = width;
|
this.displayWidth = width;
|
||||||
this.displayHeight = height;
|
this.displayHeight = height;
|
||||||
this.pixelRatio = pixelRatio;
|
this.pixelRatio = pixelRatio;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this._resizeHandler) {
|
// ResizeObserver reacts to the container's box actually changing
|
||||||
// Mobile browsers change window.innerHeight (not width) as
|
// size - a fixed-height box (svh, lvh, or plain px) never fires
|
||||||
// the address bar hides/shows during scroll, firing a
|
// this at all during the address-bar animation, so there's no
|
||||||
// `resize` with no real layout change to react to.
|
// need for the old "only redraw if width changed" heuristic that
|
||||||
// Genuine resizes (orientation change, desktop window
|
// guessed at which resize events were real.
|
||||||
// drag) always change the width too, so gate the redraw
|
setupResizeObserver() {
|
||||||
// on that instead of reacting to every resize event.
|
this._resizeObserver = new ResizeObserver((entries) => {
|
||||||
this._resizeHandler = () => {
|
const { width, height } = entries[0].contentRect;
|
||||||
if (window.innerWidth === this.displayWidth) return;
|
if (width === this.displayWidth && height === this.displayHeight) return;
|
||||||
this.setupCanvas();
|
this.setupCanvas();
|
||||||
this.rasterizeText();
|
this.rasterizeText();
|
||||||
};
|
});
|
||||||
window.addEventListener('resize', this._resizeHandler);
|
this._resizeObserver.observe(this.rasterCanvas.parentElement);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupMouseTracking() {
|
setupMouseTracking() {
|
||||||
const updatePosition = (clientX, clientY) => {
|
const updatePosition = (clientX, clientY) => {
|
||||||
this.mouseX = clientX / window.innerWidth;
|
this.mouseX = clientX / this.displayWidth;
|
||||||
this.mouseY = clientY / window.innerHeight;
|
this.mouseY = clientY / this.displayHeight;
|
||||||
this.containmentStrength = 0.1 + (this.mouseX * 0.9);
|
this.containmentStrength = 0.1 + (this.mouseX * 0.9);
|
||||||
this.wiggleAmount = 0.1 + (this.mouseY * 1.9);
|
this.wiggleAmount = 0.1 + (this.mouseY * 1.9);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user