mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
741 lines
22 KiB
HTML
741 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<script src="../dist/iife/spine-webgl.js"></script>
|
|
<!-- <script src="./spine-webgl.js"></script> -->
|
|
<title>JS Library Showcase</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
}
|
|
.section {
|
|
/* height: 100lvh; */
|
|
/* height: 800px; */
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: white;
|
|
background-color: #3498db;
|
|
}
|
|
.split {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
.full-width {
|
|
width: 100%;
|
|
}
|
|
.split-left, .split-right {
|
|
width: 50%;
|
|
min-height: 50%;
|
|
padding: 1rem;
|
|
margin: 1rem;
|
|
border: 1px solid salmon;
|
|
}
|
|
.split-nosize {
|
|
border: 1px solid salmon;
|
|
}
|
|
.split-size {
|
|
padding: 1rem;
|
|
margin: 1rem;
|
|
}
|
|
.navigation {
|
|
display: flex;
|
|
position: fixed;
|
|
left: 20px;
|
|
bottom: 20px;
|
|
transform: translateY(-50%);
|
|
}
|
|
.nav-btn {
|
|
display: block;
|
|
margin: 0px 5px;
|
|
padding: 10px;
|
|
background-color: rgba(255, 255, 255, 0.7);
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.vertical-split {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.high-page {
|
|
height: 600px;
|
|
}
|
|
|
|
.split-top {
|
|
width: 100%;
|
|
height: 600px;
|
|
}
|
|
|
|
.split-bottom {
|
|
width: 100%;
|
|
/* height: 600px; */
|
|
}
|
|
|
|
.split-bottom {
|
|
background-color: #1e1e1e;
|
|
color: #d4d4d4;
|
|
overflow: auto;
|
|
}
|
|
|
|
.split-bottom pre {
|
|
height: 100%;
|
|
margin: 0;
|
|
}
|
|
|
|
.split-bottom code {
|
|
font-family: 'Consolas', 'Courier New', monospace;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
display: block;
|
|
padding: 1rem;
|
|
}
|
|
</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 0 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!-- <div id="section0" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
aaa
|
|
</div>
|
|
<div class="split-right" id="section0-element">
|
|
<spine identifier="section0" createDiv="true" width="220" height="50"/>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
|
|
</code></pre>
|
|
</div>
|
|
</div> -->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 0 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 1 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
The <code><spine-widget></code> tag allows you to place your Spine animations into a web page.
|
|
<br>
|
|
<br>
|
|
By default, the animation bounds are calculated using the given animation, or the setup pose if no animation is provided.
|
|
<br>
|
|
The bounds is centered and scaled to fit the parent container.
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/spineboy-pma.atlas"
|
|
skeleton="assets/spineboy-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 1 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 2 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section1" class="section vertical-split">
|
|
|
|
<div class="full-width">
|
|
|
|
<div class="split">
|
|
<div class="split-left" style="height: 300px;">
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
fit="fill"
|
|
debug="true"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
You can change the fit mode of your Spine animation using the <code>fit</code> attribute.
|
|
<br>
|
|
<br>
|
|
This is <code>fit="fill"</code>. Default fit value is <code>fit="contain"</code>.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split">
|
|
<div class="split-left" style="height: 300px;">
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
scale=".125"
|
|
fit="none"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
If you want to preserve the original scale, you can use the <code>fit="none"</code>.
|
|
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
|
|
<br>
|
|
<br>
|
|
Other fit modes are <code>fitWidth</code>, <code>fitWidth</code>, <code>fitHeight</code>, <code>cover</code>,and <code>scaleDown</code>.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>
|
|
escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
fit="fill"
|
|
></spine-widget>
|
|
|
|
<spine-widget
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
scale=".125"
|
|
fit="none"
|
|
></spine-widget>`)
|
|
</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 2 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section2" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
Mode <code>origin</code> center the animation world origin with the center of the HTML element.
|
|
<br>
|
|
You are responsible to scale the skeleton using this mode.
|
|
<br>
|
|
<br>
|
|
Move the origin by a percentage of the div width and height by using <code>x-axis</code> and <code>y-axis</code> respectively.
|
|
</div>
|
|
<div class="split-right">
|
|
<spine-widget
|
|
atlas="assets/vine-pma.atlas"
|
|
skeleton="assets/vine-pro.skel"
|
|
animation="grow"
|
|
mode="origin"
|
|
scale=".5"
|
|
y-axis="-.5"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/vine-pma.atlas"
|
|
skeleton="assets/vine-pro.skel"
|
|
animation="grow"
|
|
mode="origin"
|
|
scale=".5"
|
|
y-axis="-.5"
|
|
></spine-widget>
|
|
`);</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section3" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
<spine-widget
|
|
atlas="assets/snowglobe-pma.atlas"
|
|
skeleton="assets/snowglobe-pro.skel"
|
|
animation="shake"
|
|
offset-x="100"
|
|
offset-y="-100"
|
|
></spine-widget>
|
|
</div>
|
|
<div class="split-right">
|
|
Use <code>offset-x</code> and <code>offset-y</code> to move you skeleton left or right by the pixel amount you specify.
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/snowglobe-pma.atlas"
|
|
skeleton="assets/snowglobe-pro.skel"
|
|
animation="shake"
|
|
offset-x="100"
|
|
offset-y="-100"
|
|
></spine-widget>
|
|
`);</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 3 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 4 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section4" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
Give an <code>identifier</code> to your widget to get it by using the <code>spine.getSpineWidget</code> function.
|
|
You can easily access the <code>Skeleton</code> and the <code>AnimationState</code> of your character, and use them as if you were using <code>spine-webgl</code>.
|
|
<br>
|
|
<br>
|
|
If you change animation, you can ask the widget to scale the skeleton based on the new animation.
|
|
</div>
|
|
<div class="split-right">
|
|
<spine-widget
|
|
identifier="raptor"
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(async () => {
|
|
const widget = spine.getSpineWidget("raptor");
|
|
const { state } = await widget.loadingPromise;
|
|
let isRoaring = false;
|
|
setInterval(() => {
|
|
const newAnimation = isRoaring ? "walk" : "roar";
|
|
state.setAnimation(0, newAnimation, true);
|
|
widget.recalculateBounds(); // scale the skeleton based on the new animation
|
|
isRoaring = !isRoaring;
|
|
}, 4000);
|
|
})();
|
|
</script>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
// access the spine widget
|
|
<spine-widget
|
|
identifier="raptor"
|
|
atlas="assets/raptor-pma.atlas"
|
|
skeleton="assets/raptor-pro.skel"
|
|
animation="walk"
|
|
></spine-widget>
|
|
|
|
...
|
|
|
|
// using js, access the skeleton and the state asynchronously
|
|
(async () => {
|
|
const widget = document.querySelector("spine-widget[identifier=raptor]");
|
|
const { state } = await widget.loadingPromise;
|
|
let isRoaring = false;
|
|
setInterval(() => {
|
|
const newAnimation = isRoaring ? "walk" : "roar";
|
|
state.setAnimation(0, newAnimation, true);
|
|
widget.recalculateBounds(); // scale the skeleton based on the new animation
|
|
isRoaring = !isRoaring;
|
|
}, 4000);
|
|
})();
|
|
|
|
`);</script>
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 4 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 6 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section6" class="section vertical-split">
|
|
|
|
<div class="full-width high-page" style="padding: 1em;">
|
|
<div class="split-nosize" style="padding: 1em; margin-bottom: 1em;">
|
|
Moving the div will move the skeleton origin. <br>
|
|
Resizing the div will resize the skeleton in <code>inside</code> mode, but not in <code>origin</code> mode.
|
|
</div>
|
|
<div class="split-nosize" style="width: 100%; height: 50%;" id="section6-element">
|
|
<spine-widget
|
|
atlas="assets/cloud-pot-pma.atlas"
|
|
skeleton="assets/cloud-pot.skel"
|
|
animation="playing-in-the-rain"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/cloud-pot-pma.atlas"
|
|
skeleton="assets/cloud-pot.skel"
|
|
animation="playing-in-the-rain"
|
|
></spine-widget>`
|
|
);</script>
|
|
</code></pre>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 6 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section7" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
You can view the skeleton world origin (green), the root bone position (red), and the bounds rectangle and center (blue) by setting <code>debug</code> to <code>true</code>.
|
|
</div>
|
|
<div class="split-right" id="section7-element">
|
|
<spine-widget
|
|
atlas="assets/sack-pma.atlas"
|
|
skeleton="assets/sack-pro.skel"
|
|
animation="cape-follow-example"
|
|
debug="true"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/sack-pma.atlas"
|
|
skeleton="assets/sack-pro.skel"
|
|
animation="cape-follow-example"
|
|
debug="true"
|
|
></spine-widget>`
|
|
);</script>
|
|
</code></pre>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 7 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<!--
|
|
/////////////////////
|
|
// start section 8 //
|
|
/////////////////////
|
|
-->
|
|
|
|
<div id="section8" class="section vertical-split">
|
|
|
|
<div class="split-top split">
|
|
<div class="split-left">
|
|
As a bonus item, you can move you skeleton around just by setting the <code>draggable</code> property to <code>true</code>.
|
|
</div>
|
|
<div class="split-right" id="section8-element">
|
|
<spine-widget
|
|
atlas="assets/celestial-circus-pma.atlas"
|
|
skeleton="assets/celestial-circus-pro.skel"
|
|
animation="wings-and-feet"
|
|
draggable="true"
|
|
></spine-widget>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="split-bottom">
|
|
<pre><code id="code-display">
|
|
<script>escapeHTMLandInject(`
|
|
<spine-widget
|
|
atlas="assets/celestial-circus-pma.atlas"
|
|
skeleton="assets/celestial-circus-pro.skel"
|
|
animation="wings-and-feet"
|
|
draggable="true"
|
|
></spine-widget>`
|
|
);</script>
|
|
</code></pre>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!--
|
|
/////////////////////
|
|
// end section 8 //
|
|
/////////////////////
|
|
-->
|
|
|
|
|
|
<!-- <div class="navigation">
|
|
<button class="nav-btn" onclick="scrollToSection('section1')">1</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section2')">2</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section3')">3</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section4')">4</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section5')">5</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section6')">6</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section7')">7</button>
|
|
<button class="nav-btn" onclick="scrollToSection('section8')">8</button>
|
|
</div> -->
|
|
|
|
<script>
|
|
function scrollToSection(id) {
|
|
document.getElementById(id).scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
|
|
let sections = document.querySelectorAll('.section');
|
|
let currentSection = 0;
|
|
|
|
// window.addEventListener('wheel', (e) => {
|
|
// if (e.deltaY > 0 && currentSection < sections.length - 1) {
|
|
// currentSection++;
|
|
// } else if (e.deltaY < 0 && currentSection > 0) {
|
|
// currentSection--;
|
|
// }
|
|
// sections[currentSection].scrollIntoView({ behavior: 'smooth' });
|
|
// });
|
|
</script>
|
|
|
|
<script>
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
// Drag utility
|
|
|
|
|
|
function makeDraggable(element) {
|
|
element.style["touch-action"] = "none";
|
|
|
|
let isDragging = false;
|
|
let startX, startY;
|
|
let originalX, originalY;
|
|
|
|
element.addEventListener('pointerdown', startDragging);
|
|
document.addEventListener('pointermove', drag);
|
|
document.addEventListener('pointerup', stopDragging);
|
|
|
|
function startDragging(e) {
|
|
if (e.target === element.querySelector('#resizeHandle')) return;
|
|
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
|
|
const translate = element.style.transform;
|
|
if (translate !== '') {
|
|
const translateValues = translate.match(/translate\(([^)]+)\)/)[1].split(', ');
|
|
originalX = parseFloat(translateValues[0]);
|
|
originalY = parseFloat(translateValues[1]);
|
|
} else {
|
|
originalX = 0;
|
|
originalY = 0;
|
|
}
|
|
}
|
|
|
|
function drag(e) {
|
|
if (!isDragging) return;
|
|
|
|
const deltaX = e.clientX - startX;
|
|
const deltaY = e.clientY - startY;
|
|
|
|
element.style.transform = `translate(${originalX + deltaX}px, ${originalY + deltaY}px)`;
|
|
}
|
|
|
|
function stopDragging(e) {
|
|
isDragging = false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
// Resize utility
|
|
|
|
function makeResizable(element) {
|
|
const resizeHandle = document.createElement('div');
|
|
element.appendChild(resizeHandle);
|
|
resizeHandle.id = "resizeHandle";
|
|
resizeHandle.style.width = "20%";
|
|
resizeHandle.style.height = "20%";
|
|
resizeHandle.style.bottom = "0";
|
|
resizeHandle.style.right = "0";
|
|
resizeHandle.style.position = "absolute";
|
|
resizeHandle.style["background-color"] = "#007bff";
|
|
resizeHandle.style["cursor"] = "se-resize";
|
|
|
|
element.style["position"] = "relative";
|
|
element.style["touch-action"] = "none";
|
|
|
|
let isResizing = false;
|
|
let startX, startY, startWidth, startHeight, startPaddingLeft, startPaddingRight, startPaddingTop, startPaddingBottom;
|
|
|
|
resizeHandle.addEventListener('pointerdown', initResize);
|
|
|
|
function initResize(e) {
|
|
isResizing = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
startWidth = element.offsetWidth;
|
|
startHeight = element.offsetHeight;
|
|
startPaddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
|
|
startPaddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
|
|
startPaddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
|
|
startPaddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
|
|
document.addEventListener('pointermove', resize);
|
|
document.addEventListener('pointerup', stopResize);
|
|
}
|
|
|
|
function resize(e) {
|
|
if (!isResizing) return;
|
|
const width = startWidth + (e.clientX - startX) - startPaddingLeft - startPaddingRight;
|
|
const height = startHeight + (e.clientY - startY) - startPaddingTop - startPaddingBottom;
|
|
element.style.width = width + 'px';
|
|
element.style.height = height + 'px';
|
|
}
|
|
|
|
function stopResize() {
|
|
isResizing = false;
|
|
document.removeEventListener('pointermove', resize);
|
|
document.removeEventListener('pointerup', stopResize);
|
|
}
|
|
}
|
|
|
|
makeDraggable(document.getElementById(`section6-element`));
|
|
makeResizable(document.getElementById(`section6-element`));
|
|
</script>
|
|
</body>
|
|
</html> |