mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
1783 lines
57 KiB
HTML
1783 lines
57 KiB
HTML
<!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;
|
|
}
|
|
html {
|
|
/* scroll-behavior: smooth; */
|
|
}
|
|
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 1 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
The <code><spine-widget></code> tag allows you to place your Spine animations into a web page.
|
|
<br>
|
|
<br>
|
|
By default, the animation bounds are calculated using the given animation, or the setup pose if no animation is provided.
|
|
<br>
|
|
The bounds is centered and scaled to fit the parent container.
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 1 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 2 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="full-width">
|
|
|
|
<div class="split">
|
|
<div class="split-left" style="height: 300px;">
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
fit="fill"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
You can change the fit mode of your Spine animation using the <code>fit</code> attribute.
|
|
<br>
|
|
<br>
|
|
This is <code>fit="fill"</code>. Default fit value is <code>fit="contain"</code>.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split">
|
|
<div class="split-left" style="height: 300px;">
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
scale=".125"
|
|
fit="none"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
If you want to preserve the original scale, you can use the <code>fit="none"</code>.
|
|
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
|
<br>
|
|
<br>
|
|
Other fit modes are <code>width</code>, <code>width</code>, <code>height</code>, <code>cover</code>,and <code>scaleDown</code>.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
fit="fill"
|
|
></spine-widget>
|
|
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
scale=".125"
|
|
fit="none"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 2 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 1 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
height="200"
|
|
width="200"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
If you want to manually size the Spine widget, specify the attributes <code>width</code> and <code>height</code> in pixels (without the px unit).
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
height="200"
|
|
width="200"
|
|
fit="fill"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 1 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section2" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
Mode <code>origin</code> center the animation world origin with the center of the HTML element.
|
|
<br>
|
|
You are responsible to scale the skeleton using this mode.
|
|
<br>
|
|
<br>
|
|
Move the origin by a percentage of the div width and height by using <code>x-axis</code> and <code>y-axis</code> respectively.
|
|
</div>
|
|
<div class="split-right">
|
|
<spine-widget
|
|
atlas="assets/vine-pma.atlas"
|
|
skeleton="assets/vine-pro.skel"
|
|
animation="grow"
|
|
mode="origin"
|
|
scale=".5"
|
|
y-axis="-.5"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/vine-pma.atlas"
|
|
skeleton="assets/vine-pro.skel"
|
|
animation="grow"
|
|
mode="origin"
|
|
scale=".5"
|
|
y-axis="-.5"
|
|
></spine-widget>
|
|
`);</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section3" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
atlas="assets/snowglobe-pma.atlas"
|
|
skeleton="assets/snowglobe-pro.skel"
|
|
animation="shake"
|
|
offset-x="100"
|
|
offset-y="-100"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
Use <code>offset-x</code> and <code>offset-y</code> to move you skeleton left or right by the pixel amount you specify.
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/snowglobe-pma.atlas"
|
|
skeleton="assets/snowglobe-pro.skel"
|
|
animation="shake"
|
|
offset-x="100"
|
|
offset-y="-100"
|
|
></spine-widget>
|
|
`);</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 4 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section4" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
Give an <code>identifier</code> to your widget to get it by using the <code>spine.getSpineWidget</code> function.
|
|
You can easily access the <code>Skeleton</code> and the <code>AnimationState</code> of your character, and use them as if you were using <code>spine-webgl</code>.
|
|
<br>
|
|
<br>
|
|
If you change animation, you can ask the widget to scale the skeleton based on the new animation.
|
|
</div>
|
|
<div class="split-right">
|
|
<spine-widget
|
|
identifier="raptor"
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(async () => {
|
|
const widget = spine.getSpineWidget("raptor");
|
|
const { state } = await widget.loadingPromise;
|
|
let isRoaring = false;
|
|
setInterval(() => {
|
|
const newAnimation = isRoaring ? "walk" : "roar";
|
|
state.setAnimation(0, newAnimation, true);
|
|
widget.recalculateBounds(); // scale the skeleton based on the new animation
|
|
isRoaring = !isRoaring;
|
|
}, 4000);
|
|
})();
|
|
</script>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
// access the spine widget
|
|
<spine-widget
|
|
identifier="raptor"
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
|
|
...
|
|
|
|
// using js, access the skeleton and the state asynchronously
|
|
(async () => {
|
|
const widget = spine.getSpineWidget("raptor");
|
|
const { state } = await widget.loadingPromise;
|
|
let isRoaring = false;
|
|
setInterval(() => {
|
|
const newAnimation = isRoaring ? "walk" : "roar";
|
|
state.setAnimation(0, newAnimation, true);
|
|
widget.recalculateBounds(); // scale the skeleton based on the new animation
|
|
isRoaring = !isRoaring;
|
|
}, 4000);
|
|
})();
|
|
|
|
`);</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 4 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 6 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section6" class="section vertical-split">
|
|
<div class="split high-page" style="flex-direction: column;">
|
|
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
|
|
Moving the div will move the skeleton origin. <br>
|
|
Resizing the div will resize the skeleton in <code>inside</code> mode, but not in <code>origin</code> mode.
|
|
</div>
|
|
<div class="split-nosize full-width" style="width: 80%; height: 50%; margin: 1em; padding: 1em;" id="section6-element">
|
|
<spine-widget
|
|
atlas="assets/cloud-pot-pma.atlas"
|
|
skeleton="assets/cloud-pot.skel"
|
|
animation="playing-in-the-rain"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/cloud-pot-pma.atlas"
|
|
skeleton="assets/cloud-pot.skel"
|
|
animation="playing-in-the-rain"
|
|
></spine-widget>`
|
|
);</script>
|
|
</code></pre>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 6 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section7" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
You can view the skeleton world origin (green), the root bone position (red), and the bounds rectangle and center (blue) by setting <code>debug</code> to <code>true</code>.
|
|
</div>
|
|
<div class="split-right" id="section7-element">
|
|
<spine-widget
|
|
atlas="assets/sack-pma.atlas"
|
|
skeleton="assets/sack-pro.skel"
|
|
animation="cape-follow-example"
|
|
debug="true"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/sack-pma.atlas"
|
|
skeleton="assets/sack-pro.skel"
|
|
animation="cape-follow-example"
|
|
debug="true"
|
|
></spine-widget>`
|
|
);</script>
|
|
</code></pre>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
identifier="widget-loading"
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
spinner="false"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
A loading spinner is shown during assets loading. Click the button below to simulate a 2 seconds loading:
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Simulate reload" onclick="reloadWidget(this)">
|
|
<br>
|
|
<br>
|
|
If you do not want to show the loading spinner, set <code>spinner="false"</code>.
|
|
<br>
|
|
Click the button below to toggle the spinner.
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Spinner ON" onclick="toggleSpinner(this)">
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const widget = spine.getSpineWidget("widget-loading");
|
|
async function reloadWidget(element) {
|
|
element.disabled = true;
|
|
await widget.loadingPromise;
|
|
const skeleton = widget.skeleton;
|
|
widget.skeleton = null;
|
|
setTimeout(() => {
|
|
element.disabled = false;
|
|
widget.skeleton = skeleton;
|
|
}, 1000)
|
|
}
|
|
function toggleSpinner(element) {
|
|
widget.loadingSpinner = !widget.loadingSpinner;
|
|
element.value = widget.loadingSpinner ? "Spinner ON" : "Spinner OFF";
|
|
}
|
|
</script>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
spinner="true"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
<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;">
|
|
It's super easy to show your different skins and animations. Just make a table and use the <code>skin</code> and <code>animation</code> attributes.
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="idle"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dance"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dress-up"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="walk"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="idle"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dance"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dress-up"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="walk"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="idle"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dance"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dress-up"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="walk"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="idle"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dance"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dress-up"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="walk"
|
|
skin="full-skins/girl-spring-dress"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="idle"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dance"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dress-up"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="walk"
|
|
skin="full-skins/girl-blue-cape"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="idle"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dance"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="dress-up"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/mix-and-match-pma.atlas"
|
|
skeleton="assets/mix-and-match-pro.skel"
|
|
animation="walk"
|
|
skin="full-skins/girl"
|
|
></spine-widget>
|
|
</div>
|
|
</div>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
<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;">
|
|
If you have many atlas pages, for example one for each skin, and you want to show only some of the skins,
|
|
pass to the <code>pages</code> the atlas pages you want to load as a comma concatenated list of indices.
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/chibi-stickers-pma.atlas"
|
|
skeleton="assets/chibi-stickers.skel"
|
|
animation="emotes/wave"
|
|
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="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>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/chibi-stickers-pma.atlas"
|
|
skeleton="assets/chibi-stickers.skel"
|
|
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"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/chibi-stickers-pma.atlas"
|
|
skeleton="assets/chibi-stickers.skel"
|
|
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"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/chibi-stickers-pma.atlas"
|
|
skeleton="assets/chibi-stickers.skel"
|
|
animation="emotes/wave"
|
|
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="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>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/chibi-stickers-pma.atlas"
|
|
skeleton="assets/chibi-stickers.skel"
|
|
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"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/chibi-stickers-pma.atlas"
|
|
skeleton="assets/chibi-stickers.skel"
|
|
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"
|
|
></spine-widget>
|
|
</div>
|
|
</div>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
<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;">
|
|
Let's do the same thing above, but programmatically!
|
|
Create two arrays, one for the skin and the other for the animations, and loop over them.
|
|
<br>
|
|
<br>
|
|
<code>spine.createSpineWidget</code> allows you to create a spine widget.
|
|
<br>
|
|
<br>
|
|
By default, assets are loaded immeaditely. You can postpone that by setting <code>manual-start="false"</code>.
|
|
Then it's your responsibility to call <code>start()</code> on the widget.
|
|
As usual, just wait on the <code>loadingPromise</code> to act on the skeleton or state.
|
|
</div>
|
|
|
|
<script>
|
|
const element = document.currentScript.parentElement;
|
|
const skins = ["soeren", "sinisa", "luke"];
|
|
const animations = ["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"];
|
|
const pages = [3, 7, 8];
|
|
skins.forEach((skin, i) => {
|
|
// create the table (one for each skin)
|
|
const grid = document.createElement("div");
|
|
grid.classList.add("skin-grid");
|
|
element.appendChild(grid);
|
|
|
|
animations.forEach(async (animation, j) => {
|
|
// craete the div where to place the widget (one for each animation)
|
|
const gridElement = document.createElement("div");
|
|
gridElement.classList.add("skin-grid-element");
|
|
grid.appendChild(gridElement);
|
|
|
|
// create the widget
|
|
const widgetSection = spine.createSpineWidget({
|
|
atlas: "assets/chibi-stickers-pma.atlas",
|
|
skeleton: "assets/chibi-stickers.json",
|
|
animation,
|
|
skin,
|
|
pages,
|
|
manualStart: true,
|
|
});
|
|
|
|
// append the widget to the grid element
|
|
gridElement.appendChild(widgetSection);
|
|
|
|
// manually start the widget
|
|
widgetSection.start();
|
|
|
|
// access the state of the first widget and change the animation
|
|
if (i === 0 && j === 0) {
|
|
await widgetSection.loadingPromise;
|
|
widgetSection.state.setAnimation(0, "emotes/angry", true);
|
|
}
|
|
|
|
})
|
|
})
|
|
</script>
|
|
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
const element = document.currentScript.parentElement;
|
|
const skins = ["soeren", "sinisa", "luke"];
|
|
const animations = ["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"];
|
|
const pages = [3, 7, 8];
|
|
skins.forEach((skin, i) => {
|
|
// create the table (one for each skin)
|
|
const grid = document.createElement("div");
|
|
grid.classList.add("skin-grid");
|
|
element.appendChild(grid);
|
|
|
|
animations.forEach(async (animation, j) => {
|
|
// craete the div where to place the widget (one for each animation)
|
|
const gridElement = document.createElement("div");
|
|
gridElement.classList.add("skin-grid-element");
|
|
grid.appendChild(gridElement);
|
|
|
|
// create the widget
|
|
const widgetSection = spine.createSpineWidget({
|
|
atlas: "assets/chibi-stickers-pma.atlas",
|
|
skeleton: "assets/chibi-stickers.json",
|
|
animation,
|
|
skin,
|
|
pages,
|
|
manualStart: true,
|
|
});
|
|
|
|
// append the widget to the grid element
|
|
gridElement.appendChild(widgetSection);
|
|
|
|
// manually start the widget
|
|
widgetSection.start();
|
|
|
|
// access the state of the first widget and change the animation
|
|
if (i === 0 && j === 0) {
|
|
await widgetSection.loadingPromise;
|
|
widgetSection.state.setAnimation(0, "emotes/angry", true);
|
|
}
|
|
|
|
})
|
|
})
|
|
`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
<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;">
|
|
When the widget (or the parent element) enters in the viewport, the callback <code>onScreenFunction</code> is invoked.
|
|
<br>
|
|
<br>
|
|
By default, the callback call the widget <code>start</code> the first time the widget enters the viewport.
|
|
That useful in combination with <code>manual-start="true</code> to load assets only when they are into the viewport.
|
|
<br>
|
|
The assets of the coin below are loaded only when the widget enters the viewport.
|
|
<br>
|
|
<br>
|
|
You can overwrite that behaviour. For example, the raptor below changes animation everytime the widget enters the viewport.
|
|
|
|
</div>
|
|
|
|
<div class="skin-grid">
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
atlas="assets/coin-pma.atlas"
|
|
skeleton="assets/coin-pro.skel"
|
|
animation="animation"
|
|
manual-start="true"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="skin-grid-element">
|
|
<spine-widget
|
|
identifier="coin"
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
|
|
<script>
|
|
(async () => {
|
|
const coinWidget = spine.getSpineWidget("coin");
|
|
await coinWidget.loadingPromise;
|
|
|
|
let raptorWalking = true;
|
|
coinWidget.onScreenFunction = widget => {
|
|
raptorWalking = !raptorWalking;
|
|
widget.state.setAnimation(0, raptorWalking ? "walk" : "jump", true);
|
|
}
|
|
})();
|
|
</script>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
identifier="coin"
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
|
|
...
|
|
|
|
(async () => {
|
|
const coinWidget = spine.getSpineWidget("coin");
|
|
await coinWidget.loadingPromise;
|
|
|
|
let raptorWalking = true;
|
|
coinWidget.onScreenFunction = widget => {
|
|
raptorWalking = !raptorWalking;
|
|
widget.state.setAnimation(0, raptorWalking ? "walk" : "jump", true);
|
|
}
|
|
})();
|
|
|
|
`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
identifier="dragon"
|
|
atlas="assets/dragon-pma.atlas"
|
|
skeleton="assets/dragon-ess.skel"
|
|
animation="flying"
|
|
pages=""
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
If you want to load textures programmatically, you can just pass as pages to load an empty value liek this <code>pages=""</code>.
|
|
<br>
|
|
<br>
|
|
In this way the skeleton and the atlas are loaded, but not the textures.
|
|
<br>
|
|
Then you can loads the textures whenever you want.
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Load page 0" onclick="loadPageDragon(0)">
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Load page 1" onclick="loadPageDragon(1)">
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Load page 2" onclick="loadPageDragon(2)">
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Load page 3" onclick="loadPageDragon(3)">
|
|
<br>
|
|
<br>
|
|
<input type="button" value="Load page 4" onclick="loadPageDragon(4)">
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const dragon = spine.getSpineWidget("dragon");
|
|
function loadPageDragon(pageIndex) {
|
|
if (!dragon.pages) {
|
|
dragon.pages = [];
|
|
}
|
|
if (!dragon.pages.includes(pageIndex)) {
|
|
dragon.pages.push(pageIndex);
|
|
dragon.loadTexturesInPagesAttribute(dragon.textureAtlas);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
spinner="true"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// 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 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 8 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section8" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
As a bonus item, you can move you skeleton around just by setting the <code>draggable</code> property to <code>true</code>.
|
|
</div>
|
|
<div class="split-right" id="section8-element">
|
|
<spine-widget
|
|
atlas="assets/celestial-circus-pma.atlas"
|
|
skeleton="assets/celestial-circus-pro.skel"
|
|
animation="wings-and-feet"
|
|
draggable="true"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/celestial-circus-pma.atlas"
|
|
skeleton="assets/celestial-circus-pro.skel"
|
|
animation="wings-and-feet"
|
|
draggable="true"
|
|
></spine-widget>`
|
|
);</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
|
|
|
|
|
|
function makeDraggable(element) {
|
|
element.style["touch-action"] = "none";
|
|
|
|
let isDragging = false;
|
|
let startX, startY;
|
|
let originalX, originalY;
|
|
|
|
element.addEventListener('pointerdown', startDragging);
|
|
document.addEventListener('pointermove', drag);
|
|
document.addEventListener('pointerup', stopDragging);
|
|
|
|
function startDragging(e) {
|
|
if (e.target === element.querySelector('#resizeHandle')) return;
|
|
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
|
|
const translate = element.style.transform;
|
|
if (translate !== '') {
|
|
const translateValues = translate.match(/translate\(([^)]+)\)/)[1].split(', ');
|
|
originalX = parseFloat(translateValues[0]);
|
|
originalY = parseFloat(translateValues[1]);
|
|
} else {
|
|
originalX = 0;
|
|
originalY = 0;
|
|
}
|
|
}
|
|
|
|
function drag(e) {
|
|
if (!isDragging) return;
|
|
|
|
const deltaX = e.clientX - startX;
|
|
const deltaY = e.clientY - startY;
|
|
|
|
element.style.transform = `translate(${originalX + deltaX}px, ${originalY + deltaY}px)`;
|
|
}
|
|
|
|
function stopDragging(e) {
|
|
isDragging = false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
// Resize utility
|
|
|
|
function makeResizable(element) {
|
|
const resizeHandle = document.createElement('div');
|
|
element.appendChild(resizeHandle);
|
|
resizeHandle.id = "resizeHandle";
|
|
resizeHandle.style.width = "20%";
|
|
resizeHandle.style.height = "20%";
|
|
resizeHandle.style.bottom = "0";
|
|
resizeHandle.style.right = "0";
|
|
resizeHandle.style.position = "absolute";
|
|
resizeHandle.style["background-color"] = "#007bff";
|
|
resizeHandle.style["cursor"] = "se-resize";
|
|
|
|
element.style["position"] = "relative";
|
|
element.style["touch-action"] = "none";
|
|
|
|
let isResizing = false;
|
|
let startX, startY, startWidth, startHeight, startPaddingLeft, startPaddingRight, startPaddingTop, startPaddingBottom;
|
|
|
|
resizeHandle.addEventListener('pointerdown', initResize);
|
|
|
|
function initResize(e) {
|
|
isResizing = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
startWidth = element.offsetWidth;
|
|
startHeight = element.offsetHeight;
|
|
startPaddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
|
|
startPaddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
|
|
startPaddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
|
|
startPaddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
|
|
document.addEventListener('pointermove', resize);
|
|
document.addEventListener('pointerup', stopResize);
|
|
}
|
|
|
|
function resize(e) {
|
|
if (!isResizing) return;
|
|
const width = startWidth + (e.clientX - startX) - startPaddingLeft - startPaddingRight;
|
|
const height = startHeight + (e.clientY - startY) - startPaddingTop - startPaddingBottom;
|
|
element.style.width = width + 'px';
|
|
element.style.height = height + 'px';
|
|
}
|
|
|
|
function stopResize() {
|
|
isResizing = false;
|
|
document.removeEventListener('pointermove', resize);
|
|
document.removeEventListener('pointerup', stopResize);
|
|
}
|
|
}
|
|
|
|
makeDraggable(document.getElementById(`section6-element`));
|
|
makeResizable(document.getElementById(`section6-element`));
|
|
</script>
|
|
</body>
|
|
</html> |