Changes to make the widget more dynamic while changing attributes. See now webcomponent-gui.html.

This commit is contained in:
Davide Tantillo 2024-10-08 17:26:31 +02:00
parent a34b8273b3
commit e9a07bd6b9
5 changed files with 566 additions and 433 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

View File

@ -1,274 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../dist/iife/spine-webgl.js"></script>
<!-- <script src="./spine-webgl.min.js"></script> -->
<title>JS Library Showcase</title>
<style>
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
.section {
/* height: 100lvh; */
/* height: 800px; */
display: flex;
justify-content: center;
align-items: center;
color: white;
background-color: #3498db;
}
.split {
display: flex;
justify-content: center;
align-items: center;
}
.full-width {
width: 100%;
}
.split-left, .split-right {
width: 50%;
min-height: 50%;
padding: 1rem;
margin: 1rem;
border: 1px solid salmon;
}
.split-nosize {
border: 1px solid salmon;
}
.split-size {
padding: 1rem;
margin: 1rem;
}
.navigation {
display: flex;
position: fixed;
left: 20px;
bottom: 20px;
transform: translateY(-50%);
}
.nav-btn {
display: block;
margin: 0px 5px;
padding: 10px;
background-color: rgba(255, 255, 255, 0.7);
border: none;
cursor: pointer;
}
.vertical-split {
display: flex;
flex-direction: column;
}
.high-page {
height: 600px;
}
.split-top {
width: 100%;
height: 600px;
}
.split-bottom {
width: 100%;
/* height: 600px; */
}
.split-bottom {
background-color: #1e1e1e;
color: #d4d4d4;
overflow: auto;
}
.split-bottom pre {
height: 100%;
margin: 0;
}
.split-bottom code {
font-family: 'Consolas', 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
display: block;
padding: 1rem;
}
.skin-grid {
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: space-evenly;
padding: 20px;
box-sizing: border-box;
}
.skin-grid-element {
border: 1px solid #ccc;
width: 150px;
aspect-ratio: 3 / 3;
}
</style>
<script>
function escapeHTMLandInject(text) {
const escaped = text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
document.currentScript.parentElement.innerHTML = escaped;
}
</script>
</head>
<body>
<!--
/////////////////////
// start section 8 //
/////////////////////
-->
<div id="section1" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-top split">
<!-- <div class="split-left">
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
scale="3"
fit="none"
clip="true"
></spine-widget>
</div>
<div class="split-right">
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
scale="1.5"
fit="none"
clip="true"
></spine-widget>
</div>
<div class="split-right">
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
scale="1"
fit="none"
clip="true"
></spine-widget>
</div>
<div class="split-right">
<spine-widget
atlas="assets/cloud-pot-pma.atlas"
skeleton="assets/cloud-pot.skel"
animation="playing-in-the-rain"
scale="0.5"
fit="none"
clip="true"
></spine-widget>
</div> -->
<div class="split-right">
<spine-widget
identifier="celeste"
atlas="assets/celestial-circus-pma.atlas"
skeleton="assets/celestial-circus-pro.skel"
draggable="true"
animation="swing"
></spine-widget>
<script>
(async () => {
const celeste = spine.getSpineWidget("celeste");
await celeste.loadingPromise;
celeste.state.setAnimation(0, "swing", true);
})();
</script>
</div>
<div class="split-right">
<spine-widget
identifier="celeste"
atlas="assets/celestial-circus-pma.atlas"
skeleton="assets/celestial-circus-pro.skel"
animation="swing"
fit="contain"
debug="true"
clip="true"
draggable="true"
></spine-widget>
</div>
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
...`);
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 8 //
/////////////////////
-->
<!-- <div class="navigation">
<button class="nav-btn" onclick="scrollToSection('section1')">1</button>
<button class="nav-btn" onclick="scrollToSection('section2')">2</button>
<button class="nav-btn" onclick="scrollToSection('section3')">3</button>
<button class="nav-btn" onclick="scrollToSection('section4')">4</button>
<button class="nav-btn" onclick="scrollToSection('section5')">5</button>
<button class="nav-btn" onclick="scrollToSection('section6')">6</button>
<button class="nav-btn" onclick="scrollToSection('section7')">7</button>
<button class="nav-btn" onclick="scrollToSection('section8')">8</button>
</div> -->
<script>
// function scrollToSection(id) {
// document.getElementById(id).scrollIntoView({ behavior: 'smooth' });
// }
// let sections = document.querySelectorAll('.section');
// let currentSection = 0;
// window.addEventListener('wheel', (e) => {
// if (e.deltaY > 0 && currentSection < sections.length - 1) {
// currentSection++;
// } else if (e.deltaY < 0 && currentSection > 0) {
// currentSection--;
// }
// sections[currentSection].scrollIntoView({ behavior: 'smooth' });
// });
</script>
<script>
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// Drag utility
</script>
</body>
</html>

View File

@ -0,0 +1,352 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/lil-gui@0.19.2/dist/lil-gui.umd.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/lil-gui@0.19.2/dist/lil-gui.min.css" rel="stylesheet">
<script src="../dist/iife/spine-webgl.js"></script>
<title>Webcomponent GUI</title>
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background-color: #3498db;
}
.container {
display: flex;
height: 100vh;
}
.left-column {
flex: 1;
overflow-y: auto;
margin: 20px;
background: url("assets/checker.png")
}
.right-column {
width: 300px;
overflow-y: auto;
display: flex; /* Enables Flexbox layout */
justify-content: center; /* Horizontally centers content */
}
#lil {
flex: 1;
}
</style>
</head>
<body>
<div class="container">
<div class="left-column">
<spine-widget
identifier="boi"
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
auto-recalculate-bounds
debug
></spine-widget>
</div>
<div class="right-column">
<div id="lil"></div>
</div>
</div>
<script>
(async () => {
const boi = spine.getSpineWidget("boi");
let { skeleton } = await boi.loadingPromise;
const animations = skeleton.data.animations.map(({ name }) => name);
animations.push("none");
const skins = skeleton.data.skins.map(({ name }) => name);
const gui = new lil.GUI({ container: document.getElementById('lil'), autoPlace: false, width: "100%" });
const refillAnimations = () => {
const animations = skeleton.data.animations.map(({ name }) => name);
animations.push("none");
const animationController = getController("animation");
animationController.options(animations).setValue(animations[animations.length - 1]);
}
const refillSkins = () => {
const skins = skeleton.data.skins.map(({ name }) => name);
const skinController = getController("skin");
skinController.options(skins).setValue(skins[0]);
}
const refillBounds = () => {
getController("boundsX").setValue(boi.bounds.x);
getController("boundsY").setValue(boi.bounds.y);
getController("boundsWidth").setValue(boi.bounds.width);
getController("boundsHeight").setValue(boi.bounds.height);
}
const myObject = {
isDraggable: false,
debug: true,
clip: false,
animation: "none",
skin: "default",
padLeft: 0,
padRight: 0,
padBottom: 0,
padTop: 0,
customBounds: false,
autoRecalculateBounds: true,
fit: "contain",
mode: "inside",
scaleX: 1,
scaleY: 1,
xAxis: boi.xAxis,
yAxis: boi.yAxis,
offsetX: boi.offsetX,
offsetY: boi.offsetY,
skeletonPath: boi.skeletonPath,
atlasPath: boi.atlasPath,
boundsX: boi.bounds.x,
boundsY: boi.bounds.y,
boundsWidth: boi.bounds.width,
boundsHeight: boi.bounds.height,
async reload() {
const { atlas: atlasPath, skeleton: skeletonPath } = gui.save().folders.Assets.controllers;
boi.atlasPath = atlasPath;
boi.skeletonPath = skeletonPath;
boi.start();
await boi.loadingPromise;
skeleton = boi.skeleton;
refillAnimations();
refillSkins();
refillBounds();
},
};
const assetFolder = gui.addFolder( 'Assets' );
assetFolder.add( myObject, 'skeletonPath' ).name( 'skeleton' );
assetFolder.add( myObject, 'atlasPath' ).name( 'atlas' );
assetFolder.add( myObject, 'reload' ).name( 'Reload Widget' );
gui
.add(myObject, 'animation', animations)
.name( 'animation' )
.onChange(value => {
if (value === "none") {
boi.removeAttribute("animation")
} else {
boi.setAttribute("animation", value)
}
refillBounds();
});
gui
.add(myObject, 'skin', skins)
.name( 'skin' )
.onChange(value => {
if (value === "none") {
boi.removeAttribute("skin")
} else {
boi.setAttribute("skin", value)
}
refillBounds();
});
gui
.add(myObject, 'fit', ["fill", "width", "height", "contain", "cover", "none", "scaleDown"])
.name( 'fit' )
.onChange(value => {
boi.setAttribute("fit", value)
if (value === "none") {
getController("scaleX").enable();
getController("scaleY").enable();
} else {
getController("scaleX").disable();
getController("scaleY").disable();
}
});
gui
.add(myObject, 'mode', ["inside", "origin"])
.name( 'mode' )
.onChange(value => {
boi.setAttribute("mode", value)
if (value === "origin") {
getController("fit").disable();
disableFolder("Bounds");
disableFolder("Padding");
getController("scaleX").enable();
getController("scaleY").enable();
} else {
getController("fit").enable();
enableFolder("Bounds");
enableFolder("Padding");
if (myObject.fit !== "none") {
getController("scaleX").disable();
getController("scaleY").disable();
}
}
});
gui
.add(myObject, 'scaleX').min(-20).max(20).step(0.01)
.name( 'scale-x' )
.onChange(value => {
boi.skeleton.scaleX = value;
})
.disable();
gui
.add(myObject, 'scaleY').min(-20).max(20).step(0.01)
.name( 'scale-y' )
.onChange(value => {
boi.skeleton.scaleY = value;
})
.disable();
gui
.add(myObject, 'isDraggable')
.name( 'isdraggable' )
.onChange(value => {
if (value) boi.setAttribute("isdraggable", '')
else boi.removeAttribute("isdraggable");
});
gui
.add(myObject, 'debug')
.name( 'debug' )
.onChange(value => {
if (value) boi.setAttribute("debug", '')
else boi.removeAttribute("debug");
});
gui
.add(myObject, 'clip')
.name( 'clip' )
.onChange(value => {
if (value) boi.setAttribute("clip", '')
else boi.removeAttribute("clip");
});
gui
.add(myObject, 'xAxis').min(-1).max(1).step(0.01)
.name( 'x-axis' )
.onChange(value => {
boi.setAttribute("x-axis", value)
});
gui
.add(myObject, 'yAxis').min(-1).max(1).step(0.01)
.name( 'y-axis' )
.onChange(value => {
boi.setAttribute("y-axis", value)
});
gui
.add(myObject, 'offsetX').min(-500).max(500).step(0.1)
.name( 'offset-x' )
.onChange(value => {
boi.setAttribute("offset-x", value)
});
gui
.add(myObject, 'offsetY').min(-500).max(500).step(0.1)
.name( 'offset-y' )
.onChange(value => {
boi.setAttribute("offset-y", value)
});
const paddingFolder = gui.addFolder( 'Padding' );
paddingFolder
.add(myObject, 'padLeft').min(0).max(1).step(0.01)
.name( 'pad-left' )
.onChange(value => {
boi.setAttribute("pad-left", value)
});
paddingFolder
.add(myObject, 'padTop').min(0).max(1).step(0.01)
.name( 'pad-top' )
.onChange(value => {
boi.setAttribute("pad-top", value)
});
paddingFolder
.add(myObject, 'padRight').min(0).max(1).step(0.01)
.name( 'pad-right' )
.onChange(value => {
boi.setAttribute("pad-right", value)
});
paddingFolder
.add(myObject, 'padBottom').min(0).max(1).step(0.01)
.name( 'pad-bottom' )
.onChange(value => {
boi.setAttribute("pad-bottom", value)
});
const boundsFolder = gui.addFolder( 'Bounds' );
boundsFolder
.add(myObject, 'autoRecalculateBounds')
.name( 'auto-recalculate-bounds' )
.onChange(value => {
boi.setAttribute("auto-recalculate-bounds", value)
});
boundsFolder
.add(myObject, 'boundsX').min(-500).max(500).step(0.01)
.name( 'bounds-x' )
.onChange(value => {
boi.setAttribute("bounds-x", value)
});
boundsFolder
.add(myObject, 'boundsY').min(-500).max(500).step(0.01)
.name( 'bounds-y' )
.onChange(value => {
boi.setAttribute("bounds-y", value)
});
boundsFolder
.add(myObject, 'boundsWidth').min(0).max(500).step(0.01)
.name( 'bounds-width' )
.onChange(value => {
boi.setAttribute("bounds-width", value)
});
boundsFolder
.add(myObject, 'boundsHeight').min(0).max(500).step(0.01)
.name( 'bounds-height' )
.onChange(value => {
boi.setAttribute("bounds-height", value)
});
const controllers = gui.controllersRecursive();
const getController = (propertyName) => controllers.find(({ property }) => property === propertyName);
const folders = gui.foldersRecursive();
const getFolder = (folderName) => folders.find(({ _title }) => _title === folderName);
const disableFolder = (folderName) => getFolder(folderName).controllers.forEach(c => c.disable());
const enableFolder = (folderName) => getFolder(folderName).controllers.forEach(c => c.enable());
setInterval(() => {
if (myObject.fit === "none" || myObject.mode === "origin") return;
getController("scaleX").setValue(skeleton.scaleX);
getController("scaleY").setValue(skeleton.scaleY);
}, 100)
})();
</script>
</body>
</html>

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../dist/iife/spine-webgl.js"></script>
<!-- <script src="./spine-webgl.min.js"></script> -->
<title>JS Library Showcase</title>
<title>Webcomponent Tutorial</title>
<style>
body {
margin: 0;
@ -131,11 +131,11 @@
<!--
/////////////////////
// start section 0 //
// start section //
/////////////////////
-->
<div id="section0" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -172,17 +172,17 @@
<!--
/////////////////////
// end section 0 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 1 //
// start section //
/////////////////////
-->
<div id="section1" class="section vertical-split">
<div class="section vertical-split">
<div class="full-width">
@ -252,17 +252,17 @@
<!--
/////////////////////
// end section 1 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 2 //
// start section //
/////////////////////
-->
<div id="section2" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -270,12 +270,27 @@
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
height="200"
width="200"
height="150"
width="150"
></spine-widget>
<spine-widget
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-widget>
</div>
<div class="split-right">
If you want to manually size the Spine widget, specify the attributes <code>width</code> and <code>height</code> in pixels (without the px unit).
<br>
<br>
If you prefer you can style the component using the <code>style</code> attribute. There you have more styling options.
</div>
</div>
@ -284,13 +299,28 @@
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
height="200"
width="200"
></spine-widget>`)
<div>
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
height="150"
width="150"
></spine-widget>
<spine-widget
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-widget>
</div>`)
</script>
</code></pre>
</div>
@ -298,17 +328,17 @@
<!--
/////////////////////
// end section 2 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 3 //
// start section //
/////////////////////
-->
<div id="section3" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -350,17 +380,17 @@
<!--
/////////////////////
// end section 3 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 4 //
// start section //
/////////////////////
-->
<div id="section4" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -395,17 +425,17 @@
<!--
/////////////////////
// end section 4 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 5 //
// start section //
/////////////////////
-->
<div id="section5" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -448,17 +478,73 @@
<!--
/////////////////////
// end section 5 //
// 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 certain details of your animation.
<br>
<br>
The <code>bounds-x</code>, <code>bounds-y</code>, <code>bounds-width</code> and <code>bounds-height</code> allows to define custom bounds.
<br>
<br>
In this example we're zooming in into Celeste's face. You probably want to use <code>clip</code> in this case to avoid the skeleton overflow.
</div>
<div class="split-right">
<spine-widget
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-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"
bounds-x="-155"
bounds-y="650"
bounds-width="300"
bounds-height="350"
clip
></spine-widget>`);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 6 //
// start section //
/////////////////////
-->
<div id="section6" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -526,17 +612,17 @@
<!--
/////////////////////
// end section 6 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 7 //
// start section //
/////////////////////
-->
<div id="section7" class="section vertical-split">
<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 will move the skeleton origin.
@ -570,17 +656,17 @@
<!--
/////////////////////
// end section 7 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 8 //
// start section //
/////////////////////
-->
<div id="section8" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -613,7 +699,7 @@
<!--
/////////////////////
// end section 8 //
// end section //
/////////////////////
-->
@ -621,10 +707,10 @@
<!--
/////////////////////
// start section 9 //
// start section //
/////////////////////
-->
<div id="section9" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -686,16 +772,16 @@
</div>
<!--
/////////////////////
// end section 9 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 10 //
// start section //
/////////////////////
-->
<div id="section10" class="section vertical-split">
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -924,16 +1010,16 @@
</div>
<!--
/////////////////////
// end section 10 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 11 //
// start section //
/////////////////////
-->
<div id="section11" class="section vertical-split">
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1187,16 +1273,16 @@
</div>
<!--
/////////////////////
// end section 11 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 12 //
// start section //
/////////////////////
-->
<div id="section12" class="section vertical-split">
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1309,16 +1395,16 @@ skins.forEach((skin, i) => {
</div>
<!--
/////////////////////
// end section 12 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 13 //
// start section //
/////////////////////
-->
<div id="section13" class="section vertical-split">
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1402,16 +1488,16 @@ skins.forEach((skin, i) => {
</div>
<!--
/////////////////////
// end section 13 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 14 //
// start section //
/////////////////////
-->
<div id="section14" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -1497,16 +1583,16 @@ function loadPageDragon(pageIndex) {
</div>
<!--
/////////////////////
// end section 14 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 15 //
// start section //
/////////////////////
-->
<div id="section15" class="section vertical-split">
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1600,16 +1686,16 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 15 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 16 //
// start section //
/////////////////////
-->
<div id="section16" class="section vertical-split">
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1705,16 +1791,16 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 16 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 17 //
// start section //
/////////////////////
-->
<div id="section17" class="section vertical-split">
<div class="section vertical-split">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
More examples for <code>clip</code> attribute.
@ -1854,17 +1940,17 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 17 //
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section 18 //
// start section //
/////////////////////
-->
<div id="section18" class="section vertical-split">
<div class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -1897,63 +1983,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 18 //
/////////////////////
-->
<!--
/////////////////////
// start section 19 //
/////////////////////
-->
<div id="section19" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
You can customize the bounds, for example to focus on certain details of your animation.
<br>
<br>
The <code>bounds-x</code>, <code>bounds-y</code>, <code>bounds-width</code> and <code>bounds-height</code> allows to define custom bounds.
<br>
<br>
In this example we're zooming in into Celeste's face. You probably want to use <code>clip</code> in this case to avoid the skeleton overflow.
</div>
<div class="split-right">
<spine-widget
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-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"
bounds-x="-155"
bounds-y="650"
bounds-width="300"
bounds-height="350"
clip
></spine-widget>`);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 19 //
// end section //
/////////////////////
-->

View File

@ -65,7 +65,7 @@ interface Rectangle extends Point {
type BeforeAfterUpdateSpineWidgetFunction = (skeleton: Skeleton, state: AnimationState) => void;
type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void;
type OffScreenUpdateBehaviourType = "pause" | "update" | "pose";
export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose";
function isOffScreenUpdateBehaviourType (value: string | null): value is OffScreenUpdateBehaviourType {
return (
value === "pause" ||
@ -74,7 +74,7 @@ function isOffScreenUpdateBehaviourType (value: string | null): value is OffScre
);
}
type ModeType = "inside" | "origin";
export type ModeType = "inside" | "origin";
function isModeType (value: string | null): value is ModeType {
return (
value === "inside" ||
@ -82,7 +82,7 @@ function isModeType (value: string | null): value is ModeType {
);
}
type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown";
export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown";
function isFitType (value: string | null): value is FitType {
return (
value === "fill" ||
@ -95,7 +95,7 @@ function isFitType (value: string | null): value is FitType {
);
}
type AttributeTypes = "string" | "number" | "boolean" | "string-number" | "fitType" | "modeType" | "offScreenUpdateBehaviourType";
export type AttributeTypes = "string" | "number" | "boolean" | "string-number" | "fitType" | "modeType" | "offScreenUpdateBehaviourType";
// The properties that map to widget attributes
interface WidgetAttributes {
@ -183,7 +183,8 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
public skeletonPath?: string;
/**
* The scale when loading the skeleton data. Default: 1
* The scale passed to the Skeleton Loader. SkeletonData values will be scaled accordingly.
* Default: 1
* Connected to `scale` attribute.
*/
public scale = 1;
@ -196,6 +197,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
return this._animation;
}
public set animation (value: string | undefined) {
if (value === "") value = undefined;
this._animation = value;
this.initWidget();
}
@ -296,7 +298,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
* Use `setBounds` to set you desired bounds. Bounding Box might be useful to determine the bounds to be used.
* If the skeleton overflow the element container consider setting {@link clip} to `true`.
*/
public bounds: Rectangle = { x: 0, y: 0, width: 0, height: 0 };
public bounds: Rectangle = { x: 0, y: 0, width: -1, height: -1 };
/**
* The x of the bounds in Spine world coordinates
@ -329,6 +331,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
}
set boundsWidth(value: number) {
this.bounds.width = value;
if (value <= 0) this.initWidget(true);
}
/**
@ -340,6 +343,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
}
set boundsHeight(value: number) {
this.bounds.height = value;
if (value <= 0) this.initWidget(true);
}
/**
@ -575,7 +579,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
atlas: { propertyName: "atlasPath", type: "string" },
skeleton: { propertyName: "skeletonPath", type: "string" },
scale: { propertyName: "scale", type: "number" },
animation: { propertyName: "animation", type: "string" },
animation: { propertyName: "animation", type: "string", defaultValue: undefined },
skin: { propertyName: "skin", type: "string" },
width: { propertyName: "width", type: "number", defaultValue: -1 },
height: { propertyName: "height", type: "number", defaultValue: -1 },
@ -590,8 +594,8 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
"pad-bottom": { propertyName: "padBottom", type: "number" },
"bounds-x": { propertyName: "boundsX", type: "number" },
"bounds-y": { propertyName: "boundsY", type: "number" },
"bounds-width": { propertyName: "boundsWidth", type: "number" },
"bounds-height": { propertyName: "boundsHeight", type: "number" },
"bounds-width": { propertyName: "boundsWidth", type: "number", defaultValue: -1 },
"bounds-height": { propertyName: "boundsHeight", type: "number", defaultValue: -1 },
"auto-recalculate-bounds": { propertyName: "autoRecalculateBounds", type: "boolean" },
identifier: { propertyName: "identifier", type: "string" },
debug: { propertyName: "debug", type: "boolean" },
@ -615,7 +619,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
this.debugDragDiv = document.createElement("div");
this.debugDragDiv.style.position = "absolute";
this.debugDragDiv.style.backgroundColor = "rgba(0, 1, 1, 0.3)";
this.debugDragDiv.style.backgroundColor = "rgba(255, 0, 0, .3)";
this.debugDragDiv.style.setProperty("pointer-events", "none");
}
@ -650,28 +654,28 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
attributeChangedCallback (name: string, oldValue: string | null, newValue: string | null): void {
const { type, propertyName, defaultValue } = SpineWebComponentWidget.attributesDescription[name];
const val = SpineWebComponentWidget.castValue(type, newValue, defaultValue ?? this[propertyName]);
const val = SpineWebComponentWidget.castValue(type, newValue, defaultValue);
(this as any)[propertyName] = val;
return;
}
/**
* Starts the widget. Starting the widget means to load the assets currently set into
* {@link atlasPath} and {@link skeletonPath}.
* {@link atlasPath} and {@link skeletonPath}. If start is invoked when the widget is already started,
* the skeleton, state, skin and animation will be reset.
*/
public start () {
if (this.started) {
console.warn("If you want to start again the widget, first reset it");
this.skeleton = undefined;
this.state = undefined;
this._skin = undefined;
this._animation = undefined;
this.bounds.width = -1;
this.bounds.height = -1;
}
this.started = true;
if (!this.loadingPromise) {
this.loadingPromise = customElements.whenDefined("spine-overlay").then(() => this.loadSkeleton());
}
this.loadingPromise.then(() => {
this.loading = false;
});
this.loadingPromise = customElements.whenDefined("spine-overlay").then(() => this.loadSkeleton());
}
/**
@ -700,7 +704,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
* @returns The `HTMLElement` where the widget is hosted.
*/
public getHTMLElementReference (): HTMLElement {
return this.width <= 0 || this.width <= 0
return (this.width <= 0 || this.width <= 0) && !this.getAttribute("style")
? this.parentElement!
: this;
}
@ -769,16 +773,24 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
// skeleton.scaleY = this.currentScaleDpi;
// the bounds are calculated the first time, if no custom bound is provided
this.initWidget(this.bounds.width === 0 || this.bounds.height === 0);
this.initWidget(this.bounds.width <= 0 || this.bounds.height <= 0);
this.loading = false;
return this;
}
private initWidget (forceRecalculate = false) {
const { skeleton, state, animation, skin } = this;
if (skin) skeleton?.setSkinByName(skin);
if (animation) state?.setAnimation(0, animation, true);
if (skin) {
skeleton?.setSkinByName(skin);
skeleton?.setSlotsToSetupPose();
}
if (animation) {
state?.setAnimation(0, animation, true);
} else {
state?.setEmptyAnimation(0);
}
if (forceRecalculate || this.autoRecalculateBounds) this.recalculateBounds();
}
@ -800,8 +812,11 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
display: inline-block;
width: ${width};
height: ${height};
// background-color: red;
}
:host(.debug-background-color) {
background-color: rgba(255, 0, 0, 0.3);
}
</style>
`;
}
@ -848,13 +863,15 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
skeleton.updateWorldTransform(Physics.update);
skeleton.getBounds(offset, size, tempArray, renderer.skeletonRenderer.getSkeletonClipping());
if (!isNaN(offset.x) && !isNaN(offset.y) && !isNaN(size.x) && !isNaN(size.y)) {
if (!isNaN(offset.x) && !isNaN(offset.y) && !isNaN(size.x) && !isNaN(size.y) &&
!isNaN(minX) && !isNaN(minY) && !isNaN(maxX) && !isNaN(maxY)) {
minX = Math.min(offset.x, minX);
maxX = Math.max(offset.x + size.x, maxX);
minY = Math.min(offset.y, minY);
maxY = Math.max(offset.y + size.y, maxY);
} else
console.error("Animation bounds are invalid: " + animation.name);
} else {
return { x: 0, y: 0, width: -1, height: -1 };
}
}
return {
@ -1161,7 +1178,8 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
const { skeleton, bounds, mode, debug, offsetX, offsetY, xAxis, yAxis, dragX, dragY, fit, loadingSpinner, onScreen, loading, clip, isDraggable } = widget;
if ((!onScreen && dragX === 0 && dragY === 0)) return;
const divBounds = widget.getHTMLElementReference().getBoundingClientRect();
const elementRef = widget.getHTMLElementReference();
const divBounds = elementRef.getBoundingClientRect();
// need to use left and top, because x and y are not available on older browser
divBounds.x = divBounds.left + this.overflowLeftSize;
divBounds.y = divBounds.top + this.overflowTopSize;
@ -1203,6 +1221,7 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
if (skeleton) {
if (mode === "inside") {
let { x: ax, y: ay, width: aw, height: ah } = bounds;
if (aw <= 0 || ah <= 0) return;
// scale ratio
const scaleWidth = divWidthWorld / aw;
@ -1289,6 +1308,16 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
widget.dragBoundsRectangle.x += divBounds.x;
widget.dragBoundsRectangle.y += divBounds.y;
}
if (!widget.debugDragDiv.isConnected) document.body.appendChild(widget.debugDragDiv);
widget.debugDragDiv.style.left = `${widget.dragBoundsRectangle.x - this.overflowLeftSize}px`;
widget.debugDragDiv.style.top = `${widget.dragBoundsRectangle.y - this.overflowTopSize}px`;
widget.debugDragDiv.style.width = `${widget.dragBoundsRectangle.width}px`;
widget.debugDragDiv.style.height = `${widget.dragBoundsRectangle.height}px`;
if (!debug && widget.debugDragDiv.isConnected) widget.debugDragDiv.remove();
} else {
if (widget.debugDragDiv.isConnected) widget.debugDragDiv.remove();
}
// drawing debug stuff
@ -1318,16 +1347,12 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
// show line from origin to bounds center
renderer.line(originX, originY, bbCenterX, bbCenterY, green);
if (!widget.debugDragDiv.isConnected) document.body.appendChild(widget.debugDragDiv);
widget.debugDragDiv.style.left = `${widget.dragBoundsRectangle.x - this.overflowLeftSize}px`;
widget.debugDragDiv.style.top = `${widget.dragBoundsRectangle.y - this.overflowTopSize}px`;
widget.debugDragDiv.style.width = `${widget.dragBoundsRectangle.width}px`;
widget.debugDragDiv.style.height = `${widget.dragBoundsRectangle.height}px`;
if (elementRef === widget) widget.classList.add("debug-background-color");
} else {
if (elementRef === widget) widget.classList.remove("debug-background-color");
}
if (clip) clipToBoundEnd();
}
});