mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
3294 lines
126 KiB
HTML
3294 lines
126 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-webcomponents.js"></script>
|
||
<!-- <script src="./spine-webgl.min.js"></script> -->
|
||
<title>Webcomponent Tutorial</title>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
font-size: 16px;
|
||
}
|
||
.section {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: white;
|
||
background-color: #3498db;
|
||
}
|
||
.split {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-grow: 1;
|
||
}
|
||
.full-width {
|
||
width: 100%;
|
||
}
|
||
.split-left, .split-right {
|
||
width: 50%;
|
||
min-height: 300px;
|
||
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%;
|
||
min-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;
|
||
}
|
||
|
||
.overflow-grid-container {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 100px);
|
||
grid-template-rows: repeat(4, 100px);
|
||
gap: 10px;
|
||
}
|
||
|
||
.overflow-grid-item {
|
||
background-color: lightblue;;
|
||
width: 100px;
|
||
height: 100px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
</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 //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
The <code><spine-skeleton></code> tag allows you to embed your Spine animations into a web page as a web component.
|
||
<br>
|
||
<br>
|
||
By default, the animation bounds are calculated using the specified animation, or the setup pose if no animation is provided.
|
||
<br>
|
||
The bounds are centered and scaled to fit the parent container.
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
></spine-skeleton>`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="full-width">
|
||
|
||
<div class="split">
|
||
<div class="split-left" style="height: 300px;">
|
||
<spine-skeleton
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
fit="fill"
|
||
></spine-skeleton>
|
||
</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>. The default fit value is <code>fit="contain"</code>.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split">
|
||
<div class="split-left" style="height: 300px;">
|
||
<spine-skeleton
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
scale=".125"
|
||
fit="none"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
If you want to preserve the original scale, you can use <code>fit="none"</code> (center the bounds) or <code>fit="origin"</code> (center the skeleton origin).
|
||
In combination with that, you can use the <code>scale</code> attribute to set your desired scale.
|
||
<br>
|
||
<br>
|
||
Other fit modes are <code>width</code>, <code>height</code>, <code>cover</code>, <code>scaleDown</code>..
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
fit="fill"
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
scale=".125"
|
||
fit="none"
|
||
></spine-skeleton>`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<style>
|
||
.custom-class {
|
||
width: 150px;
|
||
height: 150px;
|
||
border: 1px solid green;
|
||
border-radius: 10px;
|
||
box-shadow: -5px 5px 3px rgba(255, 0, 0, 0.3);
|
||
}
|
||
</style>
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
class="custom-class"
|
||
></spine-skeleton>
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
style="
|
||
width: 150px;
|
||
height: 150px;
|
||
border: 1px solid red;
|
||
border-radius: 10px;
|
||
box-shadow: -5px 5px 3px rgba(255, 0, 0, 0.3);
|
||
"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
By default, the widget occupy zero width and height.
|
||
If you want to manually size the Spine widget, you can style the component using the <code>style</code> or <code>class</code> attribute, which provides more styling options.
|
||
<br>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<div>
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
height="150"
|
||
width="150"
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
style="
|
||
width: 150px;
|
||
height: 150px;
|
||
border: 1px solid red;
|
||
border-radius: 10px;
|
||
box-shadow: -5px 5px 3px rgba(255, 0, 0, 0.3);
|
||
"
|
||
></spine-skeleton>
|
||
</div>`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
Move the origin by a percentage of the div's width and height using the <code>x-axis</code> and <code>y-axis</code> attributes, respectively.
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/vine-pma.atlas"
|
||
skeleton="/assets/vine-pro.skel"
|
||
animation="grow"
|
||
fit="origin"
|
||
scale=".5"
|
||
y-axis="-.5"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/vine-pma.atlas"
|
||
skeleton="/assets/vine-pro.skel"
|
||
animation="grow"
|
||
fit="origin"
|
||
scale=".5"
|
||
y-axis="-.5"
|
||
></spine-skeleton>
|
||
`);</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
atlas="/assets/snowglobe-pma.atlas"
|
||
skeleton="/assets/snowglobe-pro.skel"
|
||
animation="shake"
|
||
offset-x="100"
|
||
offset-y="-100"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
Use <code>offset-x</code> and <code>offset-y</code> to move your skeleton left or right by the specified number of pixels.
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/snowglobe-pma.atlas"
|
||
skeleton="/assets/snowglobe-pro.skel"
|
||
animation="shake"
|
||
offset-x="100"
|
||
offset-y="-100"
|
||
></spine-skeleton>
|
||
`);</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
atlas="/assets/goblins-pma.atlas"
|
||
skeleton="/assets/goblins-pro.skel"
|
||
skin="goblingirl"
|
||
animation="walk"
|
||
pad-left=".25"
|
||
pad-right=".25"
|
||
pad-top=".25"
|
||
pad-bottom=".25"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
You can virtually add padding to the element container using <code>pad-left</code>, <code>pad-right</code>, <code>pad-top</code>, and <code>pad-bottom</code>.
|
||
<br>
|
||
<br>
|
||
For the values, you can use a percentage of the width for left and right, and a percentage of the height for top and bottom.
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/goblins-pma.atlas"
|
||
skeleton="/assets/goblins-pro.skel"
|
||
skin="goblingirl"
|
||
animation="walk"
|
||
pad-left=".25"
|
||
pad-right=".25"
|
||
pad-top=".25"
|
||
pad-bottom=".25"
|
||
></spine-skeleton>`);</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
You can customize the bounds, for example to focus on specific details of your animation.
|
||
<br>
|
||
<br>
|
||
The <code>bounds-x</code>, <code>bounds-y</code>, <code>bounds-width</code>, and <code>bounds-height</code> attributes allow you to define custom bounds.
|
||
<br>
|
||
<br>
|
||
In this example, we're zooming in on Celeste's face. You’ll probably want to use <code>clip</code> in this case to prevent the skeleton from overflowing.
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animation="wings-and-feet"
|
||
bounds-x="-155"
|
||
bounds-y="650"
|
||
bounds-width="300"
|
||
bounds-height="350"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animation="wings-and-feet"
|
||
bounds-x="-155"
|
||
bounds-y="650"
|
||
bounds-width="300"
|
||
bounds-height="350"
|
||
clip
|
||
></spine-skeleton>`);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
Assign an <code>identifier</code> to your widget to retrieve it using the <code>spine.getSkeleton</code> function.
|
||
You can easily access the <code>Skeleton</code> and <code>AnimationState</code> of your character, and use them as if you were working with <code>spine-webgl</code>.
|
||
<br>
|
||
<br>
|
||
If you change the animation, you can ask the widget to rescale the skeleton based on the new animation. See the code below.
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
identifier="raptor"
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("raptor").whenReady;
|
||
let isRoaring = false;
|
||
setInterval(() => {
|
||
const newAnimation = isRoaring ? "walk" : "roar";
|
||
widget.state.setAnimation(0, newAnimation, true);
|
||
widget.calculateBounds(); // 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-skeleton
|
||
identifier="raptor"
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
></spine-skeleton>
|
||
|
||
...
|
||
|
||
// using js, access the skeleton and the state asynchronously
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("raptor").whenReady;
|
||
let isRoaring = false;
|
||
setInterval(() => {
|
||
const newAnimation = isRoaring ? "walk" : "roar";
|
||
widget.state.setAnimation(0, newAnimation, true);
|
||
widget.calculateBounds(); // scale the skeleton based on the new animation
|
||
isRoaring = !isRoaring;
|
||
}, 4000);
|
||
})();
|
||
|
||
`);</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<p>To change the animation, you can simply modify the animation attribute. The widget will reinitialize itself and switch to the new animation.</p>
|
||
<p>In this case, you should use <code>auto-calculate-bounds</code> to have the widget always recalculate the bounds, as shown in the top example.</p>
|
||
<p>If you want to keep the scale consistent while fitting multiple animations in the container, you can use the <code>animation-bounds</code> attribute to define bounds that include a list of animations, as shown in the bottom example.</p>
|
||
</div>
|
||
<div class="split-right" style="display: flex; flex-direction: column;">
|
||
<spine-skeleton
|
||
style="width: 100%; flex: 1; border: 1px solid black; box-sizing: border-box;"
|
||
identifier="spineboy-change-animation"
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="jump"
|
||
auto-calculate-bounds
|
||
></spine-skeleton>
|
||
<spine-skeleton
|
||
style="width: 100%; flex: 1; border: 1px solid black; box-sizing: border-box;"
|
||
identifier="spineboy-change-animation2"
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="jump"
|
||
animation-bounds="jump,death"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(async () => {
|
||
{
|
||
const widget = await spine.getSkeleton("spineboy-change-animation").whenReady;
|
||
let toogleAnimation = false;
|
||
setInterval(() => {
|
||
const newAnimation = toogleAnimation ? "jump" : "death";
|
||
widget.setAttribute("animation", newAnimation)
|
||
toogleAnimation = !toogleAnimation;
|
||
}, 4000);
|
||
}
|
||
|
||
{
|
||
const widget = await spine.getSkeleton("spineboy-change-animation2").whenReady;
|
||
let toogleAnimation = false;
|
||
setInterval(() => {
|
||
const newAnimation = toogleAnimation ? "jump" : "death";
|
||
widget.setAttribute("animation", newAnimation)
|
||
toogleAnimation = !toogleAnimation;
|
||
}, 4000);
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
// access the spine widget
|
||
<spine-skeleton
|
||
style="width: 100%; height: 150px; border: 1px solid black;"
|
||
identifier="spineboy-change-animation"
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="jump"
|
||
auto-calculate-bounds
|
||
></spine-skeleton>
|
||
<spine-skeleton
|
||
style="width: 100%; height: 150px; border: 1px solid black;"
|
||
identifier="spineboy-change-animation2"
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="jump"
|
||
animation-bounds="jump,death"
|
||
></spine-skeleton>
|
||
|
||
...
|
||
|
||
// using js, access the skeleton and the state asynchronously
|
||
{
|
||
const widget = await spine.getSkeleton("spineboy-change-animation").whenReady;
|
||
let toogleAnimation = false;
|
||
setInterval(() => {
|
||
const newAnimation = toogleAnimation ? "jump" : "death";
|
||
widget.setAttribute("animation", newAnimation)
|
||
toogleAnimation = !toogleAnimation;
|
||
}, 4000);
|
||
}
|
||
|
||
{
|
||
const widget = await spine.getSkeleton("spineboy-change-animation2").whenReady;
|
||
let toogleAnimation = false;
|
||
setInterval(() => {
|
||
const newAnimation = toogleAnimation ? "jump" : "death";
|
||
widget.setAttribute("animation", newAnimation)
|
||
toogleAnimation = !toogleAnimation;
|
||
}, 4000);
|
||
}
|
||
|
||
`);</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="flex-direction: column;">
|
||
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
|
||
<p>If you want to display a sequence of animations without using JavaScript, you can use the <code>animations</code> attribute.</p>
|
||
|
||
<p>It accepts a string composed of groups enclosed in square brackets, like this: <code>[...][...][...]</code></p>
|
||
|
||
<p>Each group represents an animation to play, with parameters in a comma-separated list:</p>
|
||
<ol>
|
||
<li><code>track</code>: the track number to play the animation on</li>
|
||
<li><code>animation name</code>: the name of the animation</li>
|
||
<li><code>loop</code>: true to loop the animation, false otherwise</li>
|
||
<li><code>delay</code>: the number of seconds to wait after the previous animation starts (not used for the first animation on a track)</li>
|
||
<li><code>mixDuration</code>: the mix duration between this animation and the previous one (not used for the first animation on a track)</li>
|
||
</ol>
|
||
|
||
<p>To loop a track once it reaches the end, add the special group <code>[loop, trackNumber, repeatDelay]</code>, where:</p>
|
||
<ul>
|
||
<li><code>loop</code>: identifies this as a loop instruction</li>
|
||
<li><code>trackNumber</code>: the number of the track to loop</li>
|
||
<li><code>repeatDelay</code>: the number of seconds to wait after the last animation is completed before repeating the loop</li>
|
||
</ul>
|
||
|
||
<p>The parameters of the first group on each track are passed to the <code>setAnimation</code> method, while the remaining groups use <code>addAnimation</code>.</p>
|
||
|
||
<p>To use <code>setEmptyAnimation</code> or <code>addEmptyAnimation</code>, use <code>#EMPTY#</code> as the animation name. In this case, the <code>loop</code> parameter is ignored.</p>
|
||
|
||
<p>The <code>default-mix</code> attribute allows you to set the default mix duration for the <code>AnimationState</code>.</p>
|
||
|
||
<p>See the two examples below.</p>
|
||
</div>
|
||
|
||
<div class="split-nosize full-width" style="width: 80%; min-height: 300px; margin: 1em; padding: 1em;">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation-bounds="jump,death"
|
||
default-mix="0.05"
|
||
animations="
|
||
[loop, 0]
|
||
[0, idle, true]
|
||
[0, run, false, 2, 0.25]
|
||
[0, run, false]
|
||
[0, run, false]
|
||
[0, run-to-idle, false, 0, 0.15]
|
||
[0, idle, true]
|
||
[0, jump, false, 0, 0.15]
|
||
[0, walk, false, 0, 0.05]
|
||
[0, death, false, 0, 0.05]
|
||
"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
|
||
<p>Spineboy uses the following value for the <code>animations</code> attribute:</p>
|
||
<p>
|
||
<textarea style="font-size: 0.6rem; width: 100%;" rows="10" readonly>
|
||
[loop, 0]
|
||
[0, idle, true]
|
||
[0, run, false, 2, 0.25]
|
||
[0, run, false]
|
||
[0, run, false]
|
||
[0, run-to-idle, false, 0, 0.15]
|
||
[0, idle, true]
|
||
[0, jump, false, 0, 0.15]
|
||
[0, walk, false, 0, 0.05]
|
||
[0, death, false, 0, 0.05]</textarea>
|
||
</p>
|
||
|
||
We use a single track for this animation. Let's break it down:
|
||
<ol>
|
||
<li><code>[loop, 0]</code>: when track 0 reaches the end, it loops back to the beginning</li>
|
||
<li><code>[0, idle, true]</code>: sets the idle animation to loop</li>
|
||
<li><code>[0, run, false, 2, 0.25]</code>: queues the run animation, starts it after 2 seconds with a 0.25-second mix</li>
|
||
<li><code>[0, run, false]</code>: queues another run animation</li>
|
||
<li><code>[0, run, false]</code>: queues another run animation</li>
|
||
<li><code>[0, run-to-idle, false, 0, 0.15]</code>: queues run-to-idle with no delay and a 0.15-second mix</li>
|
||
<li><code>[0, idle, true]</code>: queues the idle animation to loop</li>
|
||
<li><code>[0, jump, false, 0, 0.15]</code>: queues jump with no delay and a 0.15-second mix</li>
|
||
<li><code>[0, walk, false, 0, 0.05]</code>: queues walk with no delay and a 0.05-second mix</li>
|
||
<li><code>[0, death, false, 0, 0.05]</code>: queues death with no delay and a 0.05-second mix</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="split-nosize full-width" style="width: 80%; min-height: 300px; margin: 1em; padding: 1em;">
|
||
<spine-skeleton
|
||
identifier="celeste-animations"
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animations="
|
||
[0, wings-and-feet, true]
|
||
[loop, 1]
|
||
[1, #EMPTY#, false]
|
||
[1, eyeblink, false, 2]
|
||
"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
|
||
<p>Celeste uses the following value for the <code>animations</code> attribute:</p>
|
||
<p>
|
||
<textarea id="celeste-animations-text-area" style="font-size: 0.6rem; width: 100%;" rows="4">
|
||
[0, wings-and-feet, true]
|
||
[loop, 1]
|
||
[1, #EMPTY#, false]
|
||
[1, eyeblink, false, 2]</textarea>
|
||
</p>
|
||
|
||
<p>
|
||
This example uses two tracks. Track 0 plays the <code>wings-and-feet</code> animation. <br>
|
||
Track 1 loops, playing an empty animation followed by an <code>eyeblink</code> animation with a 2-second delay.
|
||
</p>
|
||
|
||
<p>You can modify the textarea above and experiment. For example, change the delay from 2 to 0.5, or add the <code>swing</code> animation to track 0 like this: <code>[0, swing, true, 5, 0.5]</code> to start it after 5 seconds with a 0.5-second mix. Click the button below and Celeste will start blinking more frequently.</p>
|
||
|
||
<input type="button" value="Update animation" onclick="updateCelesteAnimations(this)">
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
async function updateCelesteAnimations() {
|
||
const celesteAnimations = await spine.getSkeleton("celeste-animations").whenReady;
|
||
var celesteAnimationsTextArea = document.getElementById("celeste-animations-text-area");
|
||
celesteAnimations.setAttribute("animations", celesteAnimationsTextArea.value)
|
||
}
|
||
</script>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation-bounds="jump,death"
|
||
default-mix="0.05"
|
||
animations="
|
||
[loop, 0]
|
||
[0, idle, true]
|
||
[0, run, false, 2, 0.25]
|
||
[0, run, false]
|
||
[0, run, false]
|
||
[0, run-to-idle, false, 0, 0.15]
|
||
[0, idle, true]
|
||
[0, jump, false, 0, 0.15]
|
||
[0, walk, false, 0, 0.05]
|
||
[0, death, false, 0, 0.05]
|
||
"
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
identifier="celeste-animations"
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animations="
|
||
[0, wings-and-feet, true]
|
||
[loop, 1]
|
||
[1, #EMPTY#, false]
|
||
[1, eyeblink, false, 2]
|
||
"
|
||
></spine-skeleton>
|
||
|
||
<textarea id="celeste-animations-text-area" style="font-size: 0.6rem; width: 100%;" rows="5">
|
||
[0, wings-and-feet, true]
|
||
[loop, 1]
|
||
[1, #EMPTY#, false]
|
||
[1, eyeblink, false, 2]
|
||
</textarea>
|
||
|
||
<input type="button" value="Update animation" onclick="updateCelesteAnimations(this)">
|
||
...
|
||
|
||
async function updateCelesteAnimations() {
|
||
const celesteAnimations = await spine.getSkeleton("celeste-animations").whenReady;
|
||
var celesteAnimationsTextArea = document.getElementById("celeste-animations-text-area");
|
||
celesteAnimations.setAttribute("animations", celesteAnimationsTextArea.value)
|
||
}`);</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div 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 changes the position of the skeleton's origin.<br>
|
||
Resizing the div will scale the skeleton when in <code>inside</code> mode, but not when in <code>origin</code> mode.<br>
|
||
Try dragging and resizing the div in the example above to see the effect.
|
||
</div>
|
||
<div class="split-nosize full-width" style="width: 80%; height: 50%; margin: 1em; padding: 1em;" id="section7-element">
|
||
<spine-skeleton
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
></spine-skeleton>`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
You can view the skeleton's world origin (green), the root bone position (red), and the bounds rectangle and center (blue) by setting <code>debug</code> to <code>true</code>.
|
||
<br>
|
||
Here we slightly shift the root to prevent it from overlapping with the origin.
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
identifier="sack-debug"
|
||
atlas="/assets/sack-pma.atlas"
|
||
skeleton="/assets/sack-pro.skel"
|
||
animation="cape-follow-example"
|
||
debug
|
||
></spine-skeleton>
|
||
|
||
<script>
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("sack-debug").whenReady;
|
||
widget.skeleton.getRootBone().x += 50;
|
||
})();
|
||
</script>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
identifier="sack-debug"
|
||
atlas="/assets/sack-pma.atlas"
|
||
skeleton="/assets/sack-pro.skel"
|
||
animation="cape-follow-example"
|
||
debug
|
||
></spine-skeleton>
|
||
...
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("sack-debug").whenReady;
|
||
widget.skeleton.getRootBone().x += 50;
|
||
})();`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
If you want to embed your assets within the page, you can inline them using their base64 versions. Use a stringified JSON object where the keys are the asset names and the values are their base64-encoded contents.
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="inline.atlas"
|
||
skeleton="inline.skel"
|
||
animation="animation"
|
||
raw-data='{
|
||
"inline.atlas":"aW5saW5lLnBuZwpzaXplOjE2LDE2CmZpbHRlcjpMaW5lYXIsTGluZWFyCnBtYTp0cnVlCmRvdApib3VuZHM6MCwwLDEsMQo=",
|
||
"inline.skel":"/B8S/IqaXgYHNC4yLjM5wkgAAMJIAABCyAAAQsgAAELIAAAAAQRkb3QCBXJvb3QAAAAAAAAAAAAAAAA/gAAAP4AAAAAAAAAAAAAAAAAAAAAABGRvdAAAAAAAAAAAAAAAAABCyAAAQsgAAAAAAAAAAAAAAAAAAAAAAQRkb3QB//////////8BAAAAAAABAAEBACWwfdcAAAAAP4AAAD+AAAA/gAAAP4AAAAAAAQphbmltYXRpb24BAQABAQMAAAAAAP////8/gAAA/wAA/wBAAAAA/////wAAAAAAAAAAAA==",
|
||
"inline.png":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRF////p8QbyAAAAApJREFUeJxjZAAAAAQAAiFkrWoAAAAASUVORK5CYII="
|
||
}'
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/inline.atlas"
|
||
skeleton="/assets/inline.skel"
|
||
animation="animation"
|
||
raw-data='{
|
||
"/assets/inline.atlas":"aW5saW5lLnBuZwpzaXplOjE2LDE2CmZpbHRlcjpMaW5lYXIsTGluZWFyCnBtYTp0cnVlCmRvdApib3VuZHM6MCwwLDEsMQo=",
|
||
"/assets/inline.skel":"/B8S/IqaXgYHNC4yLjM5wkgAAMJIAABCyAAAQsgAAELIAAAAAQRkb3QCBXJvb3QAAAAAAAAAAAAAAAA/gAAAP4AAAAAAAAAAAAAAAAAAAAAABGRvdAAAAAAAAAAAAAAAAABCyAAAQsgAAAAAAAAAAAAAAAAAAAAAAQRkb3QB//////////8BAAAAAAABAAEBACWwfdcAAAAAP4AAAD+AAAA/gAAAP4AAAAAAAQphbmltYXRpb24BAQABAQMAAAAAAP////8/gAAA/wAA/wBAAAAA/////wAAAAAAAAAAAA==",
|
||
"/assets/inline.png":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRF////p8QbyAAAAApJREFUeJxjZAAAAAQAAiFkrWoAAAAASUVORK5CYII="
|
||
}'
|
||
></spine-skeleton>`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
identifier="widget-loading"
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
spinner
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
If you want to show loading spinner while assets are loading, set the <code>spinner</code> attribute. Click the button below to simulate a 2-second loading delay:
|
||
<br>
|
||
<br>
|
||
<input type="button" value="Simulate reload" onclick="reloadWidget(this)">
|
||
<br>
|
||
<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.getSkeleton("widget-loading");
|
||
async function reloadWidget(element) {
|
||
element.disabled = true;
|
||
await widget.whenReady;
|
||
widget.loading = true;
|
||
setTimeout(() => {
|
||
element.disabled = false;
|
||
widget.loading = false;
|
||
}, 1000)
|
||
}
|
||
function toggleSpinner(element) {
|
||
widget.spinner = !widget.spinner;
|
||
element.value = widget.spinner ? "Spinner ON" : "Spinner OFF";
|
||
}
|
||
</script>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
identifier="widget-loading"
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
spinner
|
||
></spine-skeleton>
|
||
<input type="button" value="Simulate reload" onclick="reloadWidget(this)">
|
||
<input type="button" value="Spinner ON" onclick="toggleSpinner(this)">
|
||
|
||
...
|
||
|
||
const widget = spine.getSkeleton("widget-loading");
|
||
async function reloadWidget(element) {
|
||
element.disabled = true;
|
||
await widget.whenReady;
|
||
const skeleton = widget.skeleton;
|
||
widget.loading = true;
|
||
setTimeout(() => {
|
||
element.disabled = false;
|
||
widget.loading = false;
|
||
}, 1000)
|
||
}
|
||
function toggleSpinner(element) {
|
||
widget.spinner = !widget.spinner;
|
||
element.value = widget.spinner ? "Spinner ON" : "Spinner OFF";
|
||
}`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
It's very easy to display your different skins and animations. Simply create a table and use the <code>skin</code> and <code>animation</code> attributes.
|
||
<br>
|
||
<code>skin</code> accepts a comma separated list of skin names. The skins will be combined in a new one, from the first to the last. If multiple skins set the same slot, the latest in the list will be used.
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="idle"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dance"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dress-up"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="walk"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="idle"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dance"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dress-up"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="walk"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="idle"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dance"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dress-up"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="walk"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="idle"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dance"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dress-up"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="walk"
|
||
skin="full-skins/girl-spring-dress"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="idle"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dance"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dress-up"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="walk"
|
||
skin="full-skins/girl-blue-cape"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="idle"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dance"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="dress-up"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/mix-and-match-pma.atlas"
|
||
skeleton="/assets/mix-and-match-pro.skel"
|
||
animation="walk"
|
||
skin="full-skins/girl"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
If you have multiple atlas pages, for example one for each skin, and you want to display only some of the skins,
|
||
pass the atlas pages you want to load to the <code>pages</code> attribute as a comma-separated list of indices.
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/wave"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="movement/trot-left"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/idea"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/hooray"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/wave"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="movement/trot-left"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/idea"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/hooray"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/wave"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="movement/trot-left"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/idea"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/hooray"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/wave"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="movement/trot-left"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/idea"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/hooray"
|
||
skin="nate"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/wave"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="movement/trot-left"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/idea"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/hooray"
|
||
skin="erikari"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/wave"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="movement/trot-left"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/idea"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.skel"
|
||
animation="emotes/hooray"
|
||
skin="mario"
|
||
pages="0,1,4,6"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
Let's do the same thing above, but programmatically!
|
||
Create two arrays, one for the skins and the other for the animations, and loop through them.
|
||
<br>
|
||
<br>
|
||
<code>spine.createSkeleton</code> allows you to create a Spine widget.
|
||
<br>
|
||
<br>
|
||
By default, assets are loaded immediately. You can delay this by setting <code>manual-start="false"</code>.
|
||
Then, add the widget to the DOM using the asynchronous method <code>appendTo</code>. It is your responsibility to call <code>start()</code> on the widget.
|
||
As usual, just wait for the <code>whenReady</code> to interact with the <code>skeleton</code> or the <code>state</code>.
|
||
</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.createSkeleton({
|
||
atlasPath: "/assets/chibi-stickers-pma.atlas",
|
||
skeletonPath: "/assets/chibi-stickers.json",
|
||
animation,
|
||
skin,
|
||
pages,
|
||
manualStart: true,
|
||
});
|
||
|
||
// append the widget to the grid element
|
||
await widgetSection.appendTo(gridElement);
|
||
|
||
// manually start the widget
|
||
widgetSection.start();
|
||
|
||
// access the state of the first widget and change the animation
|
||
if (i === 0 && j === 0) {
|
||
await widgetSection.whenReady;
|
||
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.createSkeleton({
|
||
atlasPath: "/assets/chibi-stickers-pma.atlas",
|
||
skeletonPath: "/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.whenReady;
|
||
widgetSection.state.setAnimation(0, "emotes/angry", true);
|
||
}
|
||
|
||
})
|
||
})
|
||
`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
When the widget (or its parent element) enters the viewport, two things happen:<br>
|
||
<ul>
|
||
<li>the widget's <code>onScreenAtLeastOnce</code> property is set to <code>true</code></li>
|
||
<li>the widget's <code>onScreenFunction</code> callback is invoked</li>
|
||
</ul>
|
||
By default, <code>onScreenFunction</code> invokes the widget's <code>start</code> method if the widget has the <code>start-when-visible</code> attribute set, and this occurs only the first time it enters the viewport.<br>
|
||
<br>
|
||
The assets of the coin below are loaded only when the widget enters the viewport.<br>
|
||
<br>
|
||
You can override the <code>onScreenFunction</code> behavior. For example, the raptor below changes its animation every time the widget enters the viewport.
|
||
</div>
|
||
|
||
<div class="skin-grid">
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
atlas="/assets/coin-pma.atlas"
|
||
skeleton="/assets/coin-pro.skel"
|
||
animation="animation"
|
||
start-when-visible
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="skin-grid-element">
|
||
<spine-skeleton
|
||
identifier="coin-with-raptor"
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
></spine-skeleton>
|
||
|
||
<script>
|
||
(async () => {
|
||
const raptorWidget = await spine.getSkeleton("coin-with-raptor").whenReady;
|
||
let raptorWalking = true;
|
||
raptorWidget.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-skeleton
|
||
atlas="/assets/coin-pma.atlas"
|
||
skeleton="/assets/coin-pro.skel"
|
||
animation="animation"
|
||
start-when-visible
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
identifier="coin-with-raptor"
|
||
atlas="/assets/raptor-pma.atlas"
|
||
skeleton="/assets/raptor-pro.skel"
|
||
animation="walk"
|
||
></spine-skeleton>
|
||
|
||
...
|
||
|
||
const raptorWidget = await spine.getSkeleton("coin-with-raptor").whenReady;
|
||
let raptorWalking = true;
|
||
raptorWidget.onScreenFunction = widget => {
|
||
raptorWalking = !raptorWalking;
|
||
widget.state.setAnimation(0, raptorWalking ? "walk" : "jump", true);
|
||
}`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
identifier="dragon"
|
||
atlas="/assets/dragon-pma.atlas"
|
||
skeleton="/assets/dragon-ess.skel"
|
||
animation="flying"
|
||
pages=""
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
If you want to load textures programmatically, you can just pass an empty value for pages like this <code>pages=""</code>.
|
||
<br>
|
||
<br>
|
||
In this way, the skeleton and the atlas are loaded, but not the textures.
|
||
<br>
|
||
Then you can load 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.getSkeleton("dragon");
|
||
function loadPageDragon(pageIndex) {
|
||
if (!dragon.pages) {
|
||
dragon.pages = [];
|
||
}
|
||
if (!dragon.pages.includes(pageIndex)) {
|
||
dragon.pages.push(pageIndex);
|
||
dragon.loadTexturesInPagesAttribute();
|
||
}
|
||
}
|
||
</script>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
identifier="dragon"
|
||
atlas="/assets/dragon-pma.atlas"
|
||
skeleton="/assets/dragon-ess.skel"
|
||
animation="flying"
|
||
pages=""
|
||
></spine-skeleton>
|
||
<input type="button" value="Load page 0" onclick="loadPageDragon(0)">
|
||
<input type="button" value="Load page 1" onclick="loadPageDragon(1)">
|
||
<input type="button" value="Load page 2" onclick="loadPageDragon(2)">
|
||
<input type="button" value="Load page 3" onclick="loadPageDragon(3)">
|
||
<input type="button" value="Load page 4" onclick="loadPageDragon(4)">
|
||
|
||
...
|
||
|
||
const dragon = spine.getSkeleton("dragon");
|
||
function loadPageDragon(pageIndex) {
|
||
if (!dragon.pages) {
|
||
dragon.pages = [];
|
||
}
|
||
if (!dragon.pages.includes(pageIndex)) {
|
||
dragon.pages.push(pageIndex);
|
||
dragon.loadTexturesInPagesAttribute();
|
||
}
|
||
}`)
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
Widgets are not rendered 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 case, it's your responsibility to skip the update/apply. You can use the <code>onScreen</code> property for convenience.
|
||
</div>
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||
<spine-skeleton
|
||
atlas="/assets/stretchyman-pma.atlas"
|
||
skeleton="/assets/stretchyman-pro.skel"
|
||
animation="sneak"
|
||
offscreen="pose"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||
<spine-skeleton
|
||
identifier="stretchyman"
|
||
atlas="/assets/stretchyman-pma.atlas"
|
||
skeleton="/assets/stretchyman-pro.skel"
|
||
animation="sneak"
|
||
offscreen="pose"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<script>
|
||
const stretchyman = spine.getSkeleton("stretchyman");
|
||
stretchyman.update = (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-skeleton
|
||
atlas="/assets/stretchyman-pma.atlas"
|
||
skeleton="/assets/stretchyman-pro.skel"
|
||
animation="sneak"
|
||
offscreen="pose"
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
identifier="stretchyman"
|
||
atlas="/assets/stretchyman-pma.atlas"
|
||
skeleton="/assets/stretchyman-pro.skel"
|
||
animation="sneak"
|
||
offscreen="pose"
|
||
></spine-skeleton>
|
||
|
||
...
|
||
|
||
const stretchyman = spine.getSkeleton("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>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
If for some reason your skeleton bounds go outside the div,
|
||
you can use the <code>clip</code> property to clip everything that is outside the HTML container.
|
||
<br>
|
||
<br>
|
||
Be aware that this will break batching!
|
||
</div>
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||
<spine-skeleton
|
||
identifier="tank2"
|
||
atlas="/assets/tank-pma.atlas"
|
||
skeleton="/assets/tank-pro.skel"
|
||
animation="drive"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<div class="split-left" style="width: 30%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||
<spine-skeleton
|
||
identifier="tank"
|
||
atlas="/assets/tank-pma.atlas"
|
||
skeleton="/assets/tank-pro.skel"
|
||
animation="drive"
|
||
fit="none"
|
||
clip="true"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<script>
|
||
(async () => {
|
||
const [tank, tank2] = await Promise.all([
|
||
spine.getSkeleton("tank").whenReady, spine.getSkeleton("tank2").whenReady]);
|
||
|
||
tank.beforeUpdateWorldTransforms = (delta, skeleton, state) => {
|
||
if (!tank.onScreen) return;
|
||
tank.skeleton.scaleX = tank2.skeleton.scaleX;
|
||
tank.skeleton.scaleY = tank2.skeleton.scaleY;
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
|
||
<spine-skeleton
|
||
identifier="tank2"
|
||
atlas="/assets/tank-pma.atlas"
|
||
skeleton="/assets/tank-pro.skel"
|
||
animation="drive"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
<div class="split-left" style="width: 30%; box-sizing: border-box; height: 150px;">
|
||
<spine-skeleton
|
||
identifier="tank"
|
||
atlas="/assets/tank-pma.atlas"
|
||
skeleton="/assets/tank-pro.skel"
|
||
animation="drive"
|
||
fit="none"
|
||
clip="true"
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
...
|
||
|
||
const [tank, tank2] = await Promise.all([
|
||
spine.getSkeleton("tank").whenReady, spine.getSkeleton("tank2").whenReady]);
|
||
|
||
tank.beforeUpdateWorldTransforms = (delta, skeleton, state) => {
|
||
if (!tank.onScreen) return;
|
||
tank.skeleton.scaleX = tank2.skeleton.scaleX;
|
||
tank.skeleton.scaleY = tank2.skeleton.scaleY;
|
||
}`);
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||
More examples for <code>clip</code> attribute.
|
||
</div>
|
||
|
||
<div class="split" style="width: 100%; flex-direction: column;">
|
||
|
||
<div class="split-top split" style="align-items: stretch">
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
scale="3"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
scale="1.5"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
scale="1"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
scale="0.5"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
identifier="celeste"
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
clip
|
||
drag
|
||
></spine-skeleton>
|
||
|
||
<script>
|
||
(async () => {
|
||
const celeste = spine.getSkeleton("celeste");
|
||
await celeste.whenReady;
|
||
celeste.state.setAnimation(0, "swing", true);
|
||
})();
|
||
</script>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>
|
||
escapeHTMLandInject(`
|
||
<div class="split-left">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
scale="3"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
scale="1.5"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/spineboy-pma.atlas"
|
||
skeleton="/assets/spineboy-pro.skel"
|
||
animation="walk"
|
||
scale="1"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
scale="0.5"
|
||
fit="none"
|
||
clip
|
||
></spine-skeleton>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
identifier="celeste"
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animation="wings-and-feet"
|
||
clip
|
||
drag
|
||
></spine-skeleton>
|
||
</div>
|
||
|
||
...
|
||
|
||
(async () => {
|
||
const celeste = spine.getSkeleton("celeste");
|
||
await celeste.whenReady;
|
||
celeste.state.setAnimation(0, "swing", true);
|
||
})();`);
|
||
</script>
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-left">
|
||
If you use a spine widget in an element that has an ancestor that does not follow the webpage scroll, the effect might not be the desired one. You might encounter these problems:
|
||
<br>
|
||
<br>
|
||
1) For scrollable containers, the widget will be slightly slower to scroll than the HTML behind. The effect is more evident for lower refresh rate displays.<br>
|
||
2) For scrollable containers, the widget will overflow the container bounds until the widget HTML element container is visible.<br>
|
||
3) For fixed containers, the widget will scroll in a jerky way.<br>
|
||
<br>
|
||
In order to fix this behavior, it is necessary to insert a dedicated <code>spine-overlay</code> web component as a direct child of the container.
|
||
Moreover, it is necessary to perform the following actions: <br>
|
||
<br>
|
||
1) The scrollable container must have a <code>transform</code> CSS attribute. If it doesn't have this attribute, the <code>spine-overlay</code> will add it for you.
|
||
If you don't want this attribute to be added, set the <code>no-auto-parent-transform</code> on the <code>spine-overlay</code>. But watch out, the widget might not work as intended.
|
||
<br>
|
||
2) The <code>spine-overlay</code> must have an <code>overlay-id</code> attribute. Choose the value you prefer.
|
||
<br>
|
||
3) Each <code>spine-skeleton</code> must have an <code>overlay-id</code> attribute. The same as the hosting <code>spine-overlay</code>.
|
||
<br>
|
||
<br>
|
||
Additionally, you can set <code>overflow-top</code>, <code>overflow-bottom</code>, <code>overflow-left</code>, <code>overflow-right</code> attributes to the <code>spine-overlay</code> in order to make the canvas bigger and prevent scrolling artifacts.
|
||
<br>
|
||
<br>
|
||
See the two examples below:
|
||
<br>
|
||
- Click the following button to open two elements with fixed positioning. The left one does not have a dedicated overlay and will scroll in a jerky way. <button id="popup-overlay-button-open">Open Popup</button>
|
||
<br>
|
||
- Below there are two scrolling items. The left one does not have a dedicated overlay, it will lag on scroll and the widgets will overflow the container.
|
||
<br>
|
||
<br>
|
||
</div>
|
||
|
||
<div id="popup-overlay" style="
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100%; height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: none;
|
||
justify-content: center; align-items: center; flex-direction: column;
|
||
color: black;
|
||
">
|
||
<div style="display: flex; justify-content: center; align-items: center; flex-direction: column; gap: 10px; margin-bottom: 10px;">
|
||
|
||
<div id="popup" style="background-color: white; padding: 20px; text-align: center;">
|
||
<spine-skeleton
|
||
atlas="../../spine-webgl/demos/assets/atlas2.atlas"
|
||
skeleton="../../spine-webgl/demos/assets/demos.json"
|
||
json-skeleton-key="armorgirl"
|
||
animation="animation"
|
||
style="width: 200px; height: 200px; border: 1px solid black;"
|
||
></spine-skeleton>
|
||
<br>
|
||
This fixed element lags when scrolling.
|
||
</div>
|
||
|
||
<div id="popup" style="background-color: white; padding: 20px; text-align: center;">
|
||
<spine-overlay
|
||
overlay-id="popup"
|
||
scrollable
|
||
></spine-overlay>
|
||
<spine-skeleton
|
||
atlas="../../spine-webgl/demos/assets/atlas2.atlas"
|
||
skeleton="../../spine-webgl/demos/assets/demos.json"
|
||
json-skeleton-key="armorgirl"
|
||
animation="animation"
|
||
overlay-id="popup"
|
||
style="width: 200px; height: 200px; border: 1px solid black;"
|
||
></spine-skeleton>
|
||
<br>
|
||
This fixed element does not lag when scrolling.
|
||
</div>
|
||
</div>
|
||
<button id="popup-overlay-button-close">Close Popup</button>
|
||
</div>
|
||
|
||
<script>
|
||
// Get the buttons and popup elements
|
||
const openPopupButton = document.getElementById('popup-overlay-button-open');
|
||
const closePopupButton = document.getElementById('popup-overlay-button-close');
|
||
const popupOverlay = document.getElementById('popup-overlay');
|
||
|
||
// Event listener to open the popup
|
||
openPopupButton.addEventListener('click', function() {
|
||
popupOverlay.style.display = 'flex';
|
||
});
|
||
|
||
// Event listener to close the popup
|
||
closePopupButton.addEventListener('click', function() {
|
||
popupOverlay.style.display = 'none';
|
||
});
|
||
</script>
|
||
|
||
<div class="split-top split" style="justify-content: space-between">
|
||
<div class="split-left" style="overflow-y: auto; width: 100px; height: 200px;">
|
||
<div class="overflow-grid-container">
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-left" style="overflow-y: auto; width: 100px; height: 200px; transform: translateZ(0);">
|
||
<spine-overlay
|
||
overlay-id="scroll"
|
||
overflow-top=".2"
|
||
overflow-bottom=".2"
|
||
overflow-left=".2"
|
||
overflow-right=".2"
|
||
></spine-overlay>
|
||
<div class="overflow-grid-container">
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
// POPUP EXAMPLE
|
||
|
||
<div id="popup-overlay" style="
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100%; height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: none;
|
||
justify-content: center; align-items: center; flex-direction: column;
|
||
color: black;
|
||
">
|
||
<div style="display: flex; justify-content: center; align-items: center; flex-direction: column; gap: 10px; margin-bottom: 10px;">
|
||
|
||
<div id="popup" style="background-color: white; padding: 20px; text-align: center;">
|
||
<spine-skeleton
|
||
atlas="../spine-webgl/demos/assets/atlas2.atlas"
|
||
skeleton="../spine-webgl/demos/assets/demos.json"
|
||
json-skeleton-key="armorgirl"
|
||
animation="animation"
|
||
style="width: 200px; height: 200px; border: 1px solid black;"
|
||
></spine-skeleton>
|
||
<br>
|
||
This fixed element lags when scrolling.
|
||
</div>
|
||
|
||
<div id="popup" style="background-color: white; padding: 20px; text-align: center;">
|
||
<spine-overlay
|
||
overlay-id="popup"
|
||
scrollable
|
||
no-auto-parent-transform
|
||
></spine-overlay>
|
||
<spine-skeleton
|
||
atlas="../spine-webgl/demos/assets/atlas2.atlas"
|
||
skeleton="../spine-webgl/demos/assets/demos.json"
|
||
json-skeleton-key="armorgirl"
|
||
animation="animation"
|
||
overlay-id="popup"
|
||
style="width: 200px; height: 200px; border: 1px solid black;"
|
||
></spine-skeleton>
|
||
<br>
|
||
This fixed element does not lag when scrolling.
|
||
</div>
|
||
</div>
|
||
<button id="popup-overlay-button-close">Close Popup</button>
|
||
</div>
|
||
|
||
|
||
|
||
// SCROLLABLE CONTAINER EXAMPLE
|
||
<div class="split-top split" style="justify-content: space-between">
|
||
<div class="split-left" style="overflow-y: auto; width: 100px; height: 200px;">
|
||
<div class="overflow-grid-container">
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel"></spine-skeleton></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-left" style="overflow-y: auto; width: 100px; height: 200px;">
|
||
<spine-overlay
|
||
overlay-id="scroll"
|
||
scrollable
|
||
no-auto-parent-transform
|
||
overflow-top=".2"
|
||
overflow-bottom=".2"
|
||
overflow-left=".2"
|
||
overflow-right=".2"
|
||
></spine-overlay>
|
||
<div class="overflow-grid-container">
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
<div class="overflow-grid-item"><spine-skeleton atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" overlay-id="scroll"></spine-skeleton></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
|
||
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split" id="above-popup">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
As a bonus item, you can move your skeleton around just by setting the <code>drag</code> property to <code>true</code>.
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animation="wings-and-feet"
|
||
drag
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
atlas="/assets/celestial-circus-pma.atlas"
|
||
skeleton="/assets/celestial-circus-pro.skel"
|
||
animation="wings-and-feet"
|
||
drag
|
||
></spine-skeleton>`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split" >
|
||
<div class="split-left">
|
||
If you need to determine the pointer position in the overlay world, you might find useful the following properties.
|
||
<br>
|
||
For <code>spine-skeleton</code>:
|
||
<ul>
|
||
<li><code>pointerWorldX</code> and <code>pointerWorldY</code> are the x and y of the pointer relative to the skeleton root (spine world).</li>
|
||
<li><code>worldX</code> and <code>worldY</code> are the x and y of the root relative to the canvas/webgl context origin (spine world).</li>
|
||
</ul>
|
||
|
||
For <code>spine-overlay</code>:
|
||
<ul>
|
||
<li><code>pointerCanvasX</code> and <code>pointerCanvasY</code> are the x and y of the pointer relative to the canvas top-left corner (screen world).</li>
|
||
<li><code>pointerWorldX</code> and <code>pointerWorldY</code> are the x and y of the pointer relative to the canvas/webgl context origin (spine world).</li>
|
||
</ul>
|
||
|
||
You can use these properties to interact with your widget. See the following examples where the owl eyes will follow the pointer, even if you drag the owls to another position.
|
||
Exaggerate the movement by deselecting the checkbox below.
|
||
|
||
<br>
|
||
<br>
|
||
<label>
|
||
<input type="checkbox" id="owl-checkbox" checked> Limit control bone movement
|
||
</label>
|
||
<br>
|
||
<br>
|
||
This feature is experimental and might be removed in the future.
|
||
</div>
|
||
|
||
<div id="section-owls"></div>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<div id="section-owls"></div>
|
||
|
||
...
|
||
|
||
function createCircleOfDivs(numDivs = 8) {
|
||
const container = document.createElement('div');
|
||
container.style.position = 'relative';
|
||
container.style.width = '400px';
|
||
container.style.height = '400px';
|
||
container.style.backgroundColor = '#f3f4f6';
|
||
container.style.borderRadius = '50%';
|
||
container.style.display = 'flex';
|
||
container.style.justifyContent = 'center';
|
||
container.style.alignItems = 'center';
|
||
|
||
const radius = 150;
|
||
|
||
for (let i = 0; i < numDivs; i++) {
|
||
const angle = (i / numDivs) * 2 * Math.PI;
|
||
const x = Math.cos(angle) * radius;
|
||
const y = Math.sin(angle) * radius;
|
||
|
||
const div = document.createElement('div');
|
||
div.style.position = 'absolute';
|
||
div.style.width = '100px';
|
||
div.style.height = '100px';
|
||
div.style.backgroundColor = '#3b82f6';
|
||
div.style.borderRadius = '8px';
|
||
div.style.display = 'flex';
|
||
div.style.justifyContent = 'center';
|
||
div.style.alignItems = 'center';
|
||
div.style.color = 'white';
|
||
div.style.fontWeight = 'bold';
|
||
div.style.transform = \`translate(\${x}px, \${y}px)\`;
|
||
div.innerHTML = \`
|
||
<spine-skeleton
|
||
identifier="owl\${i}"
|
||
atlas="/assets/owl-pma.atlas"
|
||
skeleton="/assets/owl-pro.json"
|
||
animation="idle"
|
||
drag
|
||
></spine-skeleton>
|
||
\`;
|
||
|
||
container.appendChild(div);
|
||
|
||
(async () => {
|
||
const widget = await div.lastElementChild.whenReady;
|
||
widget.state.setAnimation(1, "blink", true);
|
||
|
||
const control = widget.skeleton.findBone("control");
|
||
const tempVector = new spine.Vector3();
|
||
const mouseX = Smooth(0, 200);
|
||
const mouseY = Smooth(0, 200);
|
||
widget.afterUpdateWorldTransforms = () => {
|
||
updateControl(widget, control, mouseX, mouseY, tempVector);
|
||
}
|
||
})();
|
||
}
|
||
|
||
return container;
|
||
}
|
||
|
||
document.getElementById('section-owls').appendChild(createCircleOfDivs(8));
|
||
|
||
const checkbox = document.getElementById('owl-checkbox');
|
||
|
||
let limitOwl = true;
|
||
checkbox.addEventListener('change', () => limitOwl = checkbox.checked);
|
||
|
||
const updateControl = (widget, controlBone, mouseX, mouseY, tempVector) => {
|
||
controlBone.parent.worldToLocal(tempVector.set(
|
||
widget.pointerWorldX,
|
||
widget.pointerWorldY,
|
||
));
|
||
|
||
let x = tempVector.x;
|
||
let y = tempVector.y;
|
||
|
||
if (limitOwl) {
|
||
x = x / widget.overlay.canvas.width * 30;
|
||
y = y / widget.overlay.canvas.height * 30;
|
||
}
|
||
|
||
controlBone.x = controlBone.data.x + mouseX(x);
|
||
controlBone.y = controlBone.data.y + mouseY(y);
|
||
}
|
||
`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<script>
|
||
function createCircleOfDivs(numDivs = 8) {
|
||
const container = document.createElement('div');
|
||
container.style.position = 'relative';
|
||
container.style.width = '350px';
|
||
container.style.height = '350px';
|
||
container.style.backgroundColor = '#f3f4f6';
|
||
container.style.borderRadius = '50%';
|
||
container.style.display = 'flex';
|
||
container.style.justifyContent = 'center';
|
||
container.style.alignItems = 'center';
|
||
|
||
const radius = 140;
|
||
|
||
for (let i = 0; i < numDivs; i++) {
|
||
const angle = (i / numDivs) * 2 * Math.PI;
|
||
const x = Math.cos(angle) * radius;
|
||
const y = Math.sin(angle) * radius;
|
||
|
||
const div = document.createElement('div');
|
||
div.style.position = 'absolute';
|
||
div.style.width = '90px';
|
||
div.style.height = '90px';
|
||
div.style.backgroundColor = '#3b82f6';
|
||
div.style.borderRadius = '8px';
|
||
div.style.display = 'flex';
|
||
div.style.justifyContent = 'center';
|
||
div.style.alignItems = 'center';
|
||
div.style.color = 'white';
|
||
div.style.fontWeight = 'bold';
|
||
div.style.transform = `translate(${x}px, ${y}px)`;
|
||
div.innerHTML = `
|
||
<spine-skeleton
|
||
identifier="owl${i}"
|
||
atlas="/assets/owl-pma.atlas"
|
||
skeleton="/assets/owl-pro.json"
|
||
animation="idle"
|
||
drag
|
||
></spine-skeleton>
|
||
`;
|
||
|
||
container.appendChild(div);
|
||
|
||
(async () => {
|
||
const widget = await div.lastElementChild.whenReady;
|
||
widget.state.setAnimation(1, "blink", true);
|
||
|
||
const control = widget.skeleton.findBone("control");
|
||
const tempVector = new spine.Vector3();
|
||
const mouseX = Smooth(0, 200);
|
||
const mouseY = Smooth(0, 200);
|
||
widget.afterUpdateWorldTransforms = () => {
|
||
updateControl(widget, control, mouseX, mouseY, tempVector);
|
||
}
|
||
})();
|
||
}
|
||
|
||
return container;
|
||
}
|
||
|
||
document.getElementById('section-owls').appendChild(createCircleOfDivs(8));
|
||
|
||
const checkbox = document.getElementById('owl-checkbox');
|
||
|
||
let limitOwl = true;
|
||
checkbox.addEventListener('change', () => limitOwl = checkbox.checked);
|
||
|
||
const updateControl = (widget, controlBone, mouseX, mouseY, tempVector) => {
|
||
controlBone.parent.worldToLocal(tempVector.set(
|
||
widget.pointerWorldX,
|
||
widget.pointerWorldY,
|
||
));
|
||
|
||
let x = tempVector.x;
|
||
let y = tempVector.y;
|
||
|
||
if (limitOwl) {
|
||
x = x / widget.overlay.canvas.width * 30;
|
||
y = y / widget.overlay.canvas.height * 30;
|
||
}
|
||
|
||
controlBone.x = controlBone.data.x + mouseX(x);
|
||
controlBone.y = controlBone.data.y + mouseY(y);
|
||
}
|
||
</script>
|
||
|
||
|
||
<script>
|
||
function Smooth(f,t){var p=performance,b=p.now(),o=f,s=0,m,n,x,d,k=f;return function(v){n=p.now()-b;m=t*t/n/n;d=o-f;x=s*t+d+d;if(n>0)k=n<t?o-x/m/t*n+(x+x-d)/m-s*n-d:o;if(v!=void 0&&v!=o){f=k;b+=n;o=v;s=n>0&&n<t?s+3*x/m/t-(4*x-d-d)/m/n:0}return k}}
|
||
</script>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split" id="above-popup">
|
||
|
||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||
You can attach callbacks to your widget to react to pointer interactions. Just make it <code>interactive</code>.
|
||
<br>
|
||
<br>
|
||
You can attach a callback for interactions with the widget's <code>bounds</code> or with <code>slots</code>.
|
||
The available events are <code>down</code>, <code>up</code>, <code>enter</code>, <code>leave</code>, <code>move</code>, and <code>drag</code>.
|
||
<br>
|
||
<br>
|
||
In the following example, if the pointer enters the bounds, the jump animation is set, while the wave animation is set when the pointer leaves.
|
||
<br>
|
||
If you click on the <code>head-base</code> slot (the face), you can change the normal and dark tint with the colors selected in the two following selectors.
|
||
|
||
<div style="display: flex; align-items: center; justify-content: space-around">
|
||
<div>
|
||
<p>
|
||
Tint normal: <input type="color" id="color-picker" value="#ffffff" />
|
||
</p>
|
||
|
||
<p>
|
||
Tint black: <input type="color" id="dark-picker" value="#000000" />
|
||
</p>
|
||
</div>
|
||
|
||
<spine-skeleton
|
||
identifier="interactive0"
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.json"
|
||
skin="mario"
|
||
animation="emotes/wave"
|
||
interactive
|
||
style="width: 150px; height: 150px;"
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
identifier="interactive1"
|
||
atlas="/assets/chibi-stickers-pma.atlas"
|
||
skeleton="/assets/chibi-stickers.json"
|
||
skin="nate"
|
||
animation="emotes/wave"
|
||
interactive
|
||
style="width: 150px; height: 150px;"
|
||
></spine-skeleton>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const colorPicker = document.getElementById("color-picker");
|
||
const darkPicker = document.getElementById("dark-picker");
|
||
|
||
[0, 1].forEach(async (i) => {
|
||
const widget = await spine.getSkeleton(`interactive${i}`).whenReady;
|
||
|
||
widget.pointerEventCallback = (event) => {
|
||
if (event === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15;
|
||
if (event === "leave") widget.state.setAnimation(0, "emotes/wave", true).mixDuration = .25;
|
||
}
|
||
|
||
const tempColor = new spine.Color();
|
||
const slot = widget.skeleton.findSlot("head-base");
|
||
slot.darkColor = new spine.Color(0, 0, 0, 1);
|
||
widget.addPointerSlotEventCallback(slot, (slot, event) => {
|
||
if (event === "down") {
|
||
slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor));
|
||
slot.color.setFromColor(spine.Color.fromString(colorPicker.value, tempColor));
|
||
}
|
||
});
|
||
})
|
||
</script>
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
identifier="interactive0"
|
||
atlas="/assets/chibi-stickers-pma.atlas",
|
||
skeleton="/assets/chibi-stickers.json",
|
||
skin="mario"
|
||
animation="emotes/wave"
|
||
interactive
|
||
style="width: 150px; height: 150px;"
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton
|
||
identifier="interactive1"
|
||
atlas="/assets/chibi-stickers-pma.atlas",
|
||
skeleton="/assets/chibi-stickers.json",
|
||
skin="nate"
|
||
animation="emotes/wave"
|
||
interactive
|
||
style="width: 150px; height: 150px;"
|
||
></spine-skeleton>
|
||
|
||
[0, 1].forEach(async (i) => {
|
||
const widget = await spine.getSkeleton(\`interactive\${i}\`).whenReady;
|
||
|
||
widget.pointerEventCallback = (event) => {
|
||
if (event === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15;
|
||
if (event === "leave") widget.state.setAnimation(0, "emotes/wave", true).mixDuration = .25;
|
||
}
|
||
|
||
const tempColor = new spine.Color();
|
||
const slot = widget.skeleton.findSlot("head-base");
|
||
slot.darkColor = new spine.Color(0, 0, 0, 1);
|
||
widget.addPointerSlotEventCallback(slot, (slot, event) => {
|
||
if (event === "down") {
|
||
slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor));
|
||
slot.color.setFromColor(spine.Color.fromString(colorPicker.value, tempColor));
|
||
}
|
||
});
|
||
})`
|
||
);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
You can make your <code>HTMLElement</code>s follow slots. This feature is convenient when you need to generate dynamic text or content that integrates with your animation.
|
||
|
||
<p>
|
||
Invoke the `followSlot` function that takes as input:
|
||
<ol>
|
||
<li>The <code>Slot</code> or the slot name to follow</li>
|
||
<li>The <code>HTMLElement</code> that follows the slot</li>
|
||
<li>
|
||
An object with the following options:
|
||
<ul>
|
||
<li><code>followOpacity</code>: the element opacity is connected to the slot alpha</li>
|
||
<li><code>followScale</code>: the element scale is connected to the slot scale</li>
|
||
<li><code>followRotation</code>: the element rotation is connected to the slot rotation</li>
|
||
<li><code>followVisibility</code>: the element is shown/hidden depending if the slot contains an attachment or not</li>
|
||
<li><code>hideAttachment</code>: the slot attachment is hidden as if the element replaced the attachment</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
</p>
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
identifier="potty"
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="rain/rain-color" style="font-size: 50px; display: none;">A</div>
|
||
<div id="rain/rain-white" style="font-size: 50px; display: none;">B</div>
|
||
<div id="rain/rain-blue" style="font-size: 50px; display: none;">C</div>
|
||
<div id="rain/rain-green" style="font-size: 50px; display: none;">D</div>
|
||
|
||
<script>
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("potty").whenReady;
|
||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true });
|
||
})();
|
||
</script>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
identifier="potty"
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
></spine-skeleton>
|
||
|
||
<div id="rain/rain-color" style="font-size: 50px; display: none;">A</div>
|
||
<div id="rain/rain-white" style="font-size: 50px; display: none;">B</div>
|
||
<div id="rain/rain-blue" style="font-size: 50px; display: none;">C</div>
|
||
<div id="rain/rain-green" style="font-size: 50px; display: none;">D</div>
|
||
|
||
...
|
||
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("potty").whenReady;
|
||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true });
|
||
})();`);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
///////
|
||
-->
|
||
|
||
<!--
|
||
/////////////////////
|
||
// start section //
|
||
/////////////////////
|
||
-->
|
||
|
||
<div class="section vertical-split">
|
||
|
||
<div class="split-top split">
|
||
<div class="split-left">
|
||
`followSlot` works even with other spine widgets! It works even if you drag it :D
|
||
</div>
|
||
<div class="split-right">
|
||
<spine-skeleton
|
||
identifier="potty2"
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="rain"
|
||
drag
|
||
></spine-skeleton>
|
||
</div>
|
||
</div>
|
||
|
||
<spine-skeleton identifier="potty2-1" atlas="/assets/raptor-pma.atlas" skeleton="/assets/raptor-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
|
||
<spine-skeleton identifier="potty2-2" atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
|
||
<spine-skeleton identifier="potty2-3" atlas="/assets/celestial-circus-pma.atlas" skeleton="/assets/celestial-circus-pro.skel" animation="wings-and-feet" style="height:200px; width: 200px;"></spine-skeleton>
|
||
<spine-skeleton identifier="potty2-4" atlas="/assets/goblins-pma.atlas" skeleton="/assets/goblins-pro.skel" skin="goblingirl" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
|
||
|
||
<script>
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("potty2").whenReady;
|
||
widget.followSlot("rain/rain-color", spine.getSkeleton("potty2-1"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-white", spine.getSkeleton("potty2-2"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-blue", spine.getSkeleton("potty2-3"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-green", spine.getSkeleton("potty2-4"), { followVisibility: false, hideAttachment: true });
|
||
})();
|
||
</script>
|
||
|
||
|
||
<div class="split-bottom">
|
||
<pre><code id="code-display">
|
||
<script>escapeHTMLandInject(`
|
||
<spine-skeleton
|
||
identifier="potty2"
|
||
atlas="/assets/cloud-pot-pma.atlas"
|
||
skeleton="/assets/cloud-pot.skel"
|
||
animation="playing-in-the-rain"
|
||
drag
|
||
></spine-skeleton>
|
||
|
||
<spine-skeleton identifier="potty2-1" atlas="/assets/raptor-pma.atlas" skeleton="/assets/raptor-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
|
||
<spine-skeleton identifier="potty2-2" atlas="/assets/spineboy-pma.atlas" skeleton="/assets/spineboy-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
|
||
<spine-skeleton identifier="potty2-3" atlas="/assets/celestial-circus-pma.atlas" skeleton="/assets/celestial-circus-pro.skel" animation="wings-and-feet" style="height:200px; width: 200px;"></spine-skeleton>
|
||
<spine-skeleton identifier="potty2-4" atlas="/assets/goblins-pma.atlas" skeleton="/assets/goblins-pro.skel" skin="goblingirl" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
|
||
|
||
...
|
||
|
||
(async () => {
|
||
const widget = await spine.getSkeleton("potty2").whenReady;
|
||
widget.followSlot("rain/rain-color", spine.getSkeleton("potty2-1"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-white", spine.getSkeleton("potty2-2"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-blue", spine.getSkeleton("potty2-3"), { followVisibility: false, hideAttachment: true });
|
||
widget.followSlot("rain/rain-green", spine.getSkeleton("potty2-4"), { followVisibility: false, hideAttachment: true });
|
||
})();`);</script>
|
||
</code></pre>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!--
|
||
/////////////////////
|
||
// end section //
|
||
///////
|
||
-->
|
||
|
||
<script>
|
||
spine.SpineWebComponentOverlay.SHOW_FPS = true;
|
||
</script>
|
||
|
||
|
||
<script>
|
||
// Drag and resize utility for section 6
|
||
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;
|
||
}
|
||
}
|
||
|
||
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["pointer"] = "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(`section7-element`));
|
||
makeResizable(document.getElementById(`section7-element`));
|
||
</script>
|
||
</body>
|
||
</html> |