Davide Tantillo 221e3f6624 web component
2024-09-26 14:22:59 +02:00

831 lines
24 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;
}
.split-left, .split-right {
width: 50%;
min-height: 50%;
padding: 1rem;
margin: 1rem;
border: 1px solid salmon;
}
.split-nosize {
/* padding: 1rem; */
/* margin: 1rem; */
border: 1px solid salmon;
}
.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;
}
.split-top {
width: 100%;
height: 600px;
}
.split-bottom {
width: 100%;
/* height: 600px; */
}
.split-top {
display: flex;
justify-content: center;
align-items: center;
}
.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>
</head>
<body>
<span id="fps" style="position: fixed; top: 0; left: 0">FPS</span>
<!--
/////////////////////
// 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" id="section1-element">
</div>
<div class="split-right">
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
const overlay = new spine.SpineCanvasOverlay();
overlay.addSkeleton(
{
atlasPath: "assets/spineboy-pma.atlas",
skeletonPath: "assets/spineboy-pro.skel",
animation: 'walk',
},
document.getElementById(`section1-element`),
);
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 1 //
/////////////////////
-->
<!--
/////////////////////
// start section 2 //
/////////////////////
-->
<div id="section2" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
Mode <code>origin</code> uses the HTML element top-left corner as origin for the skeleton. <br>
You are responsible to scale the skeleton using this mode. <br>
Move the origin by a percentage of the div width and height by using <code>xAxis</code> and <code>yAxis</code> respectively.
</div>
<div class="split-right" id="section2-element">
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
overlay.addSkeleton(
{
atlasPath: "assets/spineboy-pma.atlas",
skeletonPath: "assets/spineboy-pro.skel",
animation: 'run',
scale: .25,
},
{
element: document.getElementById(`section2-element`),
mode: 'origin',
xAxis: .5,
yAxis: 1,
},
);
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 2 //
/////////////////////
-->
<!--
/////////////////////
// start section 3 //
/////////////////////
-->
<div id="section3" class="section vertical-split">
<div class="split-top split">
<div class="split-left" id="section3-element">
</div>
<div class="split-right">
Use <code>offsetX</code> and <code>offsetY</code> to move you skeleton left or right by the pixel amount you specify.
This works for both mode <code>origin</code> and <code>inside</code>.
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
overlay.addSkeleton(
{
atlasPath: "assets/spineboy-pma.atlas",
skeletonPath: "assets/spineboy-pro.skel",
animation: 'run',
},
{
element: document.getElementById(`section3-element`),
mode: 'inside', // default
offsetX: 100,
offsetY: 50,
},
);
</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">
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>
If you change animation, you can ask to scale the skeleton based on the new animation.
</div>
<div class="split-right" id="section4-element">
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
// access the skeleton and the state asynchronously
const { skeleton, state } = await overlay.addSkeleton(
{
atlasPath: "assets/raptor-pma.atlas",
skeletonPath: "assets/raptor-pro.skel",
animation: 'walk',
},
document.getElementById(`section4-element`)
);
let isRoaring = false;
setInterval(() => {
const newAnimation = isRoaring ? "walk" : "roar";
state.setAnimation(0, newAnimation, true);
overlay.recalculateBounds(skeleton); // scale the skeleton based on the new animation
isRoaring = !isRoaring;
}, 4000);
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 4 //
/////////////////////
-->
<!--
/////////////////////
// start section 5 //
/////////////////////
-->
<div id="section5" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
You can also set a custom bounds to center a specific element or area of you animation in the div.
</div>
<div class="split-right" id="section5-element">
TODO
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 5 //
/////////////////////
-->
<!--
/////////////////////
// start section 6 //
/////////////////////
-->
<div id="section6" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
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-right" id="section6-element">
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
overlay.addSkeleton(
{
atlasPath: "assets/cloud-pot-pma.atlas",
skeletonPath: "assets/cloud-pot.skel",
animation: 'playing-in-the-rain',
},
document.getElementById(`section6-element`)
);
</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">
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
overlay.addSkeleton(
{
atlasPath: "assets/owl-pma.atlas",
skeletonPath: "assets/owl-pro.skel",
animation: 'idle',
},
{
element: document.getElementById(`section7-element`),
debug: true,
}
);
</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">
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
overlay.addSkeleton(
{
atlasPath: "assets/celestial-circus-pma.atlas",
skeletonPath: "assets/celestial-circus-pro.skel",
animation: 'wings-and-feet',
},
{
element: document.getElementById(`section8-element`),
draggable: true,
}
);
</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>
(async () => {
const overlay = new spine.SpineCanvasOverlay();
// overlay.addSkeleton(
// {
// atlasPath: "assets/spineboy-pma.atlas",
// skeletonPath: "assets/spineboy-pro.skel",
// // scale: 1,
// // animation: 'walk',
// },
// {
// mode: "origin",
// xAxis: 1,
// yAxis: 1,
// element: document.querySelectorAll(`#section1-element`)[0],
// debug: true
// }
// );
/////////////////////
// start section 1 //
/////////////////////
overlay.addSkeleton(
{
atlasPath: "assets/spineboy-pma.atlas",
skeletonPath: "assets/spineboy-pro.skel",
animation: 'walk',
},
[
{
identifier: `section0`,
debug: true,
}
]
);
/////////////////////
// end section 1 //
/////////////////////
// /////////////////////
// // start section 1 //
// /////////////////////
// overlay.addSkeleton(
// {
// atlasPath: "assets/spineboy-pma.atlas",
// skeletonPath: "assets/spineboy-pro.skel",
// animation: 'walk',
// },
// document.querySelectorAll(`#section1-element`),
// );
// /////////////////////
// // end section 1 //
// /////////////////////
// /////////////////////
// // start section 2 //
// /////////////////////
// overlay.addSkeleton(
// {
// atlasPath: "assets/spineboy-pma.atlas",
// skeletonPath: "assets/spineboy-pro.skel",
// animation: 'run',
// scale: .25,
// },
// {
// element: document.getElementById(`section2-element`),
// mode: 'origin',
// xAxis: .5,
// yAxis: 1,
// },
// );
// /////////////////////
// // end section 2 //
// /////////////////////
// /////////////////////
// // start section 3 //
// /////////////////////
// overlay.addSkeleton(
// {
// atlasPath: "assets/spineboy-pma.atlas",
// skeletonPath: "assets/spineboy-pro.skel",
// animation: 'jump',
// },
// {
// element: document.getElementById(`section3-element`),
// mode: 'inside', // default
// offsetX: 100,
// offsetY: 50,
// },
// );
// /////////////////////
// // end section 3 //
// /////////////////////
// /////////////////////
// // start section 4 //
// /////////////////////
// const { skeleton, state } = await overlay.addSkeleton(
// {
// atlasPath: "assets/raptor-pma.atlas",
// skeletonPath: "assets/raptor-pro.skel",
// animation: 'walk',
// },
// document.getElementById(`section4-element`)
// );
// let isRoaring = false;
// setInterval(() => {
// const newAnimation = isRoaring ? "walk" : "roar";
// state.setAnimation(0, newAnimation, true);
// overlay.recalculateBounds(skeleton);
// isRoaring = !isRoaring;
// }, 4000);
// /////////////////////
// // end section 4 //
// /////////////////////
// /////////////////////
// // start section 5 //
// /////////////////////
// // const { skeleton: skeleton5 } = await overlay.addSkeleton(
// // {
// // atlasPath: "assets/spineboy-pma.atlas",
// // skeletonPath: "assets/spineboy-pro.skel",
// // animation: 'walk',
// // },
// // document.getElementById(`section5-element`)
// // );
// // const bbAttachmentSlot = skeleton5.findSlot("head-bb");
// // const currentAttachment = bbAttachmentSlot.attachment;
// // skeleton5.setAttachment("head-bb", "head");
// // const bbAttachment = bbAttachmentSlot.attachment;
// // const computedVertices = [];
// // bbAttachment.computeWorldVertices(bbAttachmentSlot, 0, bbAttachment.worldVerticesLength, computedVertices, 0, 2);
// // const vertices = computedVertices;
// // let x = Infinity, maxX = -Infinity, y = Infinity, maxY = -Infinity;
// // for (let i = 0; i < vertices.length; i+=2) {
// // x = Math.min(vertices[i], x);
// // y = Math.min(vertices[i+1], y);
// // maxX = Math.max(vertices[i], maxX);
// // maxY = Math.max(vertices[i+1], maxY);
// // }
// // const width = maxX - x;
// // const height = maxY - y;
// // console.log({ x, y, width, height })
// // overlay.setBounds(skeleton5, { x, y, width, height })
// // bbAttachmentSlot.setAttachment(currentAttachment)
// /////////////////////
// // end section 5 //
// /////////////////////
// /////////////////////
// // start section 6 //
// /////////////////////
// overlay.addSkeleton(
// {
// atlasPath: "assets/cloud-pot-pma.atlas",
// skeletonPath: "assets/cloud-pot.skel",
// animation: 'playing-in-the-rain',
// },
// document.getElementById(`section6-element`)
// );
// /////////////////////
// // end section 6 //
// /////////////////////
// /////////////////////
// // start section 7 //
// /////////////////////
// overlay.addSkeleton(
// {
// atlasPath: "assets/owl-pma.atlas",
// skeletonPath: "assets/owl-pro.skel",
// animation: 'idle',
// },
// {
// element: document.getElementById(`section7-element`),
// debug: true,
// }
// );
// //////////////////////
// // end section 7 //
// //////////////////////
// /////////////////////
// // start section 8 //
// /////////////////////
// overlay.addSkeleton(
// {
// atlasPath: "assets/celestial-circus-pma.atlas",
// skeletonPath: "assets/celestial-circus-pro.skel",
// animation: 'wings-and-feet',
// },
// {
// element: document.getElementById(`section8-element`),
// draggable: true,
// debug: true,
// }
// );
// //////////////////////
// // end section 8 //
// //////////////////////
})();
</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>