Skip to content

Commit

Permalink
Made overlay canvas size consistent across different browsers. Base s…
Browse files Browse the repository at this point in the history
…ize is not anymore the screen size due to browser limitations, but the window size. This will trigger additional canvas resize on window resize.
  • Loading branch information
davidetan committed Jan 20, 2025
1 parent 19aac5b commit 59d6cf1
Showing 1 changed file with 96 additions and 47 deletions.
143 changes: 96 additions & 47 deletions spine-ts/spine-webgl/src/SpineWebComponentWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1527,15 +1527,15 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
// we cannot use window.resize event since it does not fire when body resizes, but not the window
// Alternatively, we can store the body size, check the current body size in the loop (like the translateCanvas), and
// if they differs call the resizeCallback. I already tested it, and it works. ResizeObserver should be more efficient.
this.resizeObserver = new ResizeObserver(this.resizeCallback);
if (this.scrollable) {
const style = getComputedStyle(this.parentElement!);
if (style.transform === "none" && !this.scrollableTweakOff) {
// if the element is scrollable, the user does not disable translate tweak, and the parent did not have already a transform, add the tweak
if (this.scrollable && !this.scrollableTweakOff && getComputedStyle(this.parentElement!).transform === "none") {
this.parentElement!.style.transform = `translateZ(0)`;
}
this.resizeObserver = new ResizeObserver(this.resizeCallback);
this.resizeObserver.observe(this.parentElement!);
} else {
this.resizeObserver.observe(document.body);
window.addEventListener("resize", this.resizeCallback)
}

this.skeletonList.forEach((widget) => {
Expand All @@ -1546,12 +1546,17 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
this.startRenderingLoop();
}

private hasCssTweakOff() {
return this.scrollableTweakOff && getComputedStyle(this.parentElement!).transform === "none";
}

private running = false;
disconnectedCallback (): void {
const id = this.getAttribute('id');
if (id) SpineWebComponentOverlay.OVERLAY_LIST.delete(id);
// window.removeEventListener("scroll", this.scrollHandler);
window.removeEventListener("load", this.onLoadCallback);
window.removeEventListener("resize", this.resizeCallback);
window.screen.orientation.removeEventListener('change', this.orientationChangeCallback);
this.intersectionObserver?.disconnect();
this.resizeObserver?.disconnect();
Expand Down Expand Up @@ -1582,7 +1587,6 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,

private resizeCallback = () => {
this.updateCanvasSize();
this.zoomHandler();
}

private orientationChangeCallback = () => {
Expand All @@ -1599,7 +1603,6 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,

private onLoadCallback = () => {
this.updateCanvasSize();
this.zoomHandler();
this.scrollHandler();
if (!this.loaded) {
this.parentElement!.appendChild(this);
Expand Down Expand Up @@ -1657,13 +1660,13 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
// fps top-left span
if (SpineWebComponentWidget.SHOW_FPS) {
if (!this.fpsAppended) {
this.root.appendChild(this.fps);
this.div.appendChild(this.fps);
this.fpsAppended = true;
}
this.fps.innerText = this.time.framesPerSecond.toFixed(2) + " fps";
} else {
if (this.fpsAppended) {
this.root.removeChild(this.fps);
this.div.removeChild(this.fps);
this.fpsAppended = false;
}
}
Expand Down Expand Up @@ -1950,6 +1953,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
this.cursorWorldY = tempVector.y;
}

// return true if updated
private cursorWidgetUpdate (widget: SpineWebComponentWidget): boolean {
if (widget.worldX === Infinity) return false;

Expand Down Expand Up @@ -1987,8 +1991,11 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
},
down: (x, y, ev) => {
const input = getInput(ev);

this.cursorUpdate(input);

this.skeletonList.forEach(widget => {
if (!widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return;
if (!this.cursorWidgetUpdate(widget) || !widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return;

widget.cursorEventUpdate("down", ev);

Expand Down Expand Up @@ -2064,9 +2071,18 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
this.root.appendChild(this.div!);
} else {
this.div?.remove();
this.div!.style.width = this.parentElement!.scrollWidth + "px";
this.div!.style.height = this.parentElement!.scrollHeight + "px";
// this.canvas.style.transform = `translate(${-this.overflowLeftSize}px,${-this.overflowTopSize}px)`;

if (this.hasCssTweakOff()) {
// this case lags if scrolls or position fixed
// users should never use tweak off, unless the parent container has already a transform
this.div!.style.width = this.parentElement!.clientWidth + "px";
this.div!.style.height = this.parentElement!.clientHeight + "px";
this.canvas.style.transform = `translate(${-this.overflowLeftSize}px,${-this.overflowTopSize}px)`;
} else {
this.div!.style.width = this.parentElement!.scrollWidth + "px";
this.div!.style.height = this.parentElement!.scrollHeight + "px";
}

this.root.appendChild(this.div!);
}

Expand All @@ -2084,15 +2100,6 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
height = this.parentElement!.clientHeight;
}

// this is needed because screen size is wrong when zoom levels occurs
// zooming out will make the canvas smaller and its known that zoom level
// on browsers is not reliable
// ideally, window.innerWidth/innerHeight would be preferrable. However
// on mobile browsers the dynamic search bar makes the innerHeight smaller
// at the beginning (changing the canvas size at each scroll is not ideal)
// width = Math.max(width, window.innerWidth);
// height = Math.max(height, window.innerHeight);

if (this.currentCanvasBaseWidth !== width || this.currentCanvasBaseHeight !== height) {
this.currentCanvasBaseWidth = width;
this.currentCanvasBaseHeight = height;
Expand All @@ -2106,6 +2113,24 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
this.canvas.style.height = totalHeight + "px";
this.resize(totalWidth, totalHeight);
}

this.skeletonList.forEach((widget) => {
// inside mode scale automatically to fit the skeleton within its parent
if (widget.mode !== "origin" && widget.fit !== "none") return;

const skeleton = widget.skeleton;
if (!skeleton) return;

// I'm not sure about this. With mode origin and fit none:
// case 1) If I comment this scale code, the skeleton is never scaled and will be always at the same size and won't change size while zooming
// case 2) Otherwise, the skeleton is loaded always at the same size, but changes size while zooming
const scale = window.devicePixelRatio;
skeleton.scaleX = skeleton.scaleX / widget.currentScaleDpi * scale;
skeleton.scaleY = skeleton.scaleY / widget.currentScaleDpi * scale;
widget.currentScaleDpi = scale;

});

}

private translateCanvas () {
Expand All @@ -2116,27 +2141,38 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
scrollPositionX += window.scrollX;
scrollPositionY += window.scrollY;
} else {
scrollPositionX += this.parentElement!.scrollLeft;
scrollPositionY += this.parentElement!.scrollTop;
}

this.canvas.style.transform = `translate(${scrollPositionX}px,${scrollPositionY}px)`;
}
// Ideally this should be the only scrollable case (scrollable-tweak-off not enabled or at least an ancestor has transform)
// I'd like to get rid of the code below
if (!this.hasCssTweakOff()) {
scrollPositionX += this.parentElement!.scrollLeft;
scrollPositionY += this.parentElement!.scrollTop;
} else {
const { left, top } = this.parentElement!.getBoundingClientRect();
scrollPositionX += left + window.scrollX;
scrollPositionY += top + window.scrollY;

let offsetParent = this.offsetParent;
do {
if (offsetParent === document.body) break;

const htmlOffsetParentElement = offsetParent as HTMLElement;
if (htmlOffsetParentElement.style.position === "fixed" || htmlOffsetParentElement.style.position === "sticky" || htmlOffsetParentElement.style.position === "absolute") {
const parentRect = htmlOffsetParentElement.getBoundingClientRect();
this.div.style.transform = `translate(${left - parentRect.left}px,${top - parentRect.top}px)`;
return;
}

private zoomHandler = () => {
this.skeletonList.forEach((widget) => {
// inside mode scale automatically to fit the skeleton within its parent
if (widget.mode !== "origin" && widget.fit !== "none") return;
offsetParent = htmlOffsetParentElement.offsetParent;
} while (offsetParent);

const skeleton = widget.skeleton;
if (!skeleton) return;
const scale = window.devicePixelRatio;
skeleton.scaleX = skeleton.scaleX / widget.currentScaleDpi * scale;
skeleton.scaleY = skeleton.scaleY / widget.currentScaleDpi * scale;
widget.currentScaleDpi = scale;
});
this.div.style.transform = `translate(${scrollPositionX + this.overflowLeftSize}px,${scrollPositionY + this.overflowTopSize}px)`;
return;
}

}

this.resize(parseFloat(this.canvas.style.width), parseFloat(this.canvas.style.height));
this.canvas.style.transform = `translate(${scrollPositionX}px,${scrollPositionY}px)`;
}

private resize (width: number, height: number) {
Expand All @@ -2155,16 +2191,29 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
return document.body.getBoundingClientRect();
}

// screen size remain the same when it is rotated
// we need to swap them based and the orientation angle
private previousWidth = 0;
private previousHeight = 0;
private previousDPR = 0;
private static readonly WIDTH_INCREMENT = 1.15;
private static readonly HEIGHT_INCREMENT = 1.2;
private getScreenSize () {
const { screen } = window;
const { width, height } = window.screen;
const angle = screen.orientation.angle;
const rotated = angle === 90 || angle === 270;
return rotated
? { width: height, height: width }
: { width, height };
let width = window.innerWidth;
let height = window.innerHeight;

const dpr = window.devicePixelRatio;
if (dpr !== this.previousDPR) {
this.previousDPR = dpr;
this.previousWidth = this.previousWidth === 0 ? width : width * SpineWebComponentOverlay.WIDTH_INCREMENT;
this.previousHeight = height * SpineWebComponentOverlay.HEIGHT_INCREMENT;
} else {
if (width > this.previousWidth) this.previousWidth = width * SpineWebComponentOverlay.WIDTH_INCREMENT;
if (height > this.previousHeight) this.previousHeight = height * SpineWebComponentOverlay.HEIGHT_INCREMENT;
}

return {
width: this.previousWidth,
height: this.previousHeight,
}
}

/*
Expand Down

0 comments on commit 59d6cf1

Please sign in to comment.