Skip to content

Commit 59d6cf1

Browse files
committed
Made overlay canvas size consistent across different browsers. Base size is not anymore the screen size due to browser limitations, but the window size. This will trigger additional canvas resize on window resize.
1 parent 19aac5b commit 59d6cf1

File tree

1 file changed

+96
-47
lines changed

1 file changed

+96
-47
lines changed

spine-ts/spine-webgl/src/SpineWebComponentWidget.ts

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,15 +1527,15 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
15271527
// we cannot use window.resize event since it does not fire when body resizes, but not the window
15281528
// Alternatively, we can store the body size, check the current body size in the loop (like the translateCanvas), and
15291529
// if they differs call the resizeCallback. I already tested it, and it works. ResizeObserver should be more efficient.
1530-
this.resizeObserver = new ResizeObserver(this.resizeCallback);
15311530
if (this.scrollable) {
1532-
const style = getComputedStyle(this.parentElement!);
1533-
if (style.transform === "none" && !this.scrollableTweakOff) {
1531+
// if the element is scrollable, the user does not disable translate tweak, and the parent did not have already a transform, add the tweak
1532+
if (this.scrollable && !this.scrollableTweakOff && getComputedStyle(this.parentElement!).transform === "none") {
15341533
this.parentElement!.style.transform = `translateZ(0)`;
15351534
}
1535+
this.resizeObserver = new ResizeObserver(this.resizeCallback);
15361536
this.resizeObserver.observe(this.parentElement!);
15371537
} else {
1538-
this.resizeObserver.observe(document.body);
1538+
window.addEventListener("resize", this.resizeCallback)
15391539
}
15401540

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

1549+
private hasCssTweakOff() {
1550+
return this.scrollableTweakOff && getComputedStyle(this.parentElement!).transform === "none";
1551+
}
1552+
15491553
private running = false;
15501554
disconnectedCallback (): void {
15511555
const id = this.getAttribute('id');
15521556
if (id) SpineWebComponentOverlay.OVERLAY_LIST.delete(id);
15531557
// window.removeEventListener("scroll", this.scrollHandler);
15541558
window.removeEventListener("load", this.onLoadCallback);
1559+
window.removeEventListener("resize", this.resizeCallback);
15551560
window.screen.orientation.removeEventListener('change', this.orientationChangeCallback);
15561561
this.intersectionObserver?.disconnect();
15571562
this.resizeObserver?.disconnect();
@@ -1582,7 +1587,6 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
15821587

15831588
private resizeCallback = () => {
15841589
this.updateCanvasSize();
1585-
this.zoomHandler();
15861590
}
15871591

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

16001604
private onLoadCallback = () => {
16011605
this.updateCanvasSize();
1602-
this.zoomHandler();
16031606
this.scrollHandler();
16041607
if (!this.loaded) {
16051608
this.parentElement!.appendChild(this);
@@ -1657,13 +1660,13 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
16571660
// fps top-left span
16581661
if (SpineWebComponentWidget.SHOW_FPS) {
16591662
if (!this.fpsAppended) {
1660-
this.root.appendChild(this.fps);
1663+
this.div.appendChild(this.fps);
16611664
this.fpsAppended = true;
16621665
}
16631666
this.fps.innerText = this.time.framesPerSecond.toFixed(2) + " fps";
16641667
} else {
16651668
if (this.fpsAppended) {
1666-
this.root.removeChild(this.fps);
1669+
this.div.removeChild(this.fps);
16671670
this.fpsAppended = false;
16681671
}
16691672
}
@@ -1950,6 +1953,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
19501953
this.cursorWorldY = tempVector.y;
19511954
}
19521955

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

@@ -1987,8 +1991,11 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
19871991
},
19881992
down: (x, y, ev) => {
19891993
const input = getInput(ev);
1994+
1995+
this.cursorUpdate(input);
1996+
19901997
this.skeletonList.forEach(widget => {
1991-
if (!widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return;
1998+
if (!this.cursorWidgetUpdate(widget) || !widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return;
19921999

19932000
widget.cursorEventUpdate("down", ev);
19942001

@@ -2064,9 +2071,18 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
20642071
this.root.appendChild(this.div!);
20652072
} else {
20662073
this.div?.remove();
2067-
this.div!.style.width = this.parentElement!.scrollWidth + "px";
2068-
this.div!.style.height = this.parentElement!.scrollHeight + "px";
2069-
// this.canvas.style.transform = `translate(${-this.overflowLeftSize}px,${-this.overflowTopSize}px)`;
2074+
2075+
if (this.hasCssTweakOff()) {
2076+
// this case lags if scrolls or position fixed
2077+
// users should never use tweak off, unless the parent container has already a transform
2078+
this.div!.style.width = this.parentElement!.clientWidth + "px";
2079+
this.div!.style.height = this.parentElement!.clientHeight + "px";
2080+
this.canvas.style.transform = `translate(${-this.overflowLeftSize}px,${-this.overflowTopSize}px)`;
2081+
} else {
2082+
this.div!.style.width = this.parentElement!.scrollWidth + "px";
2083+
this.div!.style.height = this.parentElement!.scrollHeight + "px";
2084+
}
2085+
20702086
this.root.appendChild(this.div!);
20712087
}
20722088

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

2087-
// this is needed because screen size is wrong when zoom levels occurs
2088-
// zooming out will make the canvas smaller and its known that zoom level
2089-
// on browsers is not reliable
2090-
// ideally, window.innerWidth/innerHeight would be preferrable. However
2091-
// on mobile browsers the dynamic search bar makes the innerHeight smaller
2092-
// at the beginning (changing the canvas size at each scroll is not ideal)
2093-
// width = Math.max(width, window.innerWidth);
2094-
// height = Math.max(height, window.innerHeight);
2095-
20962103
if (this.currentCanvasBaseWidth !== width || this.currentCanvasBaseHeight !== height) {
20972104
this.currentCanvasBaseWidth = width;
20982105
this.currentCanvasBaseHeight = height;
@@ -2106,6 +2113,24 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
21062113
this.canvas.style.height = totalHeight + "px";
21072114
this.resize(totalWidth, totalHeight);
21082115
}
2116+
2117+
this.skeletonList.forEach((widget) => {
2118+
// inside mode scale automatically to fit the skeleton within its parent
2119+
if (widget.mode !== "origin" && widget.fit !== "none") return;
2120+
2121+
const skeleton = widget.skeleton;
2122+
if (!skeleton) return;
2123+
2124+
// I'm not sure about this. With mode origin and fit none:
2125+
// 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
2126+
// case 2) Otherwise, the skeleton is loaded always at the same size, but changes size while zooming
2127+
const scale = window.devicePixelRatio;
2128+
skeleton.scaleX = skeleton.scaleX / widget.currentScaleDpi * scale;
2129+
skeleton.scaleY = skeleton.scaleY / widget.currentScaleDpi * scale;
2130+
widget.currentScaleDpi = scale;
2131+
2132+
});
2133+
21092134
}
21102135

21112136
private translateCanvas () {
@@ -2116,27 +2141,38 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
21162141
scrollPositionX += window.scrollX;
21172142
scrollPositionY += window.scrollY;
21182143
} else {
2119-
scrollPositionX += this.parentElement!.scrollLeft;
2120-
scrollPositionY += this.parentElement!.scrollTop;
2121-
}
21222144

2123-
this.canvas.style.transform = `translate(${scrollPositionX}px,${scrollPositionY}px)`;
2124-
}
2145+
// Ideally this should be the only scrollable case (scrollable-tweak-off not enabled or at least an ancestor has transform)
2146+
// I'd like to get rid of the code below
2147+
if (!this.hasCssTweakOff()) {
2148+
scrollPositionX += this.parentElement!.scrollLeft;
2149+
scrollPositionY += this.parentElement!.scrollTop;
2150+
} else {
2151+
const { left, top } = this.parentElement!.getBoundingClientRect();
2152+
scrollPositionX += left + window.scrollX;
2153+
scrollPositionY += top + window.scrollY;
2154+
2155+
let offsetParent = this.offsetParent;
2156+
do {
2157+
if (offsetParent === document.body) break;
2158+
2159+
const htmlOffsetParentElement = offsetParent as HTMLElement;
2160+
if (htmlOffsetParentElement.style.position === "fixed" || htmlOffsetParentElement.style.position === "sticky" || htmlOffsetParentElement.style.position === "absolute") {
2161+
const parentRect = htmlOffsetParentElement.getBoundingClientRect();
2162+
this.div.style.transform = `translate(${left - parentRect.left}px,${top - parentRect.top}px)`;
2163+
return;
2164+
}
21252165

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

2131-
const skeleton = widget.skeleton;
2132-
if (!skeleton) return;
2133-
const scale = window.devicePixelRatio;
2134-
skeleton.scaleX = skeleton.scaleX / widget.currentScaleDpi * scale;
2135-
skeleton.scaleY = skeleton.scaleY / widget.currentScaleDpi * scale;
2136-
widget.currentScaleDpi = scale;
2137-
});
2169+
this.div.style.transform = `translate(${scrollPositionX + this.overflowLeftSize}px,${scrollPositionY + this.overflowTopSize}px)`;
2170+
return;
2171+
}
2172+
2173+
}
21382174

2139-
this.resize(parseFloat(this.canvas.style.width), parseFloat(this.canvas.style.height));
2175+
this.canvas.style.transform = `translate(${scrollPositionX}px,${scrollPositionY}px)`;
21402176
}
21412177

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

2158-
// screen size remain the same when it is rotated
2159-
// we need to swap them based and the orientation angle
2194+
private previousWidth = 0;
2195+
private previousHeight = 0;
2196+
private previousDPR = 0;
2197+
private static readonly WIDTH_INCREMENT = 1.15;
2198+
private static readonly HEIGHT_INCREMENT = 1.2;
21602199
private getScreenSize () {
2161-
const { screen } = window;
2162-
const { width, height } = window.screen;
2163-
const angle = screen.orientation.angle;
2164-
const rotated = angle === 90 || angle === 270;
2165-
return rotated
2166-
? { width: height, height: width }
2167-
: { width, height };
2200+
let width = window.innerWidth;
2201+
let height = window.innerHeight;
2202+
2203+
const dpr = window.devicePixelRatio;
2204+
if (dpr !== this.previousDPR) {
2205+
this.previousDPR = dpr;
2206+
this.previousWidth = this.previousWidth === 0 ? width : width * SpineWebComponentOverlay.WIDTH_INCREMENT;
2207+
this.previousHeight = height * SpineWebComponentOverlay.HEIGHT_INCREMENT;
2208+
} else {
2209+
if (width > this.previousWidth) this.previousWidth = width * SpineWebComponentOverlay.WIDTH_INCREMENT;
2210+
if (height > this.previousHeight) this.previousHeight = height * SpineWebComponentOverlay.HEIGHT_INCREMENT;
2211+
}
2212+
2213+
return {
2214+
width: this.previousWidth,
2215+
height: this.previousHeight,
2216+
}
21682217
}
21692218

21702219
/*

0 commit comments

Comments
 (0)