Add OffScreenUpdateBehaviour

This commit is contained in:
Davide Tantillo 2024-08-29 09:00:55 +02:00
parent bc192f4178
commit 26d1ca739d
8 changed files with 2081 additions and 1163 deletions

View File

@ -1456,7 +1456,143 @@ skins.forEach((skin, i) => {
/////////////////////
-->
<!--
/////////////////////
// start section 8 //
/////////////////////
-->
<div id="section1" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
Widgets are not render while they are off screen.
<br>
<br>
The state and skeleton <code>update</code>, and the skeleton <code>apply</code> and the skeleton <code>updateWorldTransform</code> functions are not invoked when the widget is off screen.
<br>
<br>
If you want the update functions to be invoked in any case, set <code>offscreen=update</code>.
<br>
If you want all the functions to be invoked in any case, set <code>offscreen=pose</code>.
<br>
<br>
You can also overwrite the update function. Just assign a function to the <code>update</code> property of the widget.
In that it's your responsibility to skip the update/apply. You can use the <code>onScreen</code> property for convinience.
</div>
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
<spine-widget
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
</div>
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
<spine-widget
identifier="stretchyman"
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
</div>
<script>
const stretchyman = spine.getSpineWidget("stretchyman");
stretchyman.update = (canvas, delta, skeleton, state) => {
// skin logiv if widget is off screen
if (!stretchyman.onScreen) return;
delta = delta * 2;
state.update(delta);
skeleton.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform(spine.Physics.update);
};
</script>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
<spine-widget
identifier="stretchyman"
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
...
const stretchyman = spine.getSpineWidget("stretchyman");
stretchyman.update = (canvas, delta, skeleton, state) => {
// skin logiv if widget is off screen
if (!stretchyman.onScreen) return;
delta = delta * 2;
state.update(delta);
skeleton.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform(spine.Physics.update);
};`)
</script>
</code></pre>
</div>
</div>
<div id="section8" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
Widgets are not render while they are off screen.
<br>
The state and skeleton <code>update</code>, and the skeleton <code>apply</code> and the skeleton <code>updateWorldTransform</code> functions are not invoked when the widget is off screen.
<br>If you want the update functions to be invoked in any case, set <code>offscreen=update</code>.
<br>If you want all the functions to be invoked in any case, set <code>offscreen=pose</code>.
</div>
<div class="split-right" id="section8-element">
<spine-widget
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>escapeHTMLandInject(`
<spine-widget
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>`);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 8 //
/////////////////////
-->

View File

@ -160,32 +160,6 @@
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
</div>
@ -197,33 +171,7 @@
animation="emotes/wave"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="erikari"
pages="0,1,4,6"
offscreen="update"
></spine-widget>
</div>
</div>
@ -236,33 +184,7 @@
animation="emotes/wave"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="mario"
pages="0,1,4,6"
offscreen="pose"
></spine-widget>
</div>
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -464,7 +464,6 @@ export class SceneRenderer implements Disposable {
}
resize2 () {
console.log("RESIZE COMMAND")
let canvas = this.canvas;
this.context.gl.viewport(0, 0, canvas.width, canvas.height);
this.camera.setViewport(canvas.width, canvas.height);

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,15 @@ interface Rectangle {
type UpdateSpineFunction = (canvas: SpineCanvas, delta: number, skeleton: Skeleton, state: AnimationState) => void;
type OffScreenUpdateBehaviourType = "pause" | "update" | "pose";
function isOffScreenUpdateBehaviourType(value: string): value is OffScreenUpdateBehaviourType {
return (
value === "pause" ||
value === "update" ||
value === "pose"
);
}
type ModeType = 'inside' | 'origin';
function isModeType(value: string): value is ModeType {
return (
@ -115,6 +124,7 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
public loadingSpinner = true;
public manualStart = false;
public pages?: Array<number>;
public offScreenUpdateBehaviour: OffScreenUpdateBehaviourType = "pause";
// state
public skeleton?: Skeleton;
@ -137,7 +147,6 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
if (widget.loading && !widget.onScreenAtLeastOnce) {
widget.onScreenAtLeastOnce = true;
console.log(widget.manualStart)
if (widget.manualStart) {
widget.start();
}
@ -164,27 +173,28 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
public loadingScreen: LoadingScreenWidget | null = null;
static get observedAttributes(): string[] {
return [
"atlas",
"skeleton",
"scale",
"animation",
"skin",
"fit",
"width",
"height",
"draggable",
"mode",
"x-axis",
"y-axis",
"identifier",
"offset-x",
"offset-y",
"debug",
"manual-start",
"spinner",
"pages"
];
return [
"atlas",
"skeleton",
"scale",
"animation",
"skin",
"fit",
"width",
"height",
"draggable",
"mode",
"x-axis",
"y-axis",
"identifier",
"offset-x",
"offset-y",
"debug",
"manual-start",
"spinner",
"pages",
"offscreen"
];
}
constructor() {
@ -254,6 +264,10 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
this.mode = isModeType(newValue) ? newValue : "inside";
}
if (name === "offscreen") {
this.offScreenUpdateBehaviour = isOffScreenUpdateBehaviourType(newValue) ? newValue : "pause";
}
if (name === "x-axis") {
let float = this.xAxis;
float = parseFloat(newValue);
@ -579,7 +593,8 @@ class SpineWebComponentOverlay extends HTMLElement {
// how many pixels to add to the edges to prevent "edge cuttin" on fast scrolling
// be aware that the canvas is already big as the display size
private overflowTop = .0;
// making it bigger might reduce performance significantly
private overflowTop = .2;
private overflowBottom = .0;
private overflowLeft = .0;
private overflowRight = .0;
@ -621,6 +636,9 @@ class SpineWebComponentOverlay extends HTMLElement {
this.spineCanvas = new SpineCanvas(this.canvas, { app: this.setupSpineCanvasApp() });
this.updateCanvasSize();
this.zoomHandler();
this.translateCanvas();
this.overflowLeftSize = this.overflowLeft * document.documentElement.clientWidth;
this.overflowTopSize = this.overflowTop * document.documentElement.clientHeight;
@ -635,11 +653,10 @@ class SpineWebComponentOverlay extends HTMLElement {
const screen = window.screen;
screen.orientation.onchange = () => {
this.updateCanvasSize();
// after an orientation change the scrolling changes, but the scroll event does not fire
this.scrollHandler();
}
this.zoomHandler();
// scroll
window.addEventListener('scroll', this.scrollHandler);
this.scrollHandler();
@ -658,15 +675,19 @@ class SpineWebComponentOverlay extends HTMLElement {
const blue = new Color(0, 0, 1, 1);
return {
update: (canvas: SpineCanvas, delta: number) => {
this.skeletonList.forEach(({ skeleton, state, update }) => {
this.skeletonList.forEach(({ skeleton, state, update, onScreen, offScreenUpdateBehaviour, skeletonPath }) => {
if (!skeleton || !state) return;
if (!onScreen && offScreenUpdateBehaviour === "pause") return;
if (update) update(canvas, delta, skeleton, state)
else {
// delta = 0
state.update(delta);
state.apply(skeleton);
skeleton.update(delta);
skeleton.updateWorldTransform(Physics.update);
if (onScreen || (!onScreen && offScreenUpdateBehaviour === "pose") ) {
state.apply(skeleton);
skeleton.updateWorldTransform(Physics.update);
}
}
});
this.fps.innerText = canvas.time.framesPerSecond.toFixed(2) + " fps";
@ -913,19 +934,13 @@ class SpineWebComponentOverlay extends HTMLElement {
}
private resizeCanvas() {
console.log("START RESI:")
const screen = window.screen;
const angle = screen.orientation.angle;
const rotated = angle === 90 || angle === 270;
const width = rotated ? screen.height : screen.width;
const height = rotated ? screen.width : screen.height;
const { width, height } = this.getScreenSize();
if (this.currentCanvasBaseWidth !== width || this.currentCanvasBaseHeight !== height) {
this.currentCanvasBaseWidth = width;
this.currentCanvasBaseHeight = height;
this.overflowLeftSize = this.overflowLeft * width;
this.overflowTopSize = this.overflowTop * height;
console.log("FROM RESI: ", width, height)
const totalWidth = width * (1 + (this.overflowLeft + this.overflowRight));
const totalHeight = height * (1 + (this.overflowTop + this.overflowBottom));
@ -935,7 +950,6 @@ class SpineWebComponentOverlay extends HTMLElement {
const dpr = window.devicePixelRatio;
this.canvas.width = Math.round(totalWidth * dpr);
this.canvas.height = Math.round(totalHeight * dpr);
console.log("FROM RESI2: ", this.canvas.width, this.canvas.height)
this.spineCanvas.renderer.resize2();
}
@ -946,20 +960,12 @@ class SpineWebComponentOverlay extends HTMLElement {
}
private translateCanvas() {
const viewportWidth = document.documentElement.clientWidth;
const viewportHeight = document.documentElement.clientHeight;
// this.overflowLeftSize = this.overflowLeft * viewportWidth;
// this.overflowTopSize = this.overflowTop * viewportHeight;
const scrollPositionX = window.scrollX - this.overflowLeftSize;
const scrollPositionY = window.scrollY - this.overflowTopSize;
console.log("FROM TRAN: ", scrollPositionY, window.scrollY, this.overflowTopSize)
this.canvas.style.transform =`translate(${scrollPositionX}px,${scrollPositionY}px)`;
}
private zoomHandler = () => {
console.log("ZOOM")
this.skeletonList.forEach((widget) => {
// inside mode scale automatically to fit the skeleton within its parent
if (widget.mode !== 'origin' && widget.fit !== 'none') return;
@ -973,13 +979,25 @@ class SpineWebComponentOverlay extends HTMLElement {
})
}
// 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
private getPageSize() {
// 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();
}
// screen size remain the same when it is rotated
// we need to swap them based and the orientation angle
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 };
}
/*
* Other utilities
*/

View File

@ -14,7 +14,7 @@ export * from "./ShapeRenderer.js";
export * from "./SkeletonDebugRenderer.js";
export * from "./SkeletonRenderer.js";
export * from "./SpineCanvas.js";
export * from "./SpineWebComponent.js";
export * from "./SpineWebComponentWidget.js";
export * from "./Vector3.js";
export * from "./WebGL.js";
export * from "@esotericsoftware/spine-core";