mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-24 10:41:24 +08:00
WIP - Doc before refactor
This commit is contained in:
parent
b4f11c2f21
commit
d178f5de7c
274
spine-ts/spine-webgl/example/canvas10.html
Normal file
274
spine-ts/spine-webgl/example/canvas10.html
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script src="../dist/iife/spine-webgl.js"></script>
|
||||||
|
<!-- <script src="./spine-webgl.min.js"></script> -->
|
||||||
|
<title>JS Library Showcase</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
/* height: 100lvh; */
|
||||||
|
/* height: 800px; */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
background-color: #3498db;
|
||||||
|
}
|
||||||
|
.split {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.split-left, .split-right {
|
||||||
|
width: 50%;
|
||||||
|
min-height: 50%;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
border: 1px solid salmon;
|
||||||
|
}
|
||||||
|
.split-nosize {
|
||||||
|
border: 1px solid salmon;
|
||||||
|
}
|
||||||
|
.split-size {
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
.navigation {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
left: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.nav-btn {
|
||||||
|
display: block;
|
||||||
|
margin: 0px 5px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-split {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-page {
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-top {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-bottom {
|
||||||
|
width: 100%;
|
||||||
|
/* height: 600px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-bottom {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-bottom pre {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-bottom code {
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skin-grid {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.skin-grid-element {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
width: 150px;
|
||||||
|
aspect-ratio: 3 / 3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function escapeHTMLandInject(text) {
|
||||||
|
const escaped = text
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
document.currentScript.parentElement.innerHTML = escaped;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
/////////////////////
|
||||||
|
// start section 8 //
|
||||||
|
/////////////////////
|
||||||
|
-->
|
||||||
|
<div id="section1" class="section vertical-split">
|
||||||
|
|
||||||
|
<div class="split" style="width: 100%; flex-direction: column;">
|
||||||
|
|
||||||
|
<div class="split-top split">
|
||||||
|
<!-- <div class="split-left">
|
||||||
|
<spine-widget
|
||||||
|
atlas="assets/spineboy-pma.atlas"
|
||||||
|
skeleton="assets/spineboy-pro.skel"
|
||||||
|
animation="walk"
|
||||||
|
scale="3"
|
||||||
|
fit="none"
|
||||||
|
clip="true"
|
||||||
|
></spine-widget>
|
||||||
|
</div>
|
||||||
|
<div class="split-right">
|
||||||
|
<spine-widget
|
||||||
|
atlas="assets/spineboy-pma.atlas"
|
||||||
|
skeleton="assets/spineboy-pro.skel"
|
||||||
|
animation="walk"
|
||||||
|
scale="1.5"
|
||||||
|
fit="none"
|
||||||
|
clip="true"
|
||||||
|
></spine-widget>
|
||||||
|
</div>
|
||||||
|
<div class="split-right">
|
||||||
|
<spine-widget
|
||||||
|
atlas="assets/spineboy-pma.atlas"
|
||||||
|
skeleton="assets/spineboy-pro.skel"
|
||||||
|
animation="walk"
|
||||||
|
scale="1"
|
||||||
|
fit="none"
|
||||||
|
clip="true"
|
||||||
|
></spine-widget>
|
||||||
|
</div>
|
||||||
|
<div class="split-right">
|
||||||
|
<spine-widget
|
||||||
|
atlas="assets/cloud-pot-pma.atlas"
|
||||||
|
skeleton="assets/cloud-pot.skel"
|
||||||
|
animation="playing-in-the-rain"
|
||||||
|
scale="0.5"
|
||||||
|
fit="none"
|
||||||
|
clip="true"
|
||||||
|
></spine-widget>
|
||||||
|
</div> -->
|
||||||
|
<div class="split-right">
|
||||||
|
<spine-widget
|
||||||
|
identifier="celeste"
|
||||||
|
atlas="assets/celestial-circus-pma.atlas"
|
||||||
|
skeleton="assets/celestial-circus-pro.skel"
|
||||||
|
draggable="true"
|
||||||
|
animation="swing"
|
||||||
|
></spine-widget>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(async () => {
|
||||||
|
const celeste = spine.getSpineWidget("celeste");
|
||||||
|
await celeste.loadingPromise;
|
||||||
|
celeste.state.setAnimation(0, "swing", true);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="split-right">
|
||||||
|
<spine-widget
|
||||||
|
identifier="celeste"
|
||||||
|
atlas="assets/celestial-circus-pma.atlas"
|
||||||
|
skeleton="assets/celestial-circus-pro.skel"
|
||||||
|
animation="swing"
|
||||||
|
fit="contain"
|
||||||
|
debug="true"
|
||||||
|
clip="true"
|
||||||
|
draggable="true"
|
||||||
|
></spine-widget>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="split-bottom">
|
||||||
|
<pre><code id="code-display">
|
||||||
|
<script>
|
||||||
|
escapeHTMLandInject(`
|
||||||
|
...`);
|
||||||
|
</script>
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
/////////////////////
|
||||||
|
// end section 8 //
|
||||||
|
/////////////////////
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <div class="navigation">
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section1')">1</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section2')">2</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section3')">3</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section4')">4</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section5')">5</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section6')">6</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section7')">7</button>
|
||||||
|
<button class="nav-btn" onclick="scrollToSection('section8')">8</button>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// function scrollToSection(id) {
|
||||||
|
// document.getElementById(id).scrollIntoView({ behavior: 'smooth' });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let sections = document.querySelectorAll('.section');
|
||||||
|
// let currentSection = 0;
|
||||||
|
|
||||||
|
// window.addEventListener('wheel', (e) => {
|
||||||
|
// if (e.deltaY > 0 && currentSection < sections.length - 1) {
|
||||||
|
// currentSection++;
|
||||||
|
// } else if (e.deltaY < 0 && currentSection > 0) {
|
||||||
|
// currentSection--;
|
||||||
|
// }
|
||||||
|
// sections[currentSection].scrollIntoView({ behavior: 'smooth' });
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// Drag utility
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -223,7 +223,7 @@
|
|||||||
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Other fit modes are <code>fitWidth</code>, <code>fitWidth</code>, <code>fitHeight</code>, <code>cover</code>,and <code>scaleDown</code>.
|
Other fit modes are <code>width</code>, <code>width</code>, <code>height</code>, <code>cover</code>,and <code>scaleDown</code>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -151,7 +151,7 @@
|
|||||||
animation="walk"
|
animation="walk"
|
||||||
scale="3"
|
scale="3"
|
||||||
clip="true"
|
clip="true"
|
||||||
fit="fitHeight"
|
fit="height"
|
||||||
></spine-widget>
|
></spine-widget>
|
||||||
</div>
|
</div>
|
||||||
<div class="split-right">
|
<div class="split-right">
|
||||||
|
|||||||
@ -223,7 +223,7 @@
|
|||||||
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Other fit modes are <code>fitWidth</code>, <code>fitWidth</code>, <code>fitHeight</code>, <code>cover</code>,and <code>scaleDown</code>.
|
Other fit modes are <code>width</code>, <code>width</code>, <code>height</code>, <code>cover</code>,and <code>scaleDown</code>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -220,7 +220,7 @@
|
|||||||
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Other fit modes are <code>fitWidth</code>, <code>fitWidth</code>, <code>fitHeight</code>, <code>cover</code>,and <code>scaleDown</code>.
|
Other fit modes are <code>width</code>, <code>width</code>, <code>height</code>, <code>cover</code>,and <code>scaleDown</code>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -59,12 +59,12 @@ function isModeType(value: string): value is ModeType {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type FitType = "fill" | "fitWidth" | "fitHeight" | "contain" | "cover" | "none" | "scaleDown";
|
type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown";
|
||||||
function isFitType(value: string): value is FitType {
|
function isFitType(value: string): value is FitType {
|
||||||
return (
|
return (
|
||||||
value === "fill" ||
|
value === "fill" ||
|
||||||
value === "fitWidth" ||
|
value === "width" ||
|
||||||
value === "fitHeight" ||
|
value === "height" ||
|
||||||
value === "contain" ||
|
value === "contain" ||
|
||||||
value === "cover" ||
|
value === "cover" ||
|
||||||
value === "none" ||
|
value === "none" ||
|
||||||
@ -94,8 +94,6 @@ interface WidgetPublicState {
|
|||||||
|
|
||||||
interface WidgetInternalState {
|
interface WidgetInternalState {
|
||||||
currentScaleDpi: number
|
currentScaleDpi: number
|
||||||
worldOffsetX: number
|
|
||||||
worldOffsetY: number
|
|
||||||
dragging: boolean
|
dragging: boolean
|
||||||
dragX: number
|
dragX: number
|
||||||
dragY: number
|
dragY: number
|
||||||
@ -105,46 +103,255 @@ interface WidgetInternalState {
|
|||||||
|
|
||||||
class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions, WidgetInternalState, Partial<WidgetPublicState> {
|
class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions, WidgetInternalState, Partial<WidgetPublicState> {
|
||||||
|
|
||||||
// skeleton options
|
/**
|
||||||
|
* The URL of the skeleton atlas file (.atlas)
|
||||||
|
*/
|
||||||
public atlasPath: string;
|
public atlasPath: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL of the skeleton JSON (.json) or binary (.skel) file
|
||||||
|
*/
|
||||||
public skeletonPath: string;
|
public skeletonPath: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scale when loading the skeleton data. Default: 1
|
||||||
|
*/
|
||||||
public scale = 1;
|
public scale = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional: The name of the animation to be played
|
||||||
|
*/
|
||||||
public animation?: string;
|
public animation?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional: The name of the skin to be set
|
||||||
|
*/
|
||||||
public skin?: string;
|
public skin?: string;
|
||||||
skeletonData?: SkeletonData; // TODO
|
|
||||||
|
/**
|
||||||
|
* Optional: Pass a `SkeletonData`, if you want to avoid creating a new one
|
||||||
|
*/
|
||||||
|
public skeletonData?: SkeletonData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the default state and skeleton update logic for this widget.
|
||||||
|
* @param delta - The milliseconds elapsed since the last update.
|
||||||
|
* @param skeleton - A reference to the widget's skeleton
|
||||||
|
* @param state - A reference to the widget's state
|
||||||
|
*/
|
||||||
update?: UpdateSpineWidgetFunction;
|
update?: UpdateSpineWidgetFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked before the world transforms are computed allows to execute additional logic.
|
||||||
|
*/
|
||||||
beforeUpdateWorldTransforms: BeforeAfterUpdateSpineWidgetFunction = () => {};
|
beforeUpdateWorldTransforms: BeforeAfterUpdateSpineWidgetFunction = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback is invoked after the world transforms are computed allows to execute additional logic.
|
||||||
|
*/
|
||||||
afterUpdateWorldTransforms: BeforeAfterUpdateSpineWidgetFunction= () => {};
|
afterUpdateWorldTransforms: BeforeAfterUpdateSpineWidgetFunction= () => {};
|
||||||
|
|
||||||
// layout options
|
/**
|
||||||
|
* Specify the way the skeleton is sized within the element automatically changing its `scaleX` and `scaleY`.
|
||||||
|
* It works only with {@link mode} `inside`. Possible values are:
|
||||||
|
* - `contain`: as large as possible while still containing the skeleton entirely within the element container (Default).
|
||||||
|
* - `fill`: fill the element container by distorting the skeleton's aspect ratio.
|
||||||
|
* - `width`: make sure the full width of the source is shown, regardless of whether this means the skeleton overflows the element container vertically.
|
||||||
|
* - `height`: make sure the full height of the source is shown, regardless of whether this means the skeleton overflows the element container horizontally.
|
||||||
|
* - `cover`: as small as possible while still covering the entire element container.
|
||||||
|
* - `scaleDown`: scale the skeleton down to ensure that the skeleton fits within the element container.
|
||||||
|
* - `none`: display the skeleton without autoscaling it.
|
||||||
|
*/
|
||||||
public fit: FitType = "contain";
|
public fit: FitType = "contain";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the way the skeleton is centered within the div:
|
||||||
|
* - `inside`: the skeleton bounds center is centered with the div container (Default)
|
||||||
|
* - `origin`: the skeleton origin is centered with the div container regardless of the bounds.
|
||||||
|
* Origin does not allow to specify any {@link fit} type and guarantee the skeleton to not be autoscaled.
|
||||||
|
*/
|
||||||
public mode: ModeType = "inside";
|
public mode: ModeType = "inside";
|
||||||
public offsetX = 0;
|
|
||||||
public offsetY = 0;
|
/**
|
||||||
|
* The x offset of the skeleton world origin x axis in div width units
|
||||||
|
*/
|
||||||
public xAxis = 0;
|
public xAxis = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y offset of the skeleton world origin x axis in div width units
|
||||||
|
*/
|
||||||
public yAxis = 0;
|
public yAxis = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x offset of the root in pixels wrt to the skeleton world origin
|
||||||
|
*/
|
||||||
|
public offsetX = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y offset of the root in pixels wrt to the skeleton world origin
|
||||||
|
*/
|
||||||
|
public offsetY = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a fixed width for the widget. If at least one of `width` and `height` is > 0,
|
||||||
|
* the widget will have an actual size and the div reference is the widget itself, not the div parent.
|
||||||
|
*/
|
||||||
public width = -1;
|
public width = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a fixed height for the widget. If at least one of `width` and `height` is > 0,
|
||||||
|
* the widget will have an actual size and the div reference is the widget itself, not the div parent.
|
||||||
|
*/
|
||||||
public height = -1;
|
public height = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the widget is draggable
|
||||||
|
*/
|
||||||
public draggable = false;
|
public draggable = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, some convenience elements are drawn to show the skeleton world origin (green),
|
||||||
|
* the root (red), and the bounds rectangle (blue)
|
||||||
|
*/
|
||||||
public debug = false;
|
public debug = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier to obtain a reference to this widget using the getSpineWidget function
|
||||||
|
*/
|
||||||
public identifier = "";
|
public identifier = "";
|
||||||
public loadingSpinner = true;
|
|
||||||
|
/**
|
||||||
|
* If true, assets loading are loaded immediately and the skeleton shown as soon as the assets are loaded
|
||||||
|
* If false, it is necessary to invoke the start method to start the loading process
|
||||||
|
*/
|
||||||
public manualStart = false;
|
public manualStart = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of indexes indicating the atlas pages indexes to be loaded.
|
||||||
|
* If undefined, all pages are loaded. If empty (default), no page is loaded;
|
||||||
|
* in this case the user can add later the indexes of the pages they want to load
|
||||||
|
* and call the loadTexturesInPagesAttribute, to lazily load them.
|
||||||
|
*/
|
||||||
public pages?: Array<number>;
|
public pages?: Array<number>;
|
||||||
public offScreenUpdateBehaviour: OffScreenUpdateBehaviourType = "pause";
|
|
||||||
|
/**
|
||||||
|
* If `true`, the skeleton is clipped to the container div bounds.
|
||||||
|
* Be careful on using this feature because it breaks batching!
|
||||||
|
*/
|
||||||
public clip = false;
|
public clip = false;
|
||||||
|
|
||||||
// state
|
/**
|
||||||
public skeleton?: Skeleton;
|
* The widget update/apply behaviour when the skeleton div container is offscreen:
|
||||||
public state?: AnimationState;
|
* - `pause`: the state is not updated, neither applied (Default)
|
||||||
public bounds?: Rectangle;
|
* - `update`: the state is updated, but not applied
|
||||||
public loadingPromise?: Promise<WidgetPublicState>;
|
* - `pose`: the state is updated and applied
|
||||||
public loading = true;
|
*/
|
||||||
public started = false;
|
public offScreenUpdateBehaviour: OffScreenUpdateBehaviourType = "pause";
|
||||||
public onScreenAtLeastOnce = false;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The skeleton hosted by this widget. It's ready once assets are loaded.
|
||||||
|
* Safely acces this property by using {@link loadingPromise}.
|
||||||
|
*/
|
||||||
|
public skeleton?: Skeleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The animation state hosted by this widget. It's ready once assets are loaded.
|
||||||
|
* Safely acces this property by using {@link loadingPromise}.
|
||||||
|
*/
|
||||||
|
public state?: AnimationState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The textureAtlas used by this widget to reference attachments. It's ready once assets are loaded.
|
||||||
|
* Safely acces this property by using {@link loadingPromise}.
|
||||||
|
*/
|
||||||
|
public textureAtlas?: TextureAtlas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rectangle representing the bounds used to fit the skeleton within the element container.
|
||||||
|
* The rectangle coordinates and size are expressed in the Spine world space, not the screen space.
|
||||||
|
* It is automatically calculated using the `skin` and `animation` provided by the user during loading.
|
||||||
|
* If no skin is provided, it is used the default skin.
|
||||||
|
* If no animation is provided, it is used the setup pose.
|
||||||
|
* Once loaded, the bounds are not automatically recalculated, but {@link recalculateBounds} need to be invoked.
|
||||||
|
* Use `setBounds` to set you desired bounds. Bounding Box might be useful to determine the bounds to be used.
|
||||||
|
*/
|
||||||
|
public bounds?: Rectangle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Promise that resolve to the widget itself once assets loading is terminated.
|
||||||
|
* Useful to safely access {@link skeleton} and {@link state} after a new widget has been just created.
|
||||||
|
*/
|
||||||
|
public loadingPromise?: Promise<this>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the widget is in the assets loading process.
|
||||||
|
*/
|
||||||
|
public loading = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the a Spine loading spinner is shown during asset loading
|
||||||
|
*/
|
||||||
|
public loadingSpinner = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the {@link LoadingScreenWidget} of this widget.
|
||||||
|
* This is instantiated only if it is really necessary.
|
||||||
|
* For example, if {@link loadingSpinner} is `false`, this property value is null
|
||||||
|
*/
|
||||||
|
public loadingScreen: LoadingScreenWidget | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the widget is in the assets loading process.
|
||||||
|
*/
|
||||||
|
public started = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the dpi (devicePixelRatio) currently used to calculate the scale for this skeleton
|
||||||
|
*/
|
||||||
|
public currentScaleDpi = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accumulated offset on the x axis due to dragging
|
||||||
|
*/
|
||||||
|
public dragX = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accumulated offset on the y axis due to dragging
|
||||||
|
*/
|
||||||
|
public dragY = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the widget is currently being dragged
|
||||||
|
*/
|
||||||
|
public dragging = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rectangle in the screen space used to determine if a click is within the skeleton bounds,
|
||||||
|
* so if to start the drag action.
|
||||||
|
*/
|
||||||
|
public dragBoundsRectangle: Rectangle = { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An HTMLDivElement used to show the drag surface in debug mode
|
||||||
|
*/
|
||||||
public debugDragDiv: HTMLDivElement;
|
public debugDragDiv: HTMLDivElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True, when the div hosting the widget enters the screen viewport. It uses an IntersectionObserver internally.
|
||||||
|
*/
|
||||||
|
public onScreen = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True, when the div hosting the widget enters the screen viewport at least once.
|
||||||
|
* It uses an IntersectionObserver internally.
|
||||||
|
*/
|
||||||
|
public onScreenAtLeastOnce = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback invoked each time div hosting the widget enters the screen viewport.
|
||||||
|
* By default, the callback call the {@link start} method the first time the widget
|
||||||
|
* enters the screen viewport.
|
||||||
|
*/
|
||||||
public onScreenFunction: (widget: SpineWebComponentWidget) => void = async (widget) => {
|
public onScreenFunction: (widget: SpineWebComponentWidget) => void = async (widget) => {
|
||||||
if (widget.loading && !widget.onScreenAtLeastOnce) {
|
if (widget.loading && !widget.onScreenAtLeastOnce) {
|
||||||
widget.onScreenAtLeastOnce = true;
|
widget.onScreenAtLeastOnce = true;
|
||||||
@ -155,26 +362,10 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: makes the interface exposes getter, make getter and make these private
|
|
||||||
// internal state
|
|
||||||
public currentScaleDpi = 1;
|
|
||||||
public worldOffsetX = 0;
|
|
||||||
public worldOffsetY = 0;
|
|
||||||
public dragX = 0;
|
|
||||||
public dragY = 0;
|
|
||||||
public dragging = false;
|
|
||||||
public intersectionObserver? : IntersectionObserver;
|
|
||||||
public onScreen = false;
|
|
||||||
public textureAtlas?: TextureAtlas;
|
|
||||||
|
|
||||||
private root: ShadowRoot;
|
private root: ShadowRoot;
|
||||||
private overlay: SpineWebComponentOverlay;
|
private overlay: SpineWebComponentOverlay;
|
||||||
|
|
||||||
private divLoader: HTMLDivElement;
|
|
||||||
|
|
||||||
public loadingScreen: LoadingScreenWidget | null = null;
|
|
||||||
public dragBoundsRectangle: Rectangle = { x: 0, y: 0, width: 0, height: 0 };
|
|
||||||
|
|
||||||
static get observedAttributes(): string[] {
|
static get observedAttributes(): string[] {
|
||||||
return [
|
return [
|
||||||
"atlas",
|
"atlas",
|
||||||
@ -208,13 +399,6 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
|
|||||||
this.atlasPath = "TODO";
|
this.atlasPath = "TODO";
|
||||||
this.skeletonPath = "TODO";
|
this.skeletonPath = "TODO";
|
||||||
|
|
||||||
this.divLoader = document.createElement("div");
|
|
||||||
this.divLoader.classList.add("container-loader");
|
|
||||||
|
|
||||||
const loader = document.createElement("div");
|
|
||||||
loader.classList.add("loader");
|
|
||||||
this.divLoader.appendChild(loader);
|
|
||||||
|
|
||||||
this.debugDragDiv = document.createElement('div');
|
this.debugDragDiv = document.createElement('div');
|
||||||
this.debugDragDiv.style.position = "absolute";
|
this.debugDragDiv.style.position = "absolute";
|
||||||
this.debugDragDiv.style.backgroundColor = "rgba(0, 1, 1, 0.3)";
|
this.debugDragDiv.style.backgroundColor = "rgba(0, 1, 1, 0.3)";
|
||||||
@ -383,8 +567,9 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
// if you want to start again the widget, first reset it
|
if (this.started) {
|
||||||
if (this.started) return;
|
console.warn("If you want to start again the widget, first reset it");
|
||||||
|
}
|
||||||
this.started = true;
|
this.started = true;
|
||||||
|
|
||||||
this.loadingPromise = this.loadSkeleton();
|
this.loadingPromise = this.loadSkeleton();
|
||||||
@ -461,7 +646,7 @@ class SpineWebComponentWidget extends HTMLElement implements WidgetLayoutOptions
|
|||||||
|
|
||||||
const bounds = this.calculateAnimationViewport(animationData);
|
const bounds = this.calculateAnimationViewport(animationData);
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
return { skeleton, state, bounds };
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHTMLElementReference(): HTMLElement {
|
public getHTMLElementReference(): HTMLElement {
|
||||||
@ -823,10 +1008,10 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
if (fit === "fill") { // Fill the target box by distorting the source's aspect ratio.
|
if (fit === "fill") { // Fill the target box by distorting the source's aspect ratio.
|
||||||
ratioW = scaleWidth;
|
ratioW = scaleWidth;
|
||||||
ratioH = scaleHeight;
|
ratioH = scaleHeight;
|
||||||
} else if (fit === "fitWidth") {
|
} else if (fit === "width") {
|
||||||
ratioW = scaleWidth;
|
ratioW = scaleWidth;
|
||||||
ratioH = scaleWidth;
|
ratioH = scaleWidth;
|
||||||
} else if (fit === "fitHeight") {
|
} else if (fit === "height") {
|
||||||
ratioW = scaleHeight;
|
ratioW = scaleHeight;
|
||||||
ratioH = scaleHeight;
|
ratioH = scaleHeight;
|
||||||
} else if (fit === "contain") {
|
} else if (fit === "contain") {
|
||||||
@ -874,20 +1059,20 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.worldOffsetX = divOriginX + offsetX + dragX;
|
const worldOffsetX = divOriginX + offsetX + dragX;
|
||||||
widget.worldOffsetY = divOriginY + offsetY + dragY;
|
const worldOffsetY = divOriginY + offsetY + dragY;
|
||||||
|
|
||||||
renderer.drawSkeleton(skeleton, true, -1, -1, (vertices, size, vertexSize) => {
|
renderer.drawSkeleton(skeleton, true, -1, -1, (vertices, size, vertexSize) => {
|
||||||
for (let i = 0; i < size; i+=vertexSize) {
|
for (let i = 0; i < size; i+=vertexSize) {
|
||||||
vertices[i] = vertices[i] + widget.worldOffsetX;
|
vertices[i] = vertices[i] + worldOffsetX;
|
||||||
vertices[i+1] = vertices[i+1] + widget.worldOffsetY;
|
vertices[i+1] = vertices[i+1] + worldOffsetY;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// store the draggable surface to make darg logic easier
|
// store the draggable surface to make darg logic easier
|
||||||
if (draggable) {
|
if (draggable) {
|
||||||
let { x: ax, y: ay, width: aw, height: ah } = bounds!;
|
let { x: ax, y: ay, width: aw, height: ah } = bounds!;
|
||||||
this.worldToScreen(tempVector, ax * skeleton.scaleX + widget.worldOffsetX, ay * skeleton.scaleY + widget.worldOffsetY);
|
this.worldToScreen(tempVector, ax * skeleton.scaleX + worldOffsetX, ay * skeleton.scaleY + worldOffsetY);
|
||||||
widget.dragBoundsRectangle.x = tempVector.x + window.scrollX;
|
widget.dragBoundsRectangle.x = tempVector.x + window.scrollX;
|
||||||
widget.dragBoundsRectangle.y = tempVector.y - ah * skeleton.scaleY / window.devicePixelRatio + window.scrollY;
|
widget.dragBoundsRectangle.y = tempVector.y - ah * skeleton.scaleY / window.devicePixelRatio + window.scrollY;
|
||||||
widget.dragBoundsRectangle.width = aw * skeleton.scaleX / window.devicePixelRatio;
|
widget.dragBoundsRectangle.width = aw * skeleton.scaleX / window.devicePixelRatio;
|
||||||
@ -906,22 +1091,22 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
|
|
||||||
// show bounds and its center
|
// show bounds and its center
|
||||||
renderer.rect(false,
|
renderer.rect(false,
|
||||||
ax * skeleton.scaleX + widget.worldOffsetX,
|
ax * skeleton.scaleX + worldOffsetX,
|
||||||
ay * skeleton.scaleY + widget.worldOffsetY,
|
ay * skeleton.scaleY + worldOffsetY,
|
||||||
aw * skeleton.scaleX,
|
aw * skeleton.scaleX,
|
||||||
ah * skeleton.scaleY,
|
ah * skeleton.scaleY,
|
||||||
blue);
|
blue);
|
||||||
const bbCenterX = (ax + aw / 2) * skeleton.scaleX + widget.worldOffsetX;
|
const bbCenterX = (ax + aw / 2) * skeleton.scaleX + worldOffsetX;
|
||||||
const bbCenterY = (ay + ah / 2) * skeleton.scaleY + widget.worldOffsetY;
|
const bbCenterY = (ay + ah / 2) * skeleton.scaleY + worldOffsetY;
|
||||||
renderer.circle(true, bbCenterX, bbCenterY, 10, blue);
|
renderer.circle(true, bbCenterX, bbCenterY, 10, blue);
|
||||||
|
|
||||||
// show skeleton root
|
// show skeleton root
|
||||||
const root = skeleton.getRootBone()!;
|
const root = skeleton.getRootBone()!;
|
||||||
renderer.circle(true, root.x + widget.worldOffsetX, root.y + widget.worldOffsetY, 10, red);
|
renderer.circle(true, root.x + worldOffsetX, root.y + worldOffsetY, 10, red);
|
||||||
|
|
||||||
// show shifted origin
|
// show shifted origin
|
||||||
const originX = widget.worldOffsetX - dragX - offsetX;
|
const originX = worldOffsetX - dragX - offsetX;
|
||||||
const originY = widget.worldOffsetY - dragY - offsetY;
|
const originY = worldOffsetY - dragY - offsetY;
|
||||||
renderer.circle(true, originX, originY, 10, green);
|
renderer.circle(true, originX, originY, 10, green);
|
||||||
|
|
||||||
// show line from origin to bounds center
|
// show line from origin to bounds center
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user