Davide Tantillo a34b8273b3 Exposed parameters to set bounds.
Deeply changed how bounds work, especially for the fact that they are not auto recalculated anymore if the animation is changed (unless autoRecalculateBounds is set to true).
2024-10-04 17:38:23 +02:00

2063 lines
66 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../dist/iife/spine-webgl.js"></script>
<!-- <script src="./spine-webgl.min.js"></script> -->
<title>JS Library Showcase</title>
<style>
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
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;
}
.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 0 //
/////////////////////
-->
<div id="section0" 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>&lt;spine-widget&gt;</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 0 //
/////////////////////
-->
<!--
/////////////////////
// start section 1 //
/////////////////////
-->
<div id="section1" class="section vertical-split">
<div class="full-width">
<div class="split">
<div class="split-left" style="height: 300px;">
<spine-widget
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
fit="fill"
></spine-widget>
</div>
<div class="split-right">
You can change the fit mode of your Spine animation using the <code>fit</code> attribute.
<br>
<br>
This is <code>fit="fill"</code>. Default fit value is <code>fit="contain"</code>.
</div>
</div>
<div class="split">
<div class="split-left" style="height: 300px;">
<spine-widget
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
scale=".125"
fit="none"
></spine-widget>
</div>
<div class="split-right">
If you want to preserve the original scale, you can use the <code>fit="none"</code>.
In combination with that, you can use the <code>scale</code> attribute to choose you desired scale.
<br>
<br>
Other fit modes are <code>width</code>, <code>width</code>, <code>height</code>, <code>cover</code>,and <code>scaleDown</code>.
</div>
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
fit="fill"
></spine-widget>
<spine-widget
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
scale=".125"
fit="none"
></spine-widget>`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 1 //
/////////////////////
-->
<!--
/////////////////////
// start section 2 //
/////////////////////
-->
<div id="section2" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
height="200"
width="200"
></spine-widget>
</div>
<div class="split-right">
If you want to manually size the Spine widget, specify the attributes <code>width</code> and <code>height</code> in pixels (without the px unit).
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
height="200"
width="200"
></spine-widget>`)
</script>
</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">
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 4 //
/////////////////////
-->
<div id="section4" 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 4 //
/////////////////////
-->
<!--
/////////////////////
// start section 5 //
/////////////////////
-->
<div id="section5" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
<spine-widget
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-widget>
</div>
<div class="split-right">
You can virtually add a padding to the element container by using <code>pad-left</code>, <code>pad-right</code>, <code>pad-top</code>, <code>pad-bottom</code>.
<br>
<br>
As a value you can use a percentage of the width for left and right, and of the height for top and bottom.
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>escapeHTMLandInject(`
<spine-widget
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-widget>`);</script>
</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">
Give an <code>identifier</code> to your widget to get it by using the <code>spine.getSpineWidget</code> function.
You can easily access the <code>Skeleton</code> and the <code>AnimationState</code> of your character, and use them as if you were using <code>spine-webgl</code>.
<br>
<br>
If you change animation, you can ask the widget to scale the skeleton based on the new animation.
</div>
<div class="split-right">
<spine-widget
identifier="raptor"
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
></spine-widget>
</div>
</div>
<script>
(async () => {
const widget = spine.getSpineWidget("raptor");
const { state } = await widget.loadingPromise;
let isRoaring = false;
setInterval(() => {
const newAnimation = isRoaring ? "walk" : "roar";
state.setAnimation(0, newAnimation, true);
widget.recalculateBounds(); // scale the skeleton based on the new animation
isRoaring = !isRoaring;
}, 4000);
})();
</script>
<div class="split-bottom">
<pre><code id="code-display">
<script>escapeHTMLandInject(`
// access the spine widget
<spine-widget
identifier="raptor"
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
></spine-widget>
...
// using js, access the skeleton and the state asynchronously
(async () => {
const widget = spine.getSpineWidget("raptor");
const { state } = await widget.loadingPromise;
let isRoaring = false;
setInterval(() => {
const newAnimation = isRoaring ? "walk" : "roar";
state.setAnimation(0, newAnimation, true);
widget.recalculateBounds(); // scale the skeleton based on the new animation
isRoaring = !isRoaring;
}, 4000);
})();
`);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 6 //
/////////////////////
-->
<!--
/////////////////////
// start section 7 //
/////////////////////
-->
<div id="section7" class="section vertical-split">
<div class="split high-page" style="flex-direction: column;">
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
Moving the div will move the skeleton origin.
<br>
Resizing the div will resize the skeleton in <code>inside</code> mode, but not in <code>origin</code> mode.
<br>
Interact with the example above dragging the div and resizing it
</div>
<div class="split-nosize full-width" style="width: 80%; height: 50%; margin: 1em; padding: 1em;" id="section7-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 7 //
/////////////////////
-->
<!--
/////////////////////
// start section 8 //
/////////////////////
-->
<div id="section8" 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">
<spine-widget
atlas="assets/sack-pma.atlas"
skeleton="assets/sack-pro.skel"
animation="cape-follow-example"
debug
></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
></spine-widget>`
);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 8 //
/////////////////////
-->
<!--
/////////////////////
// start section 9 //
/////////////////////
-->
<div id="section9" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
<spine-widget
identifier="widget-loading"
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
spinner
></spine-widget>
</div>
<div class="split-right">
A loading spinner is shown during assets loading. Click the button below to simulate a 2 seconds loading:
<br>
<br>
<input type="button" value="Simulate reload" onclick="reloadWidget(this)">
<br>
<br>
If you do not want to show the loading spinner, set <code>spinner="false"</code>.
<br>
Click the button below to toggle the spinner.
<br>
<br>
<input type="button" value="Spinner ON" onclick="toggleSpinner(this)">
</div>
</div>
<script>
const widget = spine.getSpineWidget("widget-loading");
async function reloadWidget(element) {
element.disabled = true;
await widget.loadingPromise;
const skeleton = widget.skeleton;
widget.loading = true;
setTimeout(() => {
element.disabled = false;
widget.loading = false;
}, 1000)
}
function toggleSpinner(element) {
widget.loadingSpinner = !widget.loadingSpinner;
element.value = widget.loadingSpinner ? "Spinner ON" : "Spinner OFF";
}
</script>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
spinner
></spine-widget>`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 9 //
/////////////////////
-->
<!--
/////////////////////
// start section 10 //
/////////////////////
-->
<div id="section10" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
It's super easy to show your different skins and animations. Just make a table and use the <code>skin</code> and <code>animation</code> attributes.
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="idle"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dance"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dress-up"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="walk"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="idle"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dance"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dress-up"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="walk"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="idle"
skin="full-skins/girl"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dance"
skin="full-skins/girl"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dress-up"
skin="full-skins/girl"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="walk"
skin="full-skins/girl"
></spine-widget>
</div>
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="idle"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dance"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dress-up"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="walk"
skin="full-skins/girl-spring-dress"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="idle"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dance"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dress-up"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="walk"
skin="full-skins/girl-blue-cape"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="idle"
skin="full-skins/girl"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dance"
skin="full-skins/girl"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="dress-up"
skin="full-skins/girl"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/mix-and-match-pma.atlas"
skeleton="assets/mix-and-match-pro.skel"
animation="walk"
skin="full-skins/girl"
></spine-widget>
</div>
</div>`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 10 //
/////////////////////
-->
<!--
/////////////////////
// start section 11 //
/////////////////////
-->
<div id="section11" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
If you have many atlas pages, for example one for each skin, and you want to show only some of the skins,
pass to the <code>pages</code> the atlas pages you want to load as a comma concatenated list of indices.
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/wave"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/wave"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/wave"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/wave"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="nate"
pages="0,1,4,6"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/wave"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="erikari"
pages="0,1,4,6"
></spine-widget>
</div>
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/wave"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="movement/trot-left"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/idea"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
atlas="assets/chibi-stickers-pma.atlas"
skeleton="assets/chibi-stickers.skel"
animation="emotes/hooray"
skin="mario"
pages="0,1,4,6"
></spine-widget>
</div>
</div>`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 11 //
/////////////////////
-->
<!--
/////////////////////
// start section 12 //
/////////////////////
-->
<div id="section12" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
Let's do the same thing above, but programmatically!
Create two arrays, one for the skin and the other for the animations, and loop over them.
<br>
<br>
<code>spine.createSpineWidget</code> allows you to create a spine widget.
<br>
<br>
By default, assets are loaded immeaditely. You can postpone that by setting <code>manual-start="false"</code>.
Then it's your responsibility to call <code>start()</code> on the widget.
As usual, just wait on the <code>loadingPromise</code> to act on the skeleton or state.
</div>
<script>
const element = document.currentScript.parentElement;
const skins = ["soeren", "sinisa", "luke"];
const animations = ["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"];
const pages = [3, 7, 8];
skins.forEach((skin, i) => {
// create the table (one for each skin)
const grid = document.createElement("div");
grid.classList.add("skin-grid");
element.appendChild(grid);
animations.forEach(async (animation, j) => {
// craete the div where to place the widget (one for each animation)
const gridElement = document.createElement("div");
gridElement.classList.add("skin-grid-element");
grid.appendChild(gridElement);
// create the widget
const widgetSection = spine.createSpineWidget({
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.loadingPromise;
widgetSection.state.setAnimation(0, "emotes/angry", true);
}
})
})
</script>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
const element = document.currentScript.parentElement;
const skins = ["soeren", "sinisa", "luke"];
const animations = ["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"];
const pages = [3, 7, 8];
skins.forEach((skin, i) => {
// create the table (one for each skin)
const grid = document.createElement("div");
grid.classList.add("skin-grid");
element.appendChild(grid);
animations.forEach(async (animation, j) => {
// craete the div where to place the widget (one for each animation)
const gridElement = document.createElement("div");
gridElement.classList.add("skin-grid-element");
grid.appendChild(gridElement);
// create the widget
const widgetSection = spine.createSpineWidget({
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.loadingPromise;
widgetSection.state.setAnimation(0, "emotes/angry", true);
}
})
})
`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 12 //
/////////////////////
-->
<!--
/////////////////////
// start section 13 //
/////////////////////
-->
<div id="section13" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
When the widget (or the parent element) enters in the viewport, the callback <code>onScreenFunction</code> is invoked.
<br>
<br>
By default, the callback call the widget <code>start</code> the first time the widget enters the viewport.
That useful in combination with <code>manual-start="true</code> to load assets only when they are into the viewport.
<br>
The assets of the coin below are loaded only when the widget enters the viewport.
<br>
<br>
You can overwrite that behaviour. For example, the raptor below changes animation everytime the widget enters the viewport.
</div>
<div class="skin-grid">
<div class="skin-grid-element">
<spine-widget
atlas="assets/coin-pma.atlas"
skeleton="assets/coin-pro.skel"
animation="animation"
manual-start="true"
></spine-widget>
</div>
<div class="skin-grid-element">
<spine-widget
identifier="coin"
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
></spine-widget>
<script>
(async () => {
const coinWidget = spine.getSpineWidget("coin");
await coinWidget.loadingPromise;
let raptorWalking = true;
coinWidget.onScreenFunction = widget => {
raptorWalking = !raptorWalking;
widget.state.setAnimation(0, raptorWalking ? "walk" : "jump", true);
}
})();
</script>
</div>
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
identifier="coin"
atlas="assets/raptor-pma.atlas"
skeleton="assets/raptor-pro.skel"
animation="walk"
></spine-widget>
...
(async () => {
const coinWidget = spine.getSpineWidget("coin");
await coinWidget.loadingPromise;
let raptorWalking = true;
coinWidget.onScreenFunction = widget => {
raptorWalking = !raptorWalking;
widget.state.setAnimation(0, raptorWalking ? "walk" : "jump", true);
}
})();
`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 13 //
/////////////////////
-->
<!--
/////////////////////
// start section 14 //
/////////////////////
-->
<div id="section14" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
<spine-widget
identifier="dragon"
atlas="assets/dragon-pma.atlas"
skeleton="assets/dragon-ess.skel"
animation="flying"
pages=""
></spine-widget>
</div>
<div class="split-right">
If you want to load textures programmatically, you can just pass as pages to load an empty value liek this <code>pages=""</code>.
<br>
<br>
In this way the skeleton and the atlas are loaded, but not the textures.
<br>
Then you can loads the textures whenever you want.
<br>
<br>
<input type="button" value="Load page 0" onclick="loadPageDragon(0)">
<br>
<br>
<input type="button" value="Load page 1" onclick="loadPageDragon(1)">
<br>
<br>
<input type="button" value="Load page 2" onclick="loadPageDragon(2)">
<br>
<br>
<input type="button" value="Load page 3" onclick="loadPageDragon(3)">
<br>
<br>
<input type="button" value="Load page 4" onclick="loadPageDragon(4)">
</div>
</div>
<script>
const dragon = spine.getSpineWidget("dragon");
function loadPageDragon(pageIndex) {
if (!dragon.pages) {
dragon.pages = [];
}
if (!dragon.pages.includes(pageIndex)) {
dragon.pages.push(pageIndex);
dragon.loadTexturesInPagesAttribute(dragon.textureAtlas);
}
}
</script>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
identifier="dragon"
atlas="assets/dragon-pma.atlas"
skeleton="assets/dragon-ess.skel"
animation="flying"
pages=""
></spine-widget>
<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.getSpineWidget("dragon");
function loadPageDragon(pageIndex) {
if (!dragon.pages) {
dragon.pages = [];
}
if (!dragon.pages.includes(pageIndex)) {
dragon.pages.push(pageIndex);
dragon.loadTexturesInPagesAttribute(dragon.textureAtlas);
}
}`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 14 //
/////////////////////
-->
<!--
/////////////////////
// start section 15 //
/////////////////////
-->
<div id="section15" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
Widgets are not 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 it's your responsibility to skip the update/apply. You can use the <code>onScreen</code> property for convinience.
</div>
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
<spine-widget
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
</div>
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
<spine-widget
identifier="stretchyman"
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
</div>
<script>
const stretchyman = spine.getSpineWidget("stretchyman");
stretchyman.update = (delta, skeleton, state) => {
// skin logiv if widget is off screen
if (!stretchyman.onScreen) return;
delta = delta * 2;
state.update(delta);
skeleton.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform(spine.Physics.update);
};
</script>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<spine-widget
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
<spine-widget
identifier="stretchyman"
atlas="assets/stretchyman-pma.atlas"
skeleton="assets/stretchyman-pro.skel"
animation="sneak"
offscreen="pose"
></spine-widget>
...
const stretchyman = spine.getSpineWidget("stretchyman");
stretchyman.update = (canvas, delta, skeleton, state) => {
// skin logiv if widget is off screen
if (!stretchyman.onScreen) return;
delta = delta * 2;
state.update(delta);
skeleton.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform(spine.Physics.update);
};`)
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 15 //
/////////////////////
-->
<!--
/////////////////////
// start section 16 //
/////////////////////
-->
<div id="section16" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
If for some reason your skeleton bounds go outside the div,
you can use the <code>clip</code> property to clip everything 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;">
<spine-widget
identifier="tank2"
atlas="assets/tank-pma.atlas"
skeleton="assets/tank-pro.skel"
animation="drive"
></spine-widget>
</div>
<div class="split-left" style="width: 30%; box-sizing: border-box; height: 150px;">
<spine-widget
identifier="tank"
atlas="assets/tank-pma.atlas"
skeleton="assets/tank-pro.skel"
animation="drive"
fit="none"
clip="true"
></spine-widget>
</div>
<script>
(async () => {
const tank = spine.getSpineWidget("tank");
const tank2 = spine.getSpineWidget("tank2");
await tank.loadingPromise;
await tank2.loadingPromise;
tank.beforeUpdateWorldTransforms = (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-widget
identifier="tank2"
atlas="assets/tank-pma.atlas"
skeleton="assets/tank-pro.skel"
animation="drive"
></spine-widget>
</div>
<div class="split-left" style="width: 30%; box-sizing: border-box; height: 150px;">
<spine-widget
identifier="tank"
atlas="assets/tank-pma.atlas"
skeleton="assets/tank-pro.skel"
animation="drive"
fit="none"
clip="true"
></spine-widget>
</div>
...
(async () => {
const tank = spine.getSpineWidget("tank");
const tank2 = spine.getSpineWidget("tank2");
await tank.loadingPromise;
await tank2.loadingPromise;
// since we want the tank to overflow the div, we set fit to none
// then we "sync" the tank scale to the one of the tank above
tank.beforeUpdateWorldTransforms = (canvas, 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 16 //
/////////////////////
-->
<!--
/////////////////////
// start section 17 //
/////////////////////
-->
<div id="section17" class="section vertical-split">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
scale="3"
fit="none"
clip
></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
></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
></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
></spine-widget>
</div>
<div class="split-right">
<spine-widget
identifier="celeste"
atlas="assets/celestial-circus-pma.atlas"
skeleton="assets/celestial-circus-pro.skel"
clip
isdraggable
></spine-widget>
<script>
(async () => {
const celeste = spine.getSpineWidget("celeste");
await celeste.loadingPromise;
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-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
scale="3"
fit="none"
clip
></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
></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
></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
></spine-widget>
</div>
<div class="split-right">
<spine-widget
identifier="celeste"
atlas="assets/celestial-circus-pma.atlas"
skeleton="assets/celestial-circus-pro.skel"
animation="wings-and-feet"
clip
isdraggable
></spine-widget>
</div>
...
(async () => {
const celeste = spine.getSpineWidget("celeste");
await celeste.loadingPromise;
celeste.state.setAnimation(0, "swing", true);
})();`);
</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 17 //
/////////////////////
-->
<!--
/////////////////////
// start section 18 //
/////////////////////
-->
<div id="section18" 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>isdraggable</code> property to <code>true</code>.
</div>
<div class="split-right">
<spine-widget
atlas="assets/celestial-circus-pma.atlas"
skeleton="assets/celestial-circus-pro.skel"
animation="wings-and-feet"
isdraggable
></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"
isdraggable="true"
></spine-widget>`
);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// 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 //
/////////////////////
-->
<script>
spine.SpineWebComponentWidget.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["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(`section7-element`));
makeResizable(document.getElementById(`section7-element`));
</script>
</body>
</html>