From d92046f32528253d7cc65296f8d0df16544bdaf2 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 20 Aug 2024 15:06:06 +0200 Subject: [PATCH] Scroll should be resolved --- spine-ts/spine-webgl/example/canvas5.html | 229 +++++++++--------- .../spine-webgl/src/SpineCanvasOverlay.ts | 148 +++++------ 2 files changed, 177 insertions(+), 200 deletions(-) diff --git a/spine-ts/spine-webgl/example/canvas5.html b/spine-ts/spine-webgl/example/canvas5.html index ae0dd670f..ed32eb05b 100644 --- a/spine-ts/spine-webgl/example/canvas5.html +++ b/spine-ts/spine-webgl/example/canvas5.html @@ -169,8 +169,8 @@ overlay.addSkeleton( { element: document.getElementById(`section2-element`), mode: 'origin', - xAxis: .25, - yAxis: .75, + xAxis: .5, + yAxis: 1, }, ); @@ -474,20 +474,20 @@ overlay.addSkeleton( // ); - // ///////////////////// - // // start section 1 // - // ///////////////////// - // overlay.addSkeleton( - // { - // atlasPath: "assets/spineboy-pma.atlas", - // skeletonPath: "assets/spineboy-pro.skel", - // animation: 'walk', - // }, - // document.querySelectorAll(`#section1-element`), - // ); - // ///////////////////// - // // end section 1 // - // ///////////////////// + ///////////////////// + // start section 1 // + ///////////////////// + overlay.addSkeleton( + { + atlasPath: "assets/spineboy-pma.atlas", + skeletonPath: "assets/spineboy-pro.skel", + animation: 'walk', + }, + document.querySelectorAll(`#section1-element`), + ); + ///////////////////// + // end section 1 // + ///////////////////// @@ -502,11 +502,10 @@ overlay.addSkeleton( scale: .25, }, { - element: document.getElementById(`section1-element-x`), + element: document.getElementById(`section2-element`), mode: 'origin', - xAxis: 0, - yAxis: 0, - debug: true, + xAxis: .5, + yAxis: 1, }, ); ///////////////////// @@ -514,114 +513,114 @@ overlay.addSkeleton( ///////////////////// - // ///////////////////// - // // start section 3 // - // ///////////////////// - // overlay.addSkeleton( - // { - // atlasPath: "assets/spineboy-pma.atlas", - // skeletonPath: "assets/spineboy-pro.skel", - // animation: 'jump', - // }, - // { - // element: document.getElementById(`section3-element`), - // mode: 'inside', // default - // offsetX: 100, - // offsetY: 50, - // }, - // ); - // ///////////////////// - // // end section 3 // - // ///////////////////// + ///////////////////// + // start section 3 // + ///////////////////// + overlay.addSkeleton( + { + atlasPath: "assets/spineboy-pma.atlas", + skeletonPath: "assets/spineboy-pro.skel", + animation: 'jump', + }, + { + element: document.getElementById(`section3-element`), + mode: 'inside', // default + offsetX: 100, + offsetY: 50, + }, + ); + ///////////////////// + // end section 3 // + ///////////////////// - // ///////////////////// - // // start section 4 // - // ///////////////////// - // const { skeleton, state } = await overlay.addSkeleton( - // { - // atlasPath: "assets/raptor-pma.atlas", - // skeletonPath: "assets/raptor-pro.skel", - // animation: 'walk', - // }, - // document.getElementById(`section4-element`) - // ); + ///////////////////// + // start section 4 // + ///////////////////// + const { skeleton, state } = await overlay.addSkeleton( + { + atlasPath: "assets/raptor-pma.atlas", + skeletonPath: "assets/raptor-pro.skel", + animation: 'walk', + }, + document.getElementById(`section4-element`) + ); - // let isRoaring = false; - // setInterval(() => { - // const newAnimation = isRoaring ? "walk" : "roar"; - // state.setAnimation(0, newAnimation, true); - // overlay.recalculateBounds(skeleton); - // isRoaring = !isRoaring; - // }, 4000); + let isRoaring = false; + setInterval(() => { + const newAnimation = isRoaring ? "walk" : "roar"; + state.setAnimation(0, newAnimation, true); + overlay.recalculateBounds(skeleton); + isRoaring = !isRoaring; + }, 4000); - // ///////////////////// - // // end section 4 // - // ///////////////////// + ///////////////////// + // end section 4 // + ///////////////////// - // ///////////////////// - // // start section 5 // - // ///////////////////// - // ///////////////////// - // // end section 5 // - // ///////////////////// + ///////////////////// + // start section 5 // + ///////////////////// + ///////////////////// + // end section 5 // + ///////////////////// - // ///////////////////// - // // start section 6 // - // ///////////////////// - // overlay.addSkeleton( - // { - // atlasPath: "assets/cloud-pot-pma.atlas", - // skeletonPath: "assets/cloud-pot.skel", - // animation: 'playing-in-the-rain', - // }, - // document.getElementById(`section6-element`) - // ); - // ///////////////////// - // // end section 6 // - // ///////////////////// + ///////////////////// + // start section 6 // + ///////////////////// + overlay.addSkeleton( + { + atlasPath: "assets/cloud-pot-pma.atlas", + skeletonPath: "assets/cloud-pot.skel", + animation: 'playing-in-the-rain', + }, + document.getElementById(`section6-element`) + ); + ///////////////////// + // end section 6 // + ///////////////////// - // ///////////////////// - // // start section 7 // - // ///////////////////// - // overlay.addSkeleton( - // { - // atlasPath: "assets/owl-pma.atlas", - // skeletonPath: "assets/owl-pro.skel", - // animation: 'idle', - // }, - // { - // element: document.getElementById(`section7-element`), - // debug: true, - // } - // ); - // ////////////////////// - // // end section 7 // - // ////////////////////// + ///////////////////// + // start section 7 // + ///////////////////// + overlay.addSkeleton( + { + atlasPath: "assets/owl-pma.atlas", + skeletonPath: "assets/owl-pro.skel", + animation: 'idle', + }, + { + element: document.getElementById(`section7-element`), + debug: true, + } + ); + ////////////////////// + // end section 7 // + ////////////////////// - // ///////////////////// - // // start section 8 // - // ///////////////////// - // overlay.addSkeleton( - // { - // atlasPath: "assets/celestial-circus-pma.atlas", - // skeletonPath: "assets/celestial-circus-pro.skel", - // animation: 'wings-and-feet', - // }, - // { - // element: document.getElementById(`section8-element`), - // draggable: true, - // debug: true, - // } - // ); - // ////////////////////// - // // end section 8 // - // ////////////////////// + ///////////////////// + // start section 8 // + ///////////////////// + overlay.addSkeleton( + { + atlasPath: "assets/celestial-circus-pma.atlas", + skeletonPath: "assets/celestial-circus-pro.skel", + animation: 'wings-and-feet', + }, + { + element: document.getElementById(`section8-element`), + draggable: true, + debug: true, + } + ); + ////////////////////// + // end section 8 // + ////////////////////// })(); diff --git a/spine-ts/spine-webgl/src/SpineCanvasOverlay.ts b/spine-ts/spine-webgl/src/SpineCanvasOverlay.ts index 516f27e12..c0e3ff8d8 100644 --- a/spine-ts/spine-webgl/src/SpineCanvasOverlay.ts +++ b/spine-ts/spine-webgl/src/SpineCanvasOverlay.ts @@ -80,27 +80,35 @@ export class SpineCanvasOverlay { private resizeObserver:ResizeObserver; private disposed = false; - // how may pixels to add to the bottom (to avoid cut on edge during scrolling) - private readonly additionalPixelsBottom = 300; + // how many pixels to add to the edges as parcentages (to avoid cut on edge during scrolling) + private overflowTop = .1; + private overflowBottom = .2; + private overflowLeft = .1; + private overflowRight = .1; + private overflowLeftSize: number; + private overflowTopSize: number; - // how much the canvas is translated above (to avoid cut on edge during scrolling) - private readonly offsetHeight = 100; - - // the actual base translation - private offsetHeightDraw: number; + private div: HTMLDivElement; /** Constructs a new spine canvas, rendering to the provided HTML canvas. */ constructor () { + this.div = document.createElement('div'); + this.div.style.position = "absolute"; + this.div.style.top = "0"; + this.div.style.left = "0"; + this.div.style.setProperty("pointer-events", "none"); + this.div.style.overflow = "hidden" + // this.div.style.backgroundColor = "rgba(0, 255, 0, 0.3)"; + this.canvas = document.createElement('canvas'); - document.body.appendChild(this.canvas); + this.div.appendChild(this.canvas); + document.body.appendChild(this.div); this.canvas.style.position = "absolute"; this.canvas.style.top = "0"; this.canvas.style.left = "0"; this.canvas.style.setProperty("pointer-events", "none"); - this.offsetHeightDraw = this.offsetHeight; this.canvas.style.transform =`translate(0px,0px)`; // this.canvas.style.display = "inline"; - // this.canvas.style.overflow = "hidden"; // useless // this.canvas.style.setProperty("will-change", "transform"); // performance seems to be even worse with this uncommented // resize and zoom @@ -111,7 +119,11 @@ export class SpineCanvasOverlay { this.spineCanvas.renderer.resize(ResizeMode.Expand); }); this.resizeObserver.observe(document.body); + this.updateCanvasSize(); + this.overflowLeftSize = this.overflowLeft * document.documentElement.clientWidth; + this.overflowTopSize = this.overflowTop * document.documentElement.clientHeight; + this.zoomHandler(); // scroll @@ -297,7 +309,8 @@ export class SpineCanvasOverlay { htmlOptionsList.forEach((list) => { const { element, mode, debug, offsetX, offsetY, xAxis, yAxis, dragX, dragY } = list; const divBounds = element.getBoundingClientRect(); - divBounds.y += this.offsetHeightDraw; + divBounds.x += this.overflowLeftSize; + divBounds.y += this.overflowTopSize; let x = 0, y = 0; if (mode === 'inside') { @@ -318,8 +331,6 @@ export class SpineCanvasOverlay { const boundsY = (ay + ah / 2) * ratio; // get the center of the div in world coordinate - // const divX = divBounds.x + divBounds.width / 2 + window.scrollX; - // const divY = divBounds.y - 1 + divBounds.height / 2 + window.scrollY; const divX = divBounds.x + divBounds.width / 2; const divY = divBounds.y - 1 + divBounds.height / 2; this.screenToWorld(tempVector, divX, divY); @@ -332,19 +343,10 @@ export class SpineCanvasOverlay { skeleton.scaleX = ratio; skeleton.scaleY = ratio; } else { - - // TODO: window.devicePixelRatio to manage browser zoom - // get the center of the div in world coordinate const divX = divBounds.x + divBounds.width * xAxis; const divY = divBounds.y + divBounds.height * yAxis; this.screenToWorld(tempVector, divX, divY); - // console.log(tempVector.x, tempVector.y) - // console.log(element.getBoundingClientRect().y, this.canvas.clientWidth) - - // skeleton.scaleX /= window.devicePixelRatio; - // skeleton.scaleY /= window.devicePixelRatio; - // get vertices offset x = tempVector.x; @@ -408,7 +410,7 @@ export class SpineCanvasOverlay { this.input.addListener({ down: (x, y, ev) => { const originalEvent = ev instanceof MouseEvent ? ev : ev!.changedTouches[0]; - tempVectorInput.set(originalEvent.pageX - window.scrollX, originalEvent.pageY - window.scrollY + this.offsetHeightDraw, 0); + tempVectorInput.set(originalEvent.pageX - window.scrollX + this.overflowLeftSize, originalEvent.pageY - window.scrollY + this.overflowTopSize, 0); this.spineCanvas.renderer.camera.screenToWorld(tempVectorInput, this.canvas.clientWidth, this.canvas.clientHeight); this.skeletonList.forEach(({ htmlOptionsList, bounds, skeleton }) => { htmlOptionsList.forEach((element) => { @@ -434,7 +436,7 @@ export class SpineCanvasOverlay { }, dragged: (x, y, ev) => { const originalEvent = ev instanceof MouseEvent ? ev : ev!.changedTouches[0]; - tempVectorInput.set(originalEvent.pageX - window.scrollX, originalEvent.pageY - window.scrollY + this.offsetHeightDraw, 0); + tempVectorInput.set(originalEvent.pageX - window.scrollX + this.overflowLeftSize, originalEvent.pageY - window.scrollY + this.overflowTopSize, 0); this.spineCanvas.renderer.camera.screenToWorld(tempVectorInput, this.canvas.clientWidth, this.canvas.clientHeight); let dragX = tempVectorInput.x - prevX; let dragY = tempVectorInput.y - prevY; @@ -470,57 +472,46 @@ export class SpineCanvasOverlay { */ private updateCanvasSize() { - const displayWidth = document.documentElement.clientWidth; - const displayHeight = document.documentElement.clientHeight; - // this.canvas.style.left = displayWidth * .1 + "px"; - // this.canvas.style.width = displayWidth * .8 + "px"; - // this.canvas.style.left = displayWidth + "px"; - console.log(displayWidth) - this.canvas.style.width = displayWidth + "px"; - this.canvas.style.height = displayHeight + this.additionalPixelsBottom + "px"; + // resize canvas + this.resizeCanvas(); + + // recalculate overflow left and size since canvas size changed + // we could keep the initial values, avoid this and the translation below - even though we don't have a great gain + this.translateCanvas(); + + // temporarely remove the div to get the page size without considering the div + // this is necessary otherwise if the bigger element in the page is remove and the div + // was the second bigger element, now it would be the div to dtermine the page size + this.div.remove(); + const { width, height } = this.getPageSize(); + document.body.appendChild(this.div); + + this.div.style.width = width + "px"; + this.div.style.height = height + "px"; } private scrollHandler = () => { - const { width, height } = this.getPageSize(); + this.translateCanvas(); + } - const scrollPositionX = window.scrollX; - const canvasWidth = this.canvas.offsetWidth; - const maxTranslationX = width - canvasWidth; - // const translationX = Math.min(scrollPositionX, maxTranslationX); - const translationX = scrollPositionX; - console.log(width, canvasWidth, maxTranslationX, translationX) - // const translationX = Math.max(0, Math.min(scrollPositionX, maxTranslationX)); + private resizeCanvas() { + const displayWidth = document.documentElement.clientWidth; + const displayHeight = document.documentElement.clientHeight; + this.canvas.style.width = displayWidth * (1 + (this.overflowLeft + this.overflowRight)) + "px"; + this.canvas.style.height = displayHeight * (1 + (this.overflowTop + this.overflowBottom)) + "px"; + if (this.spineCanvas) this.spineCanvas.renderer.resize(ResizeMode.Expand); + } + private translateCanvas() { + const displayWidth = document.documentElement.clientWidth; + const displayHeight = document.documentElement.clientHeight; - // when the phone zooms in, it happen that it is possible to scroll horizontally - // this means that scrollPositionX will have a value > 0 - // however, at the beginning the webpage is fit horizontally. It means that | maxTranslationX = width - canvasWidth | will be always 0 - // it means that | translationX = Math.min(scrollPositionX, maxTranslationX) | will be always 0 - // however, if I simply set translationX = scrollPositionX, the canvas is translated on the right increasing the page size - // the solution is probably to shrink the canvas on scroll - // CHECK THEN WHAT HAPPENS WHEN HORIZONTAL SCROLLBAR IS AVAILABLE ON BROWSER + this.overflowLeftSize = this.overflowLeft * displayWidth; + this.overflowTopSize = this.overflowTop * displayHeight; - - - const scrollPositionY = window.scrollY; - const canvasHeight = this.canvas.offsetHeight; - const maxTranslation = height - canvasHeight + this.offsetHeight; - const translationY = Math.min(scrollPositionY, maxTranslation) - this.offsetHeight; - - const delta = scrollPositionY - maxTranslation - this.offsetHeightDraw = this.offsetHeight; - if (delta > 0) { - this.offsetHeightDraw += delta + 1; - } - - // translate should be faster - this.canvas.style.transform =`translate(${translationX}px,${translationY}px)`; - - // console.log(translationX, translationY) - - // TODO: some browser plugins prevent transform translate - we can think to enable a mode that move by top/left - // this.canvas.style.top = `${this.currentTranslateY}px`; - // this.canvas.style.left = `${this.currentTranslateX}px`; + const scrollPositionX = window.scrollX - this.overflowLeftSize; + const scrollPositionY = window.scrollY - this.overflowTopSize; + this.canvas.style.transform =`translate(${scrollPositionX}px,${scrollPositionY}px)`; } private zoomHandler = () => { @@ -539,23 +530,10 @@ export class SpineCanvasOverlay { } private getPageSize() { - const width = Math.max( - document.body.scrollWidth, - document.documentElement.scrollWidth, - document.body.offsetWidth, - document.documentElement.offsetWidth, - document.documentElement.clientWidth - ); - - const height = Math.max( - document.body.scrollHeight, - document.documentElement.scrollHeight, - document.body.offsetHeight, - document.documentElement.offsetHeight, - document.documentElement.clientHeight - ); - - return { width, height }; + // we need the bounding client rect otherwise decimals won't be returned + // this means that during zoom it might occurs that the div would be resized + // rounded 1px more making a scrollbar appear + return document.body.getBoundingClientRect(); } /*