spine-runtimes/spine-ts/spine-webgl/example/webcomponent-tutorial.html

2093 lines
67 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>Webcomponent Tutorial</title>
<style>
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
font-size: 16px;
}
.section {
display: flex;
justify-content: center;
align-items: center;
color: white;
background-color: #3498db;
}
.split {
display: flex;
justify-content: center;
align-items: center;
}
.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 //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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="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>
<div class="split-bottom">
<pre><code id="code-display">
<script>
escapeHTMLandInject(`
<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>
</div>
<!--
/////////////////////
// end section //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// 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 //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split high-page" style="flex-direction: column;">
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
Moving the div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<!--
/////////////////////
// start section //
/////////////////////
-->
<div 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 //
/////////////////////
-->
<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>