<script src="/files/spine-widget/jszip.min.js"></script>
<script src="/files/spine-player/4.2/spine-webcomponents.min.js"></script>
<style>
.example {
	display: flex;
	gap: 1px;
	justify-content: center;
	margin-bottom: 1.1em;
}
.h-300 {
	height: 300px;
}
.h-200 {
	height: 200px;
}
.flex-1 {
	flex: 1;
}
.border {
	border: 1px solid black;
}
.flex-column {
	flex-direction: column;
}
</style>
!!

# Spine Web Components
The `spine-webcomponents` package provides web components (custom HTML elements) for embedding Spine animations directly into a web page.

<div class="example h-300">
	<div style="flex: 1; border: 1px solid black;">
		<spine-skeleton
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
		></spine-skeleton>
	</div>
	<div style="flex: 1; border: 1px solid black;">
		<spine-skeleton
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation="walk"
		></spine-skeleton>
	</div>
	<div style="flex: 1; border: 1px solid black;">
		<spine-skeleton
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation="run"
		></spine-skeleton>
	</div>
</div>
!!

When the <code>&lt;spine-skeleton&gt;</code> tag is added to an HTML page, the library creates a shared WebGL canvas overlay where multiple Spine skeletons are rendered. This design overcomes browser limitations on the number of WebGL contexts.

Unlike the [Spine player](/spine-player), the web components do not include a built-in UI for controlling playback. Instead, they expose a wide range of attributes that enable fine-grained configuration directly through HTML.

## Exporting
Spine web components use the same export format as the [Spine player](/spine-player#Exporting-for-the-player). In addition, it supports using multiple skeletons in the same JSON file.

## Set up
Adding a <code>&lt;spine-skeleton&gt;</code> web component to a website involves only a few straightforward steps, outlined below.

### Adding the JavaScript
The `spine-webcomponents` package includes the JavaScript file `spine-webcomponents.js`, which defines the two HTML custom elements <code>&lt;spine-skeleton&gt;</code> and <code>&lt;spine-overlay&gt;</code>, along with a set of related utility functions.

```
<script src="https://unpkg.com/@esotericsoftware/spine-webcomponents@4.2.*/dist/iife/spine-webcomponents.js"></script>
```

In the above example, the the file is loaded from [UNPKG](https://unpkg.com/), a fast NPM CDN. The URL contains a version number (`4.2`) which [must match](/spine-versioning#Synchronizing-versions) the Spine editor version used to export the skeleton. The asterisk (`*`) for the patch version ensures the latest JavaScript code for the `major.minor` version.

Use the `.min.js` file extension for a minified file from the UNPKG CDN:

```
<script src="https://unpkg.com/@esotericsoftware/spine-webcomponents@4.2.*/dist/iife/spine-webcomponents.min.js"></script>
```

Alternatively, the file can be self-hosted by downloading it from UNPKG or by building it from the sources available on the [GitHub repository](/git/runtimes/spine-ts). The repository also includes instructions for using the `spine-webcomponents` with NPM or Yarn.


### Using a spine-skeleton

After importing the JavaScript file, the web component can be used directly in HTML without additional JavaScript:

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
></spine-skeleton>
```

The <code>&lt;spine-skeleton&gt;</code> element loads the skeleton data from `/files/spineboy/export/spineboy-pro.skel` and the atlas from `/files/spineboy/export/spineboy.atlas`. The atlas references an image file (`spineboy.png`), which is loaded relative to the `.atlas` file, from `/files/spineboy/export/spineboy.png`.

A <code>&lt;spine-overlay&gt;</code> element is automatically added at the bottom of the DOM. This component creates a transparent WebGL canvas that spans the entire page and is used to render all <code>&lt;spine-skeleton&gt;</code> components in the correct positions within their parent containers. Most users don't need to be concerned with the overlay.

The web component renders the skeleton scaled to fit its parent element.

<div class="example">
	<table style="width: 50%;">
		<tr>
			<td>1</td>
			<td>2<spine-skeleton atlas="/files/spine-widget/assets/spineboy-pma.atlas" skeleton="/files/spine-widget/assets/spineboy-pro.skel" start-when-visible spinner></spine-skeleton></td>
		</tr>
		<tr>
			<td>3<spine-skeleton atlas="/files/spine-widget/assets/spineboy-pma.atlas" skeleton="/files/spine-widget/assets/spineboy-pro.skel" start-when-visible spinner></spine-skeleton></td>
			<td>4</td>
		</tr>
	</table>
</div>
!!

```html
<table>
	<tr>
		<td>1</td>
		<td>2
			<spine-skeleton
				atlas="/files/spine-widget/assets/spineboy-pma.atlas"
				skeleton="/files/spine-widget/assets/spineboy-pro.skel">
			</spine-skeleton>
	  </td>
	</tr>
	<tr>
		<td>3
			<spine-skeleton
				atlas="/files/spine-widget/assets/spineboy-pma.atlas"
				skeleton="/files/spine-widget/assets/spineboy-pro.skel">
			</spine-skeleton>
		</td>
		<td>4</td>
	</tr>
</table>
```

## Configuration
The <code>&lt;spine-skeleton&gt;</code> element provides many configuration attributes, allowing it to be tailored to specific requirements.

### JSON, binary, and atlas URL
The two mandatory attributes, `skeleton` and `atlas`, define the source paths for the skeleton `.json` or binary `.skel` file and the `.atlas` file, respectively. These paths can be either relative or absolute URLs.

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel">
</spine-skeleton>

<spine-skeleton
	atlas="https://esotericsoftware.com/assets/spineboy-pma.atlas"
	skeleton="https://esotericsoftware.com/assets/spineboy-pro.skel">
</spine-skeleton>
```

> When using absolute URLs to another domain, it is possible that web browsers won't be able to load the assets. This can be solved by enabling [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) on the server that hosts the assets.

### Embedding data
Instead of loading data from URLs, the `.json`/`.skel`, `.atlas`, and `.png` files can be embedded directly using the `raw-data` attribute. This attribute accepts a stringified JSON object where keys are asset names and values are their Base64-encoded contents. The `skeleton` and `atlas` attributes should then reference the corresponding asset names used in this object. The `raw-data` attribute is used to enable this setup.

<div class="example h-200">
	<spine-skeleton
		atlas="/assets/inline.atlas"
		skeleton="/assets/inline.skel"
		animation="animation"
		raw-data='{
			"/assets/inline.atlas":"aW5saW5lLnBuZwpzaXplOjE2LDE2CmZpbHRlcjpMaW5lYXIsTGluZWFyCnBtYTp0cnVlCmRvdApib3VuZHM6MCwwLDEsMQo=",
			"/assets/inline.skel":"/B8S/IqaXgYHNC4yLjM5wkgAAMJIAABCyAAAQsgAAELIAAAAAQRkb3QCBXJvb3QAAAAAAAAAAAAAAAA/gAAAP4AAAAAAAAAAAAAAAAAAAAAABGRvdAAAAAAAAAAAAAAAAABCyAAAQsgAAAAAAAAAAAAAAAAAAAAAAQRkb3QB//////////8BAAAAAAABAAEBACWwfdcAAAAAP4AAAD+AAAA/gAAAP4AAAAAAAQphbmltYXRpb24BAQABAQMAAAAAAP////8/gAAA/wAA/wBAAAAA/////wAAAAAAAAAAAA==",
			"/assets/inline.png":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRF////p8QbyAAAAApJREFUeJxjZAAAAAQAAiFkrWoAAAAASUVORK5CYII="
		}'
	></spine-skeleton>
</div>
!!

```html
<spine-skeleton
	atlas="/assets/inline.atlas"
	skeleton="/assets/inline.skel"
	animation="animation"
	raw-data='{
		"/assets/inline.atlas":"aW5saW5lLnBuZwpzaXplOjE2LDE2CmZpbHRlcjpMaW5lYXIsTGluZWFyCnBtYTp0cnVlCmRvdApib3VuZHM6MCwwLDEsMQo=",
		"/assets/inline.skel":"/B8S/IqaXgYHNC4yLjM5wkgAAMJIAABCyAAAQsgAAELIAAAAAQRkb3QCBXJvb3QAAAAAAAAAAAAAAAA/gAAAP4AAAAAAAAAAAAAAAAAAAAAABGRvdAAAAAAAAAAAAAAAAABCyAAAQsgAAAAAAAAAAAAAAAAAAAAAAQRkb3QB//////////8BAAAAAAABAAEBACWwfdcAAAAAP4AAAD+AAAA/gAAAP4AAAAAAAQphbmltYXRpb24BAQABAQMAAAAAAP////8/gAAA/wAA/wBAAAAA/////wAAAAAAAAAAAA==",
		"/assets/inline.png":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRF////p8QbyAAAAApJREFUeJxjZAAAAAQAAiFkrWoAAAAASUVORK5CYII="
	}'
></spine-skeleton>
```

### Style, width, height
By default, the web component renders the skeleton to fill its parent's size, but its actual dimensions are zero width and height. To manually set the size of the web component, use the standard `style` or `class` attributes. These attributes can be used to apply any desired styles.

<div class="example">
	<style>
		.custom-class {
			width: 150px;
			height: 150px;
			border: 1px solid green;
			border-radius: 10px;
			box-shadow: -5px 5px 3px rgba(0, 255, 0, 0.3);
			margin-right: 10px;
		}
	</style>
	<spine-skeleton
		atlas="/files/spine-widget/assets/spineboy-pma.atlas"
		skeleton="/files/spine-widget/assets/spineboy-pro.skel"
		animation="walk"
		class="custom-class"
		start-when-visible spinner
	></spine-skeleton>
	<spine-skeleton
		atlas="/files/spine-widget/assets/spineboy-pma.atlas"
		skeleton="/files/spine-widget/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);
		"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

```html
<style>
	.custom-class {
		width: 150px;
		height: 150px;
		border: 1px solid green;
		border-radius: 10px;
		box-shadow: -5px 5px 3px rgba(0, 255, 0, 0.3);
		margin-right: 10px;
	}
</style>

<spine-skeleton
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation="walk"
	class="custom-class"
></spine-skeleton>

<spine-skeleton
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/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-skeleton>
```

### JSON skeleton key
To minimize requests for resources, multiple skeletons can be embedded in a single JSON file. When using such JSON, specify which skeleton to display by setting the `json-skeleton-key` attribute on the web component. The `spine-webcomponents` asset manager efficiently loads each asset only once, even if used on the page multiple times.

<div class="example h-200">
	<spine-skeleton style="flex: 1; height: 200px;"
		atlas="/files/spine-widget/assets/atlas2.atlas"
		skeleton="/files/spine-widget/assets/demos.json"
		json-skeleton-key="armorgirl"
		animation="animation"
		start-when-visible spinner
	></spine-skeleton>
	<spine-skeleton style="flex: 1; height: 200px;"
		atlas="/files/spine-widget/assets/atlas2.atlas"
		skeleton="/files/spine-widget/assets/demos.json"
		json-skeleton-key="greengirl"
		animation="animation"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/atlas2.atlas"
	skeleton="/files/spine-widget/assets/demos.json"
	json-skeleton-key="armorgirl"
	animation="animation"
></spine-skeleton>

<spine-skeleton
	atlas="/files/spine-widget/assets/atlas2.atlas"
	skeleton="/files/spine-widget/assets/demos.json"
	json-skeleton-key="greengirl"
	animation="animation"
></spine-skeleton>
```

### Animation
By default, the web component will show the setup pose. An animation can be set using the `animation` attribute:

<div class="example h-200">
	<spine-skeleton
		atlas="/files/spine-widget/assets/spineboy-pma.atlas"
		skeleton="/files/spine-widget/assets/spineboy-pro.skel"
		animation="walk"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation="walk"
></spine-skeleton>
```

### Default skin
By default, the web component will use the default skin of the skeleton, which only has attachments that are not in a skin in the Spine editor. The active skin can be set explicitly in the configuration via the `skin` property:

<div class="example h-200">
	<spine-skeleton
		atlas="/files/spine-widget/assets/mix-and-match-pma.atlas"
		skeleton="/files/spine-widget/assets/mix-and-match-pro.skel"
		animation="dance"
		skin="full-skins/girl-spring-dress"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/mix-and-match-pma.atlas"
	skeleton="/files/spine-widget/assets/mix-and-match-pro.skel"
	animation="dance"
	skin="full-skins/girl-spring-dress"
></spine-skeleton>
```

`skin` accepts a comma-separated list of skin names. The skins will be combined into a new one, in the order provided. If multiple skins affect the same slot, the last one in the list takes precedence.

<div class="example h-200">
	<spine-skeleton
		atlas="/files/spine-widget/assets/mix-and-match-pma.atlas"
		skeleton="/files/spine-widget/assets/mix-and-match-pro.skel"
		animation="dance"
		skin="nose/short,skin-base,eyes/violet,hair/brown,clothes/hoodie-orange,legs/pants-jeans,accessories/bag,accessories/hat-red-yellow,eyelids/girly"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/mix-and-match-pma.atlas"
	skeleton="/files/spine-widget/assets/mix-and-match-pro.skel"
	animation="dance"
	skin="nose/short,skin-base,eyes/violet,hair/brown,clothes/hoodie-orange,legs/pants-jeans,accessories/bag,accessories/hat-red-yellow,eyelids/girly"
></spine-skeleton>
```

### Fit mode
The web component tries to fit the skeleton animation within its container element depending on the `fit` attribute. Here are some examples:

<div class="example" style="flex-direction: column;">
	<div style="flex: 1; display: flex; border: 1px solid black;">
		<div style="flex: 1; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			fit="contain"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.5;  border: 1px solid black;"><code>contain</code>: as large as possible while still containing the skeleton entirely within the container element (Default)</div>
	</div>
	<div style="flex: 1; display: flex; border: 1px solid black;">
		<div style="flex: 1; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			fit="fill"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.5;  border: 1px solid black;"><code>fill</code>: fill the container element by distorting the skeleton's aspect ratio</div>
	</div>
	<div style="flex: 1; display: flex; border: 1px solid black;">
		<div style="flex: 1; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			fit="scaleDown"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.5;  border: 1px solid black;"><code>scaleDown</code>: scale the skeleton down to ensure that the skeleton fits within the container element</div>
	</div>
	<div style="flex: 1; display: flex; border: 1px solid black;">
		<div style="flex: 1; display: flex; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			scale=".05"
			fit="none"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.5; border: 1px solid black;"><code>none</code>: display the skeleton without regard to the container element size (here the [scale](#scale) attribute is set to `0.05`).</div>
	</div>
</div>
!!

Additional `fit` modes are:
   - <code>width</code>: fill the container element width, regardless of whether the skeleton overflows the container element vertically.
   - <code>height</code>: fill the container element height, regardless of whether the skeleton overflows the container element horizontally.
   - <code>cover</code>: as small as possible while still covering the entire container element.
   - <code>origin</code>: the skeleton origin is centered with the container element regardless of the bounds.

#### Bounds
The web component uses the skeleton bounds to fit inside the container element. The skeleton bounds are the bounding box (AABB) of the animation (or [multiple animations](#Animations-bounds)), or of the setup pose if no animation is specified. To ensure the bounds fit the parent element according to the specified fit mode, the skeleton's `scaleX` and `scaleY` are set. This means these properties cannot be changed manually. To control `scaleX` and `scaleY` directly, set `fit="none"` or `fit="origin"` and access the skeleton object via JavaScript as explained below.

### Scale
The Skeleton loader scale is set through the `scale` attribute. Read more about scaling in the [Spine Runtimes Guide](/spine-loading-skeleton-data#Scaling).

In this example we set the `fit` mode to `none` to effectively view the scale change (otherwise `scaleX` and `scaleY` would be modified using the default [fit mode](#Fit-mode)).

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			fit="none"
			scale="0.3"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center"><code>scale="0.3"</code></div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			fit="none"
			scale="0.2"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center"><code>scale="0.2"</code></div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/raptor-pma.atlas"
			skeleton="/files/spine-widget/assets/raptor-pro.skel"
			animation="walk"
			fit="none"
			scale="0.1"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center"><code>scale="0.1"</code></div>
	</div>
</div>
!!

### Axis
Use `x-axis` and `y-axis` to shift the skeleton horizontally or vertically by percentage of the container element's width and height.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
				atlas="/files/spine-widget/assets/vine-pma.atlas"
				skeleton="/files/spine-widget/assets/vine-pro.skel"
				animation="grow"
				fit="none"
				scale=".2"
				x-axis=".25"
				start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center; font-size: 75%;"><code>fit="none"</code><br><code>scale=".2"</code><br><code>x-axis=".25"</code></div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
				atlas="/files/spine-widget/assets/vine-pma.atlas"
				skeleton="/files/spine-widget/assets/vine-pro.skel"
				animation="grow"
				fit="origin"
				scale=".2"
				start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center; font-size: 75%;"><code>fit="origin"</code><br><code>scale=".2"</code></div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
				atlas="/files/spine-widget/assets/vine-pma.atlas"
				skeleton="/files/spine-widget/assets/vine-pro.skel"
				animation="grow"
				fit="origin"
				scale=".2"
				y-axis="-.5"
				start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center; font-size: 75%;"><code>fit="origin"</code><br><code>scale=".2"</code><br><code>y-axis="-.5"</code><br></div>
	</div>
</div>
!!

### Offset
Use `offset-x` and `offset-y` to shift the skeleton horizontally or vertically by the specified number of pixels.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/vine-pma.atlas"
			skeleton="/files/spine-widget/assets/vine-pro.skel"
			animation="grow"
			offset-x="0"
			offset-y="0"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>offset-x="0"</code>
			<br>
			<code>offset-y="0"</code>
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/vine-pma.atlas"
			skeleton="/files/spine-widget/assets/vine-pro.skel"
			animation="grow"
			offset-x="-100"
			offset-y="50"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>offset-x="-100"</code>
			<br>
			<code>offset-y="50"</code>
		</div>
	</div>
</div>
!!

### Padding
Add virtual padding to the container element using `pad-left`, `pad-right`, `pad-top`, and `pad-bottom`. These values are percentages of the container's width for left and right, and percentages of the container's height for top and bottom.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/goblins-pma.atlas"
			skeleton="/files/spine-widget/assets/goblins-pro.skel"
			skin="goblingirl"
			animation="walk"
			pad-left="0"
			pad-right="0"
			pad-top="0"
			pad-bottom="0"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>pad-left="0"</code>
			<code>pad-right="0"</code>
			<code>pad-top="0"</code>
			<code>pad-top="0"</code>
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/goblins-pma.atlas"
			skeleton="/files/spine-widget/assets/goblins-pro.skel"
			skin="goblingirl"
			animation="walk"
			pad-left=".25"
			pad-right=".25"
			pad-top=".25"
			pad-bottom=".25"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>pad-left=".25"</code>
			<code>pad-right=".25"</code>
			<code>pad-top=".25"</code>
			<code>pad-top=".25"</code>
		</div>
	</div>
</div>
!!

### Identifier
Assign an identifier to the web component to retrieve it using the `spine.getSkeleton` function. This makes it easy to access the `Skeleton` and `AnimationState` objects in JavaScript code.

<code>&lt;spine-skeleton&gt;</code> executes some asynchronous operations to retrieve assets. The `whenReady` method is used to know when to access the `Skeleton` and `AnimationState` objects.

<div class="example h-200">
	<spine-skeleton
		atlas="/files/spine-widget/assets/raptor-pma.atlas"
		skeleton="/files/spine-widget/assets/raptor-pro.skel"
		identifier="raptor"
		start-when-visible spinner
	></spine-skeleton>
</div>
<script>
   (async () => {
	  const raptor = await spine.getSkeleton("raptor").whenReady;
	  raptor.skeleton.color.set(1, 0, 0, 1);
   })();
</script>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/raptor-pma.atlas"
	skeleton="/files/spine-widget/assets/raptor-pro.skel"
	identifier="raptor"
></spine-skeleton>
```
```js
const raptor = await spine.getSkeleton("raptor").whenReady;
raptor.skeleton.color.set(1, 0, 0, 1);
```

### Clip
The `clip` attribute will hide everything that is outside the container element.

Beware that this will break batching across skeletons.

<div class="example" style="flex-direction: column; align-items: stretch;">
	<div style="align-self: center; display: flex; flex-direction: column; border: 1px solid black; height: 200px; width: 200px;">
		<div style="flex: 1; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/tank-pma.atlas"
			skeleton="/files/spine-widget/assets/tank-pro.skel"
			animation="drive"
			fit="height"
			pad-top="0.3"
			pad-bottom="0.3"
			start-when-visible spinner
			></spine-skeleton>
		</div>
	</div>
	<div style="align-self: center; display: flex; flex-direction: column; border: 1px solid black; height: 200px; width: 200px;">
		<div style="flex: 1; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/tank-pma.atlas"
			skeleton="/files/spine-widget/assets/tank-pro.skel"
			animation="drive"
			fit="height"
			pad-top="0.3"
			pad-bottom="0.3"
			clip
			start-when-visible spinner
			></spine-skeleton>
		</div>
	</div>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/tank-pma.atlas"
	skeleton="/files/spine-widget/assets/tank-pro.skel"
	animation="drive"
	fit="height"
	pad-top="0.3"
	pad-bottom="0.3"
></spine-skeleton>

<spine-skeleton
	atlas="/files/spine-widget/assets/tank-pma.atlas"
	skeleton="/files/spine-widget/assets/tank-pro.skel"
	animation="drive"
	fit="height"
	pad-top="0.3"
	pad-bottom="0.3"
	clip
></spine-skeleton>
```

### Custom bounds
Custom [bounds](#Bounds) can be specified to focus on specific details of the animation, to zoom out, simulate camera movement, etc.

The `bounds-x`, `bounds-y`, `bounds-width`, and `bounds-height` attributes define custom bounds.

This example focuses on Celeste's face. To prevent the skeleton from overflowing the container element, we set the `clip` attribute.

<div class="example">
	<div style="width: 300px; height: 200px; border: 1px solid black;">
		<spine-skeleton
			atlas="/files/spine-widget/assets/celestial-circus-pma.atlas"
			skeleton="/files/spine-widget/assets/celestial-circus-pro.skel"
			animation="wings-and-feet"
			bounds-x="-155"
			bounds-y="650"
			bounds-width="300"
			bounds-height="350"
			clip
			start-when-visible spinner
		></spine-skeleton>
	</div>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/celestial-circus-pma.atlas"
	skeleton="/files/spine-widget/assets/celestial-circus-pro.skel"
	animation="wings-and-feet"
	bounds-x="-155"
	bounds-y="650"
	bounds-width="300"
	bounds-height="350"
	clip
></spine-skeleton>
```

### Auto calculate bounds
The animation can be changed by modifying the `animation` attribute. The web component will switch to the new animation as if it were freshly created.
However, new [bounds](#Bounds) are not recalculated unless the `auto-calculate-bounds` attribute is set.
This default behavior helps maintain consistent skeleton dimensions across animations. It might be useful to combine it with the [animation-bounds](#Animations-bounds) attribute.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			identifier="spineboy-auto-bounds-1"
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation="jump"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			No <code>auto-calculate-bounds</code> set
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			identifier="spineboy-auto-bounds-2"
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation="jump"
			auto-calculate-bounds
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>auto-calculate-bounds</code>
		</div>
	</div>
</div>
<script>
	(async () => {
		const [wc1, wc2] = await Promise.all([
			spine.getSkeleton("spineboy-auto-bounds-1").whenReady,
			spine.getSkeleton("spineboy-auto-bounds-2").whenReady,
		]);
		let toogleAnimation = false;
		setInterval(() => {
			const newAnimation = toogleAnimation ? "jump" : "death";
			wc1.setAttribute("animation", newAnimation)
			wc2.setAttribute("animation", newAnimation)
			toogleAnimation = !toogleAnimation;
		}, 4000);
	})();
</script>
!!

```html
<spine-skeleton
	identifier="spineboy-auto-bounds-1"
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation="jump"
></spine-skeleton>

<spine-skeleton
	identifier="spineboy-auto-bounds-2"
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation="jump"
	auto-calculate-bounds
></spine-skeleton>
```

```js
const [wc1, wc2] = await Promise.all([
	spine.getSkeleton("spineboy-auto-bounds-1").whenReady,
	spine.getSkeleton("spineboy-auto-bounds-2").whenReady,
]);
let toogleAnimation = false;
setInterval(() => {
	const newAnimation = toogleAnimation ? "jump" : "death";
	wc1.setAttribute("animation", newAnimation)
	wc2.setAttribute("animation", newAnimation)
	toogleAnimation = !toogleAnimation;
}, 4000);
```

### Default mix
The `default-mix` attribute defines the default mix duration for the `AnimationState`. This is the default time in seconds to mix between animations when the animation changes, whether by using the [`animations`](#Animations) attribute or by JavaScript code using the `AnimationState` object.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			identifier="spineboy-default-mix-1"
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation="idle"
			default-mix="0"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>default-mix="0" (default)</code>
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			identifier="spineboy-default-mix-2"
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation="idle"
			default-mix="1"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>default-mix="1"</code>
		</div>
	</div>
</div>
<script>
   (async () => {
		const [wc1, wc2] = await Promise.all([
			spine.getSkeleton("spineboy-default-mix-1").whenReady,
			spine.getSkeleton("spineboy-default-mix-2").whenReady,
		]);
		let toogleAnimation = false;
		setInterval(() => {
			const newAnimation = toogleAnimation ? "idle" : "run";
			wc1.setAttribute("animation", newAnimation)
			wc2.setAttribute("animation", newAnimation)
			toogleAnimation = !toogleAnimation;
		}, 4000);
   })();
</script>
!!

```html
<spine-skeleton
	identifier="spineboy-default-mix-1"
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation="idle"
	default-mix="0"
></spine-skeleton>
<spine-skeleton
	identifier="spineboy-default-mix-2"
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation="idle"
	default-mix="1"
></spine-skeleton>
```

```js
const [wc1, wc2] = await Promise.all([
	spine.getSkeleton("spineboy-default-mix-1").whenReady,
	spine.getSkeleton("spineboy-default-mix-2").whenReady,
]);
let toogleAnimation = false;
setInterval(() => {
	const newAnimation = toogleAnimation ? "idle" : "run";
	wc1.setAttribute("animation", newAnimation)
	wc2.setAttribute("animation", newAnimation)
	toogleAnimation = !toogleAnimation;
}, 4000);
```

### Animations

To display a sequence of animations without code, the `animations` attribute can be used. Here we reproduced the example above with just web component attributes:

<div class="example h-200">
	<spine-skeleton
		atlas="/files/spine-widget/assets/spineboy-pma.atlas"
		skeleton="/files/spine-widget/assets/spineboy-pro.skel"
		animation-bounds="walk,run"
		default-mix="1"
		animations="
			[loop, 0, 3.5]
			[0, idle, true]
			[0, run, true, 4]
		"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

```html
<spine-skeleton
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	animation-bounds="walk,run"
	default-mix="1"
	animations="
		[loop, 0, 3.5]
		[0, idle, true]
		[0, run, true, 4]
	"
></spine-skeleton>
```

The `animations` attribute accepts a string composed of groups enclosed in square brackets, for example: `[...][...][...]`.

Each group defines an animation to be played, with parameters provided as a comma-separated list:

* **track**: the track number on which the animation will play
* **animation name**: the name of the animation
* **loop**: `true` to loop the animation (`false` if omitted)
* **delay**: seconds to wait after the previous animation starts, or `0` to wait until the previous animation ends (`0` if omitted)
* **mixDuration**: seconds to mix from the previous animation to this animation (`default-mix` if omitted, not applicable for the first animation on a track)

To enable looping of a track after the last animation, a special group `[loop, trackNumber, repeatDelay]` can be added, where:

* **loop**: identifies this as a loop instruction
* **trackNumber**: the track number to loop
* **repeatDelay**: the number of seconds to wait after the last animation is completed before repeating the loop (`0` if omitted)

The first group for each track number is passed to the [setAnimation](/spine-api-reference#AnimationState-setAnimation2) method. Subsequent groups for the same track are passed to [addAnimation](/spine-api-reference#AnimationState-addAnimation2).

To use [setEmptyAnimation](/spine-api-reference#AnimationState-setEmptyAnimation) or [addEmptyAnimation](/spine-api-reference#AnimationState-addEmptyAnimation), the animation name `#EMPTY#` must be specified. In this case, the `loop` parameter is ignored.

Refer to the two examples below for clarification.

<div class="example h-200">
	<spine-skeleton
		atlas="/files/spine-widget/assets/spineboy-pma.atlas"
		skeleton="/files/spine-widget/assets/spineboy-pro.skel"
		animation-bounds="jump,death"
		default-mix="0.05"
		animations="
			[loop, 0]
			[0, idle, true]
			[0, run, false, 2, 0.25]
			[0, run, false]
			[0, run, false]
			[0, run-to-idle, false, 0, 0.15]
			[0, idle, true]
			[0, jump, false, 0, 0.15]
			[0, walk, false, 0, 0.05]
			[0, death, false, 0, 0.05]
		"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

Spineboy uses this value for the `animations` attribute:

```plain
[loop, 0]
[0, idle, true]
[0, run, false, 2, 0.25]
[0, run]
[0, run]
[0, run-to-idle, false, 0, 0.15]
[0, idle, true]
[0, jump, false, 0, 0.15]
[0, walk, false, 0, 0.05]
[0, death, false, 0, 0.05]
```

All animations are played on a single track. Here's a breakdown of the sequence:

- `[loop, 0]`: instructs track 0 to loop back to the beginning upon reaching the end
- `[0, idle, true]`: sets the idle animation to loop
- `[0, run, false, 2, 0.25]`: queues the run animation to start after 2 seconds with a 0.25 second mix
- `[0, run]`: queues an additional run animation, without looping
- `[0, run]`: queues another run animation
- `[0, run-to-idle, false, 0, 0.15]`: queues the run-to-idle transition with no delay and a 0.15 second mix
- `[0, idle, true]`: queues the idle animation to loop again
- `[0, jump, false, 0, 0.15]`: queues the jump animation with no delay and a 0.15 second mix
- `[0, walk, false, 0, 0.05]`: queues the walk animation with no delay and a 0.05 second mix
- `[0, death, false, 0, 0.05]`: queues the death animation with no delay and a 0.05 second mix

<div class="example h-200">
	<spine-skeleton
		identifier="celeste-animations"
		atlas="/files/spine-widget/assets/celestial-circus-pma.atlas"
		skeleton="/files/spine-widget/assets/celestial-circus-pro.skel"
		default-mix="0.05"
		animations="
			[0, wings-and-feet, true]
			[loop, 1]
			[1, #EMPTY#, false]
			[1, eyeblink, false, 2]
		"
		start-when-visible spinner
	></spine-skeleton>
</div>
!!

Celeste uses the following value for the `animations` attribute:

```
[0, wings-and-feet, true]
[loop, 1]
[1, #EMPTY#]
[1, eyeblink, false, 2]
```

<div class="example" style="flex-direction: column;">
	<textarea id="celeste-animations-text-area" style="margin: 0;" rows="5">
[0, wings-and-feet, true]
[loop, 1]
[1, #EMPTY#]
[1, eyeblink, false, 2]
	</textarea>
	<input type="button" value="Update animation" onclick="updateCelesteAnimations(this)" style="align-self: center;">
</div>
<script>
	async function updateCelesteAnimations() {
		const celesteAnimations = await spine.getSkeleton("celeste-animations").whenReady;
		var celesteAnimationsTextArea = document.getElementById("celeste-animations-text-area");
		celesteAnimations.setAttribute("animations", celesteAnimationsTextArea.value)
	}
</script>
!!

This example uses two tracks. Track 0 plays the `wings-and-feet` animation. Track 1 loops, playing an empty animation followed by the `eyeblink` animation with a 2 second delay.

The textarea above can be modified for experimentation, then click `Update animation`. For example, changing the delay from `2` to `0.5` results in more frequent blinking. To start the `swing` animation on track 0 after 5 seconds with a 0.5 second mix, append:
`[0, swing, true, 5, 0.5]`

### Animations bounds
To define [bounds](#Bounds) based on multiple animations, the `animation-bounds` attribute can be used. This attribute accepts a list of animations and calculates bounds that encompass all of them.

This approach helps maintain a consistent scale across animations and prevents the skeleton from overflowing its container when switching to an animation with larger bounds.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animations="
				[loop, 0]
				[0, idle, true]
				[0, jump, true]
			"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			No <code>animation-bounds</code>
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			animation-bounds="walk,jump"
			animations="
				[loop, 0]
				[0, idle, true]
				[0, jump, true]
			"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>animation-bounds="walk,jump"</code>
		</div>
	</div>
</div>
!!

### Spinner
`spinner` attribute allows to show a spinner while assets are loading. By default, nothing is shown during assets load. Click the buttons below to simulate a 1 second loading delay and to toggle the `spinner` attribute.

<div class="example flex-column">
	<div style="display: flex; justify-content: center; height: 200px; border: 1px solid black;">
			<spine-skeleton
			identifier="spineboy-loading"
			atlas="/files/spine-widget/assets/spineboy-pma.atlas"
			skeleton="/files/spine-widget/assets/spineboy-pro.skel"
			spinner
			start-when-visible spinner
			></spine-skeleton>
	</div>
	<input type="button" value="Toggle spinner: OFF" onclick="toggleSpinner(this)" />
	<input type="button" value="Reload" onclick="reloadWidget(this)" />
</div>
<script>
	const wcLoading = spine.getSkeleton("spineboy-loading");
	async function reloadWidget(element) {
		element.disabled = true;
		await wcLoading.whenReady;
		wcLoading.loading = true;
		setTimeout(() => {
		element.disabled = false;
		wcLoading.loading = false;
		}, 1000)
	}
	function toggleSpinner(element) {
		wcLoading.spinner = !wcLoading.spinner;
		element.value = wcLoading.spinner ? "Toggle spinner: OFF" : "Toggle spinner: ON";
	}
</script>
!!

```html
<spine-skeleton
	identifier="spineboy-loading"
	atlas="/files/spine-widget/assets/spineboy-pma.atlas"
	skeleton="/files/spine-widget/assets/spineboy-pro.skel"
	spinner
></spine-skeleton>

<input type="button" value="Toggle spinner: OFF" onclick="toggleSpinner(this)" />
<input type="button" value="Reload" onclick="reloadWidget(this)" />
```

```js
const wcLoading = spine.getSkeleton("spineboy-loading");
async function reloadWidget(element) {
	element.disabled = true;
	await wcLoading.whenReady;
	wcLoading.loading = true;
	setTimeout(() => {
	element.disabled = false;
	wcLoading.loading = false;
	}, 1000)
}
function toggleSpinner(element) {
	wcLoading.spinner = !wcLoading.spinner;
	element.value = wcLoading.spinner ? "Toggle spinner: OFF" : "Toggle spinner: ON";
}
```

### Offscreen behavior
Web components that are off-screen are not rendered. While off-screen, by default the `AnimationState` update, `Skeleton` update, `skeleton.apply`, and `skeleton.updateWorldTransform` functions are not invoked. This corresponds to `offscreen=pause`.

To ensure that update functions are invoked even when off-screen, set `offscreen=update`.

To ensure that all functions are invoked regardless of visibility, set `offscreen=pose`.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black; height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/vine-pma.atlas"
			skeleton="/files/spine-widget/assets/vine-pro.skel"
			animation="grow"
			offscreen="pause"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>pause</code>
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black; height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/vine-pma.atlas"
			skeleton="/files/spine-widget/assets/vine-pro.skel"
			animation="grow"
			offscreen="update"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>update</code>
		</div>
	</div>
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black; height: 200px;">
		<div style="flex: 0.9; border: 1px solid black;">
			<spine-skeleton
			atlas="/files/spine-widget/assets/vine-pma.atlas"
			skeleton="/files/spine-widget/assets/vine-pro.skel"
			animation="grow"
			offscreen="pose"
			start-when-visible spinner
			></spine-skeleton>
		</div>
		<div style="flex: 0.1; border: 1px solid black; text-align: center">
			<code>pose</code>
		</div>
	</div>
</div>
!!

When this page is refreshed with all three skeletons visible in the viewport, their animations start in sync. However, the first skeleton has `offscreen="pause"`, which causes its state to pause when scrolled out of view. Upon returning to the viewport, its animation resumes without advancing the paused time, resulting in desynchronization with the other two skeletons. While the other two skeletons remain in sync, slight differences may occur, particularly when physics is involved.

To prevent a skeleton from being paused while off-screen, it is recommended to use the `update` behavior. This avoids invoking `updateWorldTransform`, which is typically the most CPU-intensive function.

### Custom update
A web component's `skeleton` and `state` are updated and applied similarly to [other runtimes](/spine-runtime-skeletons#updateWorldTransform).

Custom logic can be injected before and after `updateWorldTransform` is called by setting `beforeUpdateWorldTransforms` and `afterUpdateWorldTransforms`, respectively.

To fully replace the default update behavior, assign a function to the `update` property. This replaces both state and skeleton updates, including [off-screen](#Offscreen-behavior) optimizations. In this case, managing the `update`, `apply`, and `updateWorldTransform` invocation becomes the developer's responsibility.
The `onScreen` property, which is `true` when the component is visible on screen, can be used for covenience.

All three functions follow the same signature: `(delta: number, skeleton: Skeleton, state: AnimationState) => void`


### Drag
Setting the `drag` attribute enables dragging for the web component. This may increase CPU usage, so it is advisable to use this feature only when draggable behavior is required.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<spine-skeleton
			atlas="/files/spine-widget/assets/celestial-circus-pma.atlas"
			skeleton="/files/spine-widget/assets/celestial-circus-pro.skel"
			animations="[0, wings-and-feet, true][1, eyeblink-long, true]"
			drag
			start-when-visible spinner
		></spine-skeleton>
	</div>
</div>
!!

### Pointer position
To determine the pointer position in various coordinate spaces, the following properties can be used:

For `spine-skeleton`:

* `pointerWorldX` and `pointerWorldY`: the x and y coordinates of the pointer relative to the skeleton origin (Spine world coordinates).
* `worldX` and `worldY`: the x and y coordinates of the skeleton origin relative to the canvas/WebGL context origin (Spine world coordinates).

For `spine-overlay`:

* `pointerCanvasX` and `pointerCanvasY`: the x and y coordinates of the pointer relative to the top-left corner of the canvas (screen coordinates).
* `pointerWorldX` and `pointerWorldY`: the x and y coordinates of the pointer relative to the canvas/WebGL context origin (Spine world coordinates).

These properties enable interactive behavior with the web component. For example, in the examples below the owl's eyes follow the pointer.

<div class="example">
	<div style="flex: 1; display: flex; flex-direction: column; border: 1px solid black;  height: 200px;">
		<spine-skeleton
			identifier="owl-pointer"
			atlas="/files/spine-widget/assets/owl-pma.atlas"
			skeleton="/files/spine-widget/assets/owl-pro.skel"
			animations="[0, idle, true][1, blink, true]"
			start-when-visible spinner
		></spine-skeleton>
	</div>
</div>
<script>
	(async () => {
		const wc = await spine.getSkeleton("owl-pointer").whenReady;
		const controlBone = wc.skeleton.findBone("control");
		const tempVector = new spine.Vector3();
		wc.afterUpdateWorldTransforms = () => {
			controlBone.parent.worldToLocal(tempVector.set(wc.pointerWorldX, wc.pointerWorldY));
			controlBone.x = controlBone.data.x + tempVector.x / wc.overlay.canvas.width * 30;
			controlBone.y = controlBone.data.y + tempVector.y / wc.overlay.canvas.height * 30;
		}
	})();
</script>
!!

```html
<spine-skeleton
	identifier="owl-pointer"
	atlas="/files/spine-widget/assets/owl-pma.atlas"
	skeleton="/files/spine-widget/assets/owl-pro.skel"
	animations="[0, idle, true][1, blink, true]"
></spine-skeleton>
```

```js
const wc = await spine.getSkeleton("owl-pointer").whenReady;
const controlBone = wc.skeleton.findBone("control");
const tempVector = new spine.Vector3();
wc.afterUpdateWorldTransforms = () => {
	controlBone.parent.worldToLocal(tempVector.set(wc.pointerWorldX, wc.pointerWorldY));
	controlBone.x = controlBone.data.x + tempVector.x / wc.overlay.canvas.width * 30;
	controlBone.y = controlBone.data.y + tempVector.y / wc.overlay.canvas.height * 30;
}
```

### Interaction callbacks
Callbacks can be attached to the web components to handle pointer interactions by setting the `interactive` attribute.

Callbacks can respond to interactions either within the web component's [bounds](#Bounds) or with specific slots. Supported events (`PointerEventType`) include `down`, `up`, `enter`, `leave`, `move`, and `drag`.

To add callbacks:

* Set `pointerEventCallback: (event: PointerEventType) => void` to handle pointer actions within the web component bounds.
* Call `addPointerSlotEventCallback (slotRef: number | string | Slot, slotFunction: (slot: Slot, event: PointerEventType) => void)` to handle pointer actions within the attachment bounds of the specified slot.

In the example below:

* `pointerEventCallback` triggers the `jump` animation on `enter`, and the `wave` animation on `leave`.
* `addPointerSlotEventCallback` adds a callback for the `head-base` slot (the face). When the attachment receives a `down` event, the normal and dark tint are updated based on the selected colors in the two tint selectors.

<div class="example flex-column">
	<div style="flex: 1; display: flex; border: 1px solid black;  height: 200px;">
			<div style="flex: 1; display: flex; border: 1px solid black;  height: 200px;">
			<spine-skeleton
				identifier="interactive0"
				atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
				skeleton="/files/spine-widget/assets/chibi-stickers.skel"
				skin="mario"
				animation="emotes/wave"
				animation-bounds="emotes/wave,emotes/hooray"
				pages="0,4"
				interactive
				start-when-visible spinner
			></spine-skeleton>
			</div>
			<div style="flex: 1; display: flex; border: 1px solid black;  height: 200px;">
			<spine-skeleton
				identifier="interactive1"
				atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
				skeleton="/files/spine-widget/assets/chibi-stickers.skel"
				skin="nate"
				animation="emotes/wave"
				animation-bounds="emotes/wave,emotes/hooray"
				pages="0,6"
				interactive
				start-when-visible spinner
			></spine-skeleton>
			</div>
	</div>
	<div style="flex: 1; display: flex; border: 2px solid black;">
		<div style="flex: 1; display: flex; justify-content: space-evenly;">
			Tint normal: <input type="color" id="color-picker" value="#ff0000" style="margin: 0;" />
		</div>
		<div style="flex: 1; display: flex; justify-content: space-evenly;">
			Tint black: <input type="color" id="dark-picker" value="#000000" style="margin: 0;"/>
		</div>
	</div>
</div>
<script>
	const colorPicker = document.getElementById("color-picker");
	const darkPicker = document.getElementById("dark-picker");
	[0, 1].forEach(async (i) => {
		const wc = await spine.getSkeleton(`interactive${i}`).whenReady;
		wc.pointerEventCallback = (event) => {
			if (event === "enter") wc.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15;
			if (event === "leave") wc.state.setAnimation(0, "emotes/wave", true).mixDuration = .25;
		}
		const tempColor = new spine.Color();
		const slot = wc.skeleton.findSlot("head-base");
		slot.darkColor = new spine.Color(0, 0, 0, 1);
		wc.addPointerSlotEventCallback(slot, (slot, event) => {
			if (event === "down") {
				slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor));
				slot.color.setFromColor(spine.Color.fromString(colorPicker.value, tempColor));
			}
		});
	})
</script>
!!

```html
<spine-skeleton
	identifier="interactive0"
	atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
	skeleton="/files/spine-widget/assets/chibi-stickers.skel"
	skin="mario"
	animation="emotes/wave"
	animation-bounds="emotes/wave,emotes/hooray"
	pages="0,4"
	interactive
></spine-skeleton>

<spine-skeleton
	identifier="interactive1"
	atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
	skeleton="/files/spine-widget/assets/chibi-stickers.skel"
	skin="nate"
	animation="emotes/wave"
	animation-bounds="emotes/wave,emotes/hooray"
	pages="0,6"
	interactive
></spine-skeleton>

Tint normal: <input type="color" id="color-picker" value="#ff0000" style="margin: 0;" />
Tint black: <input type="color" id="dark-picker" value="#000000" style="margin: 0;"/>
```

```js
const colorPicker = document.getElementById("color-picker");
const darkPicker = document.getElementById("dark-picker");
[0, 1].forEach(async (i) => {
	const wc = await spine.getSkeleton(`interactive${i}`).whenReady;
	wc.pointerEventCallback = (event) => {
		if (event === "enter") wc.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15;
		if (event === "leave") wc.state.setAnimation(0, "emotes/wave", true).mixDuration = .25;
	}
	const tempColor = new spine.Color();
	const slot = wc.skeleton.findSlot("head-base");
	slot.darkColor = new spine.Color(0, 0, 0, 1);
	wc.addPointerSlotEventCallback(slot, (slot, event) => {
		if (event === "down") {
		slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor));
		slot.color.setFromColor(spine.Color.fromString(colorPicker.value, tempColor));
		}
	});
})
```

### Debug mode
Debug mode can be enabled by setting the `debug` attribute. This displays the following visual indicators:

* The skeleton's world origin (green)
* The root bone position (red)
* The [bounds](#Bounds) rectangle and its center (blue)
* The draggable area (semi-transparent red), if the web component is set as draggable.

In this example, the root is shifted to avoid overlapping with the origin.

<div class="example">
	<spine-skeleton
		style="width: 200px; height: 200px;"
		identifier="sack-debug"
		atlas="/files/spine-widget/assets/sack-pma.atlas"
		skeleton="/files/spine-widget/assets/sack-pro.skel"
		animation="cape-follow-example"
		drag
		offscreen="pose"
		debug
		start-when-visible spinner
	></spine-skeleton>
</div>
<script>
	spine.getSkeleton("sack-debug").whenReady
		.then(({ skeleton }) => skeleton.getRootBone().x += 50);
</script>
!!

```html
<spine-skeleton
	style="width: 200px; height: 200px;"
	identifier="sack-debug"
	atlas="/files/spine-widget/assets/sack-pma.atlas"
	skeleton="/files/spine-widget/assets/sack-pro.skel"
	animation="cape-follow-example"
	drag
	offscreen="pose"
	debug
></spine-skeleton>
```

```js
spine.getSkeleton("sack-debug").whenReady
	.then(({ skeleton }) => skeleton.getRootBone().x += 50);
```

### Page
When using multiple atlas pages (for example, one page per skin) and only a subset of pages need to be displayed, the `pages` attribute can be used to specify which atlas pages to load. Provide a comma-separated list of the desired page indices.

<div class="example" style="justify-content: space-evenly; width: 100%;">
	<div style="display: flex; flex-direction:column; width: 20%;">
		<spine-skeleton
			style="width: 100%; height: 100px;"
			atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
			skeleton="/files/spine-widget/assets/chibi-stickers.skel"
			animation="emotes/wave"
			skin="nate"
			pages="0,6"
			start-when-visible spinner
		></spine-skeleton>
		<div><code>pages="0,6"</code></div>
	</div>
	<div style="display: flex; flex-direction:column; width: 20%;">
		<spine-skeleton
			style="width: 100%; height: 100px;"
			atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
			skeleton="/files/spine-widget/assets/chibi-stickers.skel"
			animation="emotes/wave"
			skin="mario"
			pages="0,4"
			start-when-visible spinner
		></spine-skeleton>
		<div><code>pages="0,4"</code></div>
	</div>
	<div style="display: flex; flex-direction:column; width: 20%;">
		<spine-skeleton
			style="width: 100%; height: 100px;"
			atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
			skeleton="/files/spine-widget/assets/chibi-stickers.skel"
			animation="emotes/wave"
			skin="erikari"
			pages="0,1"
			start-when-visible spinner
		></spine-skeleton>
		<div><code>pages="0,1"</code></div>
	</div>
</div>
!!

To load textures programmatically, set the `pages` attribute to an empty value: `pages=""`. This loads the skeleton and atlas data without loading any textures, allowing textures to be loaded manually at a later time.

<div class="example h-200" style="justify-content: space-evenly;">
	<spine-skeleton
		identifier="dragon"
		style="flex: 0.8; height: 100%;"
		atlas="/files/spine-widget/assets/dragon-pma.atlas"
		skeleton="/files/spine-widget/assets/dragon-ess.skel"
		animation="flying"
		pages=""
		start-when-visible spinner
	></spine-skeleton>
	<div style="flex: 0.2;">
		<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)">
	</div>
</div>
<script>
	async function loadPageDragon(pageIndex) {
		const dragon = await spine.getSkeleton("dragon").whenReady;
		if (!dragon.pages.includes(pageIndex)) {
			dragon.pages.push(pageIndex);
			dragon.loadTexturesInPagesAttribute();
		}
	}
</script>
!!

```html
<spine-skeleton
	identifier="dragon"
	style="flex: 0.8; height: 100%;"
	atlas="/files/spine-widget/assets/dragon-pma.atlas"
	skeleton="/files/spine-widget/assets/dragon-ess.skel"
	animation="flying"
	pages=""
></spine-skeleton>

<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)" />
```

```js
async function loadPageDragon(pageIndex) {
	const dragon = await spine.getSkeleton("dragon").whenReady;
	if (!dragon.pages.includes(pageIndex)) {
		dragon.pages.push(pageIndex);
		dragon.loadTexturesInPagesAttribute();
	}
}
```

### Follow slot
An HTMLElement can be made to follow a slot. This is useful for integrating dynamic content such as text into animations.

Call the `followSlot` function with these parameters:

* The `Slot` instance or slot name to follow
* The `HTMLElement` that will follow the slot
* An "options" object with these properties:

  * `followOpacity`: links the element's opacity to the slot's alpha
  * `followScale`: links the element's scale to the slot's scale
  * `followRotation`: links the element's rotation to the slot's rotation
  * `followVisibility`: shows or hides the element based on whether the slot has an attachment visible
  * `hideAttachment`: hides the slot's attachment, as if the element visually replaces it

<div class="example">
	<spine-skeleton
		style="width: 200px; height: 200px;"
		identifier="potty"
		atlas="/files/spine-widget/assets/cloud-pot-pma.atlas"
		skeleton="/files/spine-widget/assets/cloud-pot.skel"
		animation="playing-in-the-rain"
		start-when-visible spinner
	></spine-skeleton>
</div>
<div id="rain/rain-color" style="font-size: 50px; display: none;">A</div>
<div id="rain/rain-white" style="font-size: 50px; display: none;">B</div>
<div id="rain/rain-blue" style="font-size: 50px; display: none;">C</div>
<div id="rain/rain-green" style="font-size: 50px; display: none;">D</div>
<script>
	(async () => {
		const wc = await spine.getSkeleton("potty").whenReady;
		const options = { followVisibility: false, hideAttachment: true };
		wc.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), options);
		wc.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), options);
		wc.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), options);
		wc.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), options);
	})();
</script>
!!

```html
<spine-skeleton
	style="width: 200px; height: 200px;"
	identifier="potty"
	atlas="/files/spine-widget/assets/cloud-pot-pma.atlas"
	skeleton="/files/spine-widget/assets/cloud-pot.skel"
	animation="playing-in-the-rain"
></spine-skeleton>

<div id="rain/rain-color" style="font-size: 50px; display: none;">A</div>
<div id="rain/rain-white" style="font-size: 50px; display: none;">B</div>
<div id="rain/rain-blue" style="font-size: 50px; display: none;">C</div>
<div id="rain/rain-green" style="font-size: 50px; display: none;">D</div>
```

```js
const wc = await spine.getSkeleton("potty").whenReady;
const options = { followVisibility: false, hideAttachment: true };
wc.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), options);
wc.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), options);
wc.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), options);
wc.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), options);
```

`followSlot` works even with other spine web components! It works even when it is dragged!

<div class="example">
	<spine-skeleton
		style="width: 200px; height: 200px;"
		identifier="potty2"
		atlas="/files/spine-widget/assets/cloud-pot-pma.atlas"
		skeleton="/files/spine-widget/assets/cloud-pot.skel"
		animation="rain"
		drag
		offscreen="pose"
		start-when-visible spinner
	></spine-skeleton>
</div>
<spine-skeleton identifier="potty2-1" atlas="/files/spine-widget/assets/raptor-pma.atlas" skeleton="/files/spine-widget/assets/raptor-pro.skel" start-when-visible spinner animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
<spine-skeleton identifier="potty2-2" atlas="/files/spine-widget/assets/spineboy-pma.atlas" skeleton="/files/spine-widget/assets/spineboy-pro.skel" start-when-visible spinner animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
<spine-skeleton identifier="potty2-3" atlas="/files/spine-widget/assets/celestial-circus-pma.atlas" skeleton="/files/spine-widget/assets/celestial-circus-pro.skel" start-when-visible spinner animation="wings-and-feet" style="height:200px; width: 200px;"></spine-skeleton>
<spine-skeleton identifier="potty2-4" atlas="/files/spine-widget/assets/goblins-pma.atlas" skeleton="/files/spine-widget/assets/goblins-pro.skel" skin="goblingirl" start-when-visible spinner animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
<script>
	(async () => {
		const wc = await spine.getSkeleton("potty2").whenReady;
		const options = { followVisibility: false, hideAttachment: true };
		wc.followSlot("rain/rain-color", spine.getSkeleton("potty2-1"), options);
		wc.followSlot("rain/rain-white", spine.getSkeleton("potty2-2"), options);
		wc.followSlot("rain/rain-blue", spine.getSkeleton("potty2-3"), options);
		wc.followSlot("rain/rain-green", spine.getSkeleton("potty2-4"), options);
	})();
</script>
!!

```html
<spine-skeleton
	style="width: 200px; height: 200px;"
	identifier="potty2"
	atlas="/files/spine-widget/assets/cloud-pot-pma.atlas"
	skeleton="/files/spine-widget/assets/cloud-pot.skel"
	animation="rain"
	drag
	offscreen="pose"
></spine-skeleton>

<spine-skeleton identifier="potty2-1" atlas="/files/spine-widget/assets/raptor-pma.atlas" skeleton="/files/spine-widget/assets/raptor-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
<spine-skeleton identifier="potty2-2" atlas="/files/spine-widget/assets/spineboy-pma.atlas" skeleton="/files/spine-widget/assets/spineboy-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
<spine-skeleton identifier="potty2-3" atlas="/files/spine-widget/assets/celestial-circus-pma.atlas" skeleton="/files/spine-widget/assets/celestial-circus-pro.skel" animation="wings-and-feet" style="height:200px; width: 200px;"></spine-skeleton>
<spine-skeleton identifier="potty2-4" atlas="/files/spine-widget/assets/goblins-pma.atlas" skeleton="/files/spine-widget/assets/goblins-pro.skel" skin="goblingirl" animation="walk" style="height:200px; width: 200px;"></spine-skeleton>
```

```js
const wc = await spine.getSkeleton("potty2").whenReady;
const options = { followVisibility: false, hideAttachment: true };
wc.followSlot("rain/rain-color", spine.getSkeleton("potty2-1"), options);
wc.followSlot("rain/rain-white", spine.getSkeleton("potty2-2"), options);
wc.followSlot("rain/rain-blue", spine.getSkeleton("potty2-3"), options);
wc.followSlot("rain/rain-green", spine.getSkeleton("potty2-4"), options);
```

## Advanced usage

### Programmatic creation

A <code>&lt;spine-skeleton&gt;</code> element can be created programmatically using the `createSkeleton` function. This function accepts an object where each property corresponds to a camelCase version of the web component's attributes.

<div class="example" style="flex-wrap: wrap; justify-content: space-evenly; align-items: center; width: 100%;">
	<script>
		["soeren", "sinisa", "luke"].forEach(skin => {
			["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"].forEach(animation => {
			const wc = spine.createSkeleton({
				atlasPath: "/files/spine-widget/assets/chibi-stickers-pma.atlas",
				skeletonPath: "/files/spine-widget/assets/chibi-stickers.skel",
				animation,
				skin,
				pages: [0, 3, 7, 8],
				startWhenVisible: true,
				spinner: true,
			});
			wc.style.width = "25%";
			wc.style.height = "100px";
			document.currentScript.parentElement.appendChild(wc);
			})
		})
	</script>
</div>
!!

```html
<div style="display: flex; flex-wrap: wrap; justify-content: space-evenly; align-items: center; width: 100%;">
	<script>
		["soeren", "sinisa", "luke"].forEach(skin => {
			["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"].forEach(animation => {
			const wc = spine.createSkeleton({
				atlasPath: "/files/spine-widget/assets/chibi-stickers-pma.atlas",
				skeletonPath: "/files/spine-widget/assets/chibi-stickers.skel",
				animation,
				skin,
				pages: [0, 3, 7, 8],
			});
			wc.style.width = "25%";
			wc.style.height = "100px";
			document.currentScript.parentElement.appendChild(wc);
			})
		})
	</script>
</div>
```

Alternatively, the web component can be directly appended to the DOM as HTML using standard DOM manipulation methods.

<div class="example" style="flex-wrap: wrap; justify-content: space-evenly; align-items: center; width: 100%;">
	<script>
		["harri", "misaki", "spineboy"].forEach(skin => {
			["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"].forEach(animation => {
			document.currentScript.parentElement.insertAdjacentHTML('beforeend', `<spine-skeleton
				style="width: 25%; height: 100px;"
				atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
				skeleton="/files/spine-widget/assets/chibi-stickers.skel"
				animation="${animation}"
				skin="${skin}"
				pages="0,2,5,9"
				start-when-visible spinner
			></spine-skeleton>
			`);
			});
		});
	</script>
</div>
!!

```html
<div style="display: flex; flex-wrap: wrap; justify-content: space-evenly; align-items: center; width: 100%;">
	<script>
		["harri", "misaki", "spineboy"].forEach(skin => {
			["emotes/wave", "movement/trot-left", "emotes/idea", "emotes/hooray"].forEach(animation => {
			document.currentScript.parentElement.insertAdjacentHTML('beforeend', `<spine-skeleton
				style="width: 25%; height: 100px;"
				atlas="/files/spine-widget/assets/chibi-stickers-pma.atlas"
				skeleton="/files/spine-widget/assets/chibi-stickers.skel"
				animation="${animation}"
				skin="${skin}"
				pages="0,2,5,9"
			></spine-skeleton>
			`);
			});
		});
	</script>
</div>
```

By default, assets load immediately.

By default, assets are loaded immediately. To delay asset loading, set `manualStart: "false"`. After creation, append the element to the DOM using the asynchronous `appendTo` method. At the appropriate time, call `start()` on the element to begin loading. Any interaction with the `Skeleton` or `AnimationState` must be deferred until `whenReady` resolves.

### Dispose
Removing a web component from the DOM does not automatically dispose of it, as it may be intended for reuse elsewhere. To explicitly dispose it, call its `dispose()` method. This operation is safe and will not release resources still in use by other web components.

To dispose of all `spine-webcomponents` resources, call `dispose()` on the overlay instance.

The [`dispose.html`](/git/spine-runtimes/spine-ts/spine-webcomponents/example/dispose.html) shows how to use the `dispose` function.


### Manual overlays
When a <code>&lt;spine-skeleton&gt;</code> is added to the page, a <code>&lt;spine-overlay&gt;</code> is automatically appended to contain the WebGL canvas where skeletons are rendered. This overlay spans the entire browser viewport.

Alternatively, a <code>&lt;spine-overlay&gt;</code> can be manually appended to a specific HTML element. In this case, it inherits the size of its parent. To render a <code>&lt;spine-skeleton&gt;</code> into a manually defined overlay, assign the same `overlay-id` to both the skeleton and the overlay.

Regardless of where the overlay is placed within an HTML element, it will always reposition itself as the last child. This ensures the overlay remains on top of other elements.
To prevent unnecessary DOM detachments and reattachments, it is recommended to place the overlay as the last element inside the desired container.

Manual overlay creation is useful in these scenarios:
1. Scrollable containers
   - The skeleton may overflow the container until the container element is fully visible.
   - The skeleton may scroll with some lag, especially on displays with low refresh rates.
2. Fixed/sticky containers
   - The skeleton may scroll in a jerky or uneven manner.
3. Custom overlay placement
   - Useful when a smaller overlay is needed, or when the overlay should not be appended directly to the <code>&lt;body&gt;</code>, but to a specific container node.

These issues can be observed in the example below. The first scrollable list uses the default overlay, while the second uses a dedicated overlay inside the scrollable <code>&lt;div&gt;</code>. Clicking the button demonstrates the jerky scrolling behavior by setting the position of the container <code>&lt;div&gt;</code> to fixed.

<div class="example flex-column">
	<button id="popup-overlay-button-open">Set fixed position</button>
	<div style="height: 250px; display: flex;">
		<div id="fixed" style="display: flex;">
			<div style="overflow-y: auto; width: 150px; height: 250px; border: 1px solid black; padding: 1px; background: white;">
			<script>
				for (let i = 0; i < 6; i++)
					document.currentScript.parentElement.insertAdjacentHTML('beforeend', `
					<spine-skeleton style="height:80px; width: 120px; border: 1px solid black;"
						atlas="/files/spine-widget/assets/spineboy-pma.atlas"
						skeleton="/files/spine-widget/assets/spineboy-pro.skel"
						animation="walk"
						start-when-visible spinner
					></spine-skeleton>`);
			</script>
			</div>
			<div style="overflow-y: auto; width: 150px; height: 250px; border: 1px solid black; padding: 1px;  background: white;">
			<spine-overlay overlay-id="scroll"></spine-overlay>
			<script>
				for (let i = 0; i < 6; i++)
					document.currentScript.parentElement.insertAdjacentHTML('beforeend', `
					<spine-skeleton style="height:80px; width: 120px; border: 1px solid black;"
						overlay-id="scroll"
						atlas="/files/spine-widget/assets/spineboy-pma.atlas"
						skeleton="/files/spine-widget/assets/spineboy-pro.skel"
						animation="walk"
						start-when-visible spinner
					></spine-skeleton>`);
			</script>
			</div>
		</div>
	</div>
</div>
<script>
	let positionFixed = false;
	const openPopupButton = document.getElementById('popup-overlay-button-open');
	const popupOverlay = document.getElementById('fixed');
	openPopupButton.addEventListener('click', function() {
		if (positionFixed) {
			popupOverlay.style.position = "";
			popupOverlay.style.top = "";
			popupOverlay.style.background = "";
			openPopupButton.innerText = "Set fixed position";
		} else {
			popupOverlay.style.position = 'fixed';
			popupOverlay.style.top = 'calc(50% - 125px)';
			popupOverlay.style.background = 'white';
			openPopupButton.innerText = "Unset fixed position";
		}
		positionFixed = !positionFixed;
	});
</script>
!!

```html
<div style="display: flex; flex-direction: column;">
	<button id="popup-overlay-button-open">Set fixed position</button>
	<div style="height: 250px; display: flex;">
		<div id="fixed" style="display: flex;">
			<div style="overflow-y: auto; width: 150px; height: 250px; border: 1px solid black; padding: 1px; background: white;">
			<script>
				for (let i = 0; i < 6; i++)
					document.currentScript.parentElement.insertAdjacentHTML('beforeend', `
					<spine-skeleton style="height:80px; width: 120px; border: 1px solid black;"
						atlas="/files/spine-widget/assets/spineboy-pma.atlas"
						skeleton="/files/spine-widget/assets/spineboy-pro.skel"
						animation="walk"
					></spine-skeleton>`);
			</script>
			</div>
			<div style="overflow-y: auto; width: 150px; height: 250px; border: 1px solid black; padding: 1px;  background: white;">
			<spine-overlay overlay-id="scroll"></spine-overlay>
			<script>
				for (let i = 0; i < 6; i++)
					document.currentScript.parentElement.insertAdjacentHTML('beforeend', `
					<spine-skeleton style="height:80px; width: 120px; border: 1px solid black;"
						overlay-id="scroll"
						atlas="/files/spine-widget/assets/spineboy-pma.atlas"
						skeleton="/files/spine-widget/assets/spineboy-pro.skel"
						animation="walk"
					></spine-skeleton>`);
			</script>
			</div>
		</div>
	</div>
</div>
```

```js
let positionFixed = false;
const openPopupButton = document.getElementById('popup-overlay-button-open');
const popupOverlay = document.getElementById('fixed');
openPopupButton.addEventListener('click', function() {
	if (positionFixed) {
		popupOverlay.style.position = "";
		popupOverlay.style.top = "";
		popupOverlay.style.background = "";
		openPopupButton.innerText = "Set fixed position";
	} else {
		popupOverlay.style.position = 'fixed';
		popupOverlay.style.top = 'calc(50% - 125px)';
		popupOverlay.style.background = 'white';
		openPopupButton.innerText = "Unset fixed position";
	}
	positionFixed = !positionFixed;
});
```

Notice that each overlay creates its own WebGL contex, which counts against the maximum number of allowed WebGL contexts.