mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
Add followSlot method
This commit is contained in:
parent
f9d73920d2
commit
a636ef0964
14
spine-ts/spine-webgl/example/assets/pwd/button.atlas
Normal file
14
spine-ts/spine-webgl/example/assets/pwd/button.atlas
Normal file
@ -0,0 +1,14 @@
|
||||
button.png
|
||||
size:372,510
|
||||
filter:Linear,Linear
|
||||
Button
|
||||
bounds:220,2,400,150
|
||||
offsets:1,1,402,152
|
||||
rotate:90
|
||||
CLICK ME
|
||||
bounds:2,470,231,38
|
||||
offsets:1,1,233,40
|
||||
Shadow
|
||||
bounds:2,2,466,216
|
||||
offsets:9,9,484,234
|
||||
rotate:90
|
||||
139
spine-ts/spine-webgl/example/assets/pwd/button.json
Normal file
139
spine-ts/spine-webgl/example/assets/pwd/button.json
Normal file
@ -0,0 +1,139 @@
|
||||
{
|
||||
"skeleton": {
|
||||
"hash": "yil4eBV+4V0",
|
||||
"spine": "4.2.38",
|
||||
"x": -241,
|
||||
"y": -139,
|
||||
"width": 484,
|
||||
"height": 234,
|
||||
"images": "./images/",
|
||||
"audio": "./audio"
|
||||
},
|
||||
"bones": [
|
||||
{ "name": "root" },
|
||||
{ "name": "button-small", "parent": "root" },
|
||||
{ "name": "button-big", "parent": "button-small" },
|
||||
{ "name": "follower", "parent": "button-small", "rotation": 45, "x": 100, "y": 200 }
|
||||
],
|
||||
"slots": [
|
||||
{ "name": "Shadow", "bone": "button-small", "attachment": "Shadow" },
|
||||
{ "name": "button-big", "bone": "button-big", "attachment": "Button" },
|
||||
{ "name": "button-small", "bone": "button-small", "attachment": "Button" },
|
||||
{ "name": "CLICK ME", "bone": "button-small", "attachment": "CLICK ME" },
|
||||
{ "name": "follower", "bone": "follower" }
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"name": "default",
|
||||
"attachments": {
|
||||
"button-big": {
|
||||
"Button": { "width": 402, "height": 152 }
|
||||
},
|
||||
"button-small": {
|
||||
"Button": { "width": 402, "height": 152 }
|
||||
},
|
||||
"CLICK ME": {
|
||||
"CLICK ME": { "x": 0.5, "y": -1, "width": 233, "height": 40 }
|
||||
},
|
||||
"Shadow": {
|
||||
"Shadow": { "x": 1, "y": -22, "width": 484, "height": 234 }
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"animations": {
|
||||
"enhance-in": {
|
||||
"slots": {
|
||||
"button-big": {
|
||||
"rgba": [
|
||||
{ "color": "ffffffff" },
|
||||
{ "time": 0.3333, "color": "ffffff00" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"bones": {
|
||||
"button-big": {
|
||||
"scale": [
|
||||
{},
|
||||
{ "time": 0.3333, "x": 1.5, "y": 1.5 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"enhance-out": {
|
||||
"slots": {
|
||||
"button-big": {
|
||||
"rgba": [
|
||||
{ "color": "ffffff00" },
|
||||
{ "time": 0.3333, "color": "ffffffff" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"bones": {
|
||||
"button-big": {
|
||||
"scale": [
|
||||
{ "x": 1.5, "y": 1.5 },
|
||||
{ "time": 0.3333 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"idle": {
|
||||
"slots": {
|
||||
"button-big": {
|
||||
"attachment": [
|
||||
{}
|
||||
]
|
||||
},
|
||||
"Shadow": {
|
||||
"attachment": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"jump": {
|
||||
"bones": {
|
||||
"button-small": {
|
||||
"translate": [
|
||||
{
|
||||
"curve": [ 0.078, 0, 0.156, 0, 0.078, 0, 0.156, 10 ]
|
||||
},
|
||||
{
|
||||
"time": 0.2333,
|
||||
"y": 10,
|
||||
"curve": [ 0.311, 0, 0.389, 0, 0.311, 10, 0.389, 0 ]
|
||||
},
|
||||
{ "time": 0.4667 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"shadow-in": {
|
||||
"slots": {
|
||||
"Shadow": {
|
||||
"rgba": [
|
||||
{ "color": "ffffff00" },
|
||||
{ "time": 0.3333, "color": "ffffffff" }
|
||||
],
|
||||
"attachment": [
|
||||
{ "name": "Shadow" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"shadow-out": {
|
||||
"slots": {
|
||||
"Shadow": {
|
||||
"rgba": [
|
||||
{ "color": "ffffffff" },
|
||||
{ "time": 0.3333, "color": "ffffff00" }
|
||||
],
|
||||
"attachment": [
|
||||
{ "name": "Shadow" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
spine-ts/spine-webgl/example/assets/pwd/button.png
Normal file
BIN
spine-ts/spine-webgl/example/assets/pwd/button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,390 @@
|
||||
chibi-stickers-pro-pwd-test.png
|
||||
size:2047,501
|
||||
filter:Linear,Linear
|
||||
pma:true
|
||||
scale:0.5
|
||||
common/angry-mark
|
||||
bounds:762,2,42,41
|
||||
common/big-purple-fear
|
||||
bounds:1016,147,134,71
|
||||
offsets:0,0,134,72
|
||||
common/big-tear
|
||||
bounds:65,38,33,82
|
||||
common/eye-3
|
||||
bounds:934,245,15,26
|
||||
rotate:90
|
||||
common/eye-closed-happy
|
||||
bounds:131,217,25,9
|
||||
common/eye-dafault
|
||||
bounds:2019,398,22,21
|
||||
common/eye-equal
|
||||
bounds:934,228,25,15
|
||||
common/eye-fire
|
||||
bounds:1574,14,26,28
|
||||
rotate:90
|
||||
common/eye-half-open
|
||||
bounds:2019,348,26,16
|
||||
rotate:90
|
||||
common/eye-heart
|
||||
bounds:2019,473,26,23
|
||||
rotate:90
|
||||
common/eye-reverse-v
|
||||
bounds:682,355,26,16
|
||||
rotate:90
|
||||
common/eye-sideway-v
|
||||
bounds:682,413,21,23
|
||||
common/eye-slant-close
|
||||
bounds:2,196,23,16
|
||||
common/eye-small-dot
|
||||
bounds:689,339,14,14
|
||||
offsets:0,1,15,15
|
||||
common/eye-sparkle
|
||||
bounds:2006,12,30,29
|
||||
rotate:90
|
||||
common/eye-star
|
||||
bounds:1506,13,29,27
|
||||
common/eye-twirl
|
||||
bounds:682,438,21,23
|
||||
common/eye-u
|
||||
bounds:572,353,24,17
|
||||
common/eye-x
|
||||
bounds:2019,446,25,22
|
||||
rotate:90
|
||||
common/lamp
|
||||
bounds:867,230,47,65
|
||||
rotate:90
|
||||
common/mouth-3
|
||||
bounds:682,383,15,28
|
||||
common/mouth-bracket
|
||||
bounds:1731,201,34,11
|
||||
common/mouth-doubt
|
||||
bounds:934,262,26,15
|
||||
common/mouth-fangs
|
||||
bounds:1288,77,39,14
|
||||
common/mouth-line
|
||||
bounds:698,463,36,7
|
||||
rotate:90
|
||||
common/mouth-neutral
|
||||
bounds:1218,27,27,12
|
||||
common/mouth-o-tall
|
||||
bounds:1604,18,22,33
|
||||
rotate:90
|
||||
common/mouth-open-smile
|
||||
bounds:1468,18,36,22
|
||||
common/mouth-rectangle
|
||||
bounds:1537,19,35,21
|
||||
common/mouth-reverse-v
|
||||
bounds:284,57,27,10
|
||||
common/mouth-s
|
||||
bounds:366,76,41,11
|
||||
common/mouth-smile-little
|
||||
bounds:1672,21,33,19
|
||||
common/mouth-toungue-sticking-out
|
||||
bounds:1639,19,31,21
|
||||
common/mouth-u
|
||||
bounds:728,81,36,19
|
||||
common/mouth-v
|
||||
bounds:548,221,27,14
|
||||
common/mouth-x
|
||||
bounds:2019,376,21,20
|
||||
common/purple-fear-lines
|
||||
bounds:1418,12,48,28
|
||||
common/shadow
|
||||
bounds:955,44,111,29
|
||||
offsets:1,1,113,31
|
||||
common/small-drop-line
|
||||
bounds:303,198,16,17
|
||||
rotate:90
|
||||
common/small-purple-fear
|
||||
bounds:2,18,54,38
|
||||
common/tear
|
||||
bounds:140,228,20,19
|
||||
erikari/arm
|
||||
bounds:438,33,28,90
|
||||
rotate:90
|
||||
erikari/arm-shoulder-decoration
|
||||
bounds:153,98,32,43
|
||||
rotate:90
|
||||
erikari/back-hair
|
||||
bounds:2,317,158,141
|
||||
erikari/back-hair-long
|
||||
bounds:706,279,220,254
|
||||
rotate:90
|
||||
erikari/blush
|
||||
bounds:1126,21,29,18
|
||||
erikari/body
|
||||
bounds:460,63,70,98
|
||||
rotate:90
|
||||
erikari/bracelet
|
||||
bounds:249,69,33,11
|
||||
erikari/collar
|
||||
bounds:2,58,61,62
|
||||
erikari/ear
|
||||
bounds:1792,7,34,42
|
||||
rotate:90
|
||||
erikari/eyebrow
|
||||
bounds:303,170,19,12
|
||||
offsets:0,0,20,12
|
||||
erikari/hair-front
|
||||
bounds:1895,147,130,65
|
||||
erikari/hair-side
|
||||
bounds:258,82,43,132
|
||||
erikari/hat-border
|
||||
bounds:2,460,254,39
|
||||
erikari/hat-top
|
||||
bounds:1299,152,160,60
|
||||
erikari/head-base
|
||||
bounds:1154,204,143,125
|
||||
erikari/leg
|
||||
bounds:560,38,28,101
|
||||
rotate:90
|
||||
erikari/leg-decoration
|
||||
bounds:328,74,36,13
|
||||
erikari/skirt
|
||||
bounds:162,216,164,101
|
||||
erikari/strawberries-decoration
|
||||
bounds:560,68,112,56
|
||||
harri/arm
|
||||
bounds:850,35,28,90
|
||||
rotate:90
|
||||
harri/back-hair
|
||||
bounds:412,321,158,141
|
||||
harri/back-hair-long
|
||||
bounds:509,239,40,80
|
||||
harri/beard
|
||||
bounds:572,340,10,11
|
||||
harri/blush
|
||||
bounds:1095,21,29,18
|
||||
luke/blush
|
||||
bounds:1095,21,29,18
|
||||
nate/blush
|
||||
bounds:1095,21,29,18
|
||||
spineboy/blush
|
||||
bounds:1095,21,29,18
|
||||
harri/body
|
||||
bounds:855,65,70,98
|
||||
rotate:90
|
||||
harri/body-decoration
|
||||
bounds:1216,74,70,67
|
||||
harri/ear
|
||||
bounds:1748,7,34,42
|
||||
rotate:90
|
||||
soeren/ear
|
||||
bounds:1748,7,34,42
|
||||
rotate:90
|
||||
spineboy/ear
|
||||
bounds:1748,7,34,42
|
||||
rotate:90
|
||||
harri/eyebrow
|
||||
bounds:303,184,22,12
|
||||
harri/hair-front
|
||||
bounds:303,27,143,90
|
||||
harri/head-base
|
||||
bounds:1884,214,143,125
|
||||
luke/head-base
|
||||
bounds:1884,214,143,125
|
||||
soeren/head-base
|
||||
bounds:1884,214,143,125
|
||||
spineboy/head-base
|
||||
bounds:1884,214,143,125
|
||||
harri/leg
|
||||
bounds:1171,41,28,101
|
||||
rotate:90
|
||||
harri/sword
|
||||
bounds:962,220,185,82
|
||||
luke/arm
|
||||
bounds:192,35,28,90
|
||||
rotate:90
|
||||
luke/arm-shoulder-decoration
|
||||
bounds:754,250,31,27
|
||||
luke/back-hair
|
||||
bounds:1876,341,158,141
|
||||
rotate:90
|
||||
luke/body
|
||||
bounds:1116,71,70,98
|
||||
rotate:90
|
||||
luke/eyebrow
|
||||
bounds:1189,27,27,12
|
||||
nate/eyebrow
|
||||
bounds:1189,27,27,12
|
||||
spineboy/eyebrow
|
||||
bounds:1189,27,27,12
|
||||
luke/face-cover
|
||||
bounds:552,198,169,153
|
||||
rotate:90
|
||||
luke/glasses-shadow
|
||||
bounds:867,137,147,81
|
||||
luke/hair-decoration
|
||||
bounds:328,119,130,107
|
||||
luke/hair-front
|
||||
bounds:1294,93,122,57
|
||||
luke/leg
|
||||
bounds:1068,41,28,101
|
||||
rotate:90
|
||||
luke/shield
|
||||
bounds:162,354,88,104
|
||||
luke/skirt
|
||||
bounds:1879,12,81,31
|
||||
luke/sword
|
||||
bounds:2,122,102,71
|
||||
offsets:0,0,104,71
|
||||
mario/arm
|
||||
bounds:100,35,28,90
|
||||
rotate:90
|
||||
mario/back-hair
|
||||
bounds:1154,331,168,148
|
||||
rotate:90
|
||||
mario/back-hair-long
|
||||
bounds:460,135,86,91
|
||||
mario/beard
|
||||
bounds:706,70,147,93
|
||||
mario/blush
|
||||
bounds:1064,21,29,18
|
||||
mario/body
|
||||
bounds:1618,72,70,98
|
||||
rotate:90
|
||||
mario/ear
|
||||
bounds:1962,8,34,42
|
||||
rotate:90
|
||||
mario/eyebrow
|
||||
bounds:1352,23,32,17
|
||||
mario/hair-front
|
||||
bounds:1461,146,137,66
|
||||
mario/head-base
|
||||
bounds:1739,214,143,125
|
||||
mario/leg
|
||||
bounds:1521,42,28,101
|
||||
rotate:90
|
||||
misaki/arm
|
||||
bounds:1624,42,28,90
|
||||
rotate:90
|
||||
misaki/back-hair
|
||||
bounds:1733,341,158,141
|
||||
rotate:90
|
||||
misaki/back-hair-long
|
||||
bounds:962,304,190,195
|
||||
misaki/belt
|
||||
bounds:1274,19,76,26
|
||||
misaki/blush
|
||||
bounds:1836,22,29,18
|
||||
misaki/body
|
||||
bounds:1518,72,70,98
|
||||
rotate:90
|
||||
misaki/ear
|
||||
bounds:986,8,34,42
|
||||
rotate:90
|
||||
misaki/eyebrow
|
||||
bounds:1157,27,30,12
|
||||
misaki/glasses
|
||||
bounds:555,464,141,35
|
||||
misaki/glasses-side
|
||||
bounds:728,269,8,23
|
||||
rotate:90
|
||||
misaki/hair-front
|
||||
bounds:1152,143,140,59
|
||||
misaki/hair-side
|
||||
bounds:1277,42,47,140
|
||||
rotate:90
|
||||
misaki/head-base
|
||||
bounds:1594,214,143,125
|
||||
misaki/leg
|
||||
bounds:1418,42,28,101
|
||||
rotate:90
|
||||
misaki/skirt
|
||||
bounds:572,372,108,90
|
||||
nate/arm
|
||||
bounds:1748,43,28,90
|
||||
rotate:90
|
||||
nate/back-hair
|
||||
bounds:1590,341,158,141
|
||||
rotate:90
|
||||
nate/beard
|
||||
bounds:1600,144,147,68
|
||||
nate/body
|
||||
bounds:1749,73,70,98
|
||||
rotate:90
|
||||
nate/ear
|
||||
bounds:942,8,34,42
|
||||
rotate:90
|
||||
nate/glasses
|
||||
bounds:412,464,141,35
|
||||
nate/glasses-side
|
||||
bounds:2037,358,8,16
|
||||
nate/hair-front
|
||||
bounds:106,65,142,65
|
||||
nate/head-base
|
||||
bounds:1449,214,143,125
|
||||
nate/leg
|
||||
bounds:1879,45,28,101
|
||||
rotate:90
|
||||
sinisa/arm
|
||||
bounds:324,89,28,90
|
||||
rotate:90
|
||||
sinisa/back-hair
|
||||
bounds:1447,341,158,141
|
||||
rotate:90
|
||||
sinisa/beard
|
||||
bounds:709,33,139,45
|
||||
sinisa/blush
|
||||
bounds:1386,22,29,18
|
||||
sinisa/body
|
||||
bounds:1418,74,70,98
|
||||
rotate:90
|
||||
sinisa/body-decoration
|
||||
bounds:530,34,27,27
|
||||
sinisa/ear
|
||||
bounds:284,12,34,42
|
||||
sinisa/eyebrow
|
||||
bounds:791,83,38,18
|
||||
offsets:0,0,38,19
|
||||
sinisa/hair-front
|
||||
bounds:561,126,143,92
|
||||
sinisa/head-base
|
||||
bounds:1304,214,143,125
|
||||
sinisa/leg
|
||||
bounds:1718,41,28,101
|
||||
soeren/arm
|
||||
bounds:162,324,28,90
|
||||
rotate:90
|
||||
soeren/back-hair
|
||||
bounds:258,319,150,141
|
||||
soeren/beard
|
||||
bounds:1749,144,145,68
|
||||
soeren/blush
|
||||
bounds:787,259,29,18
|
||||
soeren/body
|
||||
bounds:1895,75,70,98
|
||||
rotate:90
|
||||
soeren/eyebrow
|
||||
bounds:838,151,27,12
|
||||
soeren/glasses
|
||||
bounds:258,462,152,37
|
||||
soeren/glasses-side
|
||||
bounds:509,230,7,20
|
||||
rotate:90
|
||||
soeren/glove
|
||||
bounds:955,82,42,53
|
||||
soeren/hair-front
|
||||
bounds:706,164,159,113
|
||||
soeren/leg
|
||||
bounds:1849,42,28,101
|
||||
spineboy/arm
|
||||
bounds:674,34,28,90
|
||||
spineboy/arm-decoration
|
||||
bounds:1030,13,32,29
|
||||
spineboy/arm-shoulder-decoration
|
||||
bounds:2019,421,23,23
|
||||
spineboy/back-hair
|
||||
bounds:1304,341,158,141
|
||||
rotate:90
|
||||
spineboy/body
|
||||
bounds:1016,75,70,98
|
||||
rotate:90
|
||||
spineboy/glasses
|
||||
bounds:328,228,179,89
|
||||
spineboy/glasses-shadow
|
||||
bounds:117,132,139,82
|
||||
spineboy/hair-front
|
||||
bounds:2,195,145,120
|
||||
spineboy/leg
|
||||
bounds:1995,44,29,101
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 745 KiB |
File diff suppressed because one or more lines are too long
@ -27,13 +27,14 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
.split-left, .split-right {
|
||||
width: 50%;
|
||||
min-height: 50%;
|
||||
min-height: 300px;
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
border: 1px solid salmon;
|
||||
@ -72,7 +73,7 @@
|
||||
|
||||
.split-top {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.split-bottom {
|
||||
@ -801,7 +802,7 @@
|
||||
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
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>
|
||||
|
||||
@ -1039,7 +1040,7 @@
|
||||
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
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>
|
||||
@ -1302,7 +1303,7 @@
|
||||
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
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>
|
||||
@ -1424,7 +1425,7 @@ skins.forEach((skin, i) => {
|
||||
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
When the widget (or the parent element) enters in the viewport, the callback <code>onScreenFunction</code> is invoked.
|
||||
<br>
|
||||
<br>
|
||||
@ -1617,7 +1618,7 @@ function loadPageDragon(pageIndex) {
|
||||
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
Widgets are not rendered while they are off screen.
|
||||
<br>
|
||||
<br>
|
||||
@ -1633,7 +1634,7 @@ function loadPageDragon(pageIndex) {
|
||||
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;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||||
<spine-widget
|
||||
atlas="assets/stretchyman-pma.atlas"
|
||||
skeleton="assets/stretchyman-pro.skel"
|
||||
@ -1642,7 +1643,7 @@ function loadPageDragon(pageIndex) {
|
||||
></spine-widget>
|
||||
</div>
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||||
<spine-widget
|
||||
identifier="stretchyman"
|
||||
atlas="assets/stretchyman-pma.atlas"
|
||||
@ -1720,7 +1721,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
||||
|
||||
<div class="split" style="width: 100%; flex-direction: column;">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
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>
|
||||
@ -1728,7 +1729,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
||||
Be aware that this will break batching!
|
||||
</div>
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||||
<spine-widget
|
||||
identifier="tank2"
|
||||
atlas="assets/tank-pma.atlas"
|
||||
@ -1737,7 +1738,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
||||
></spine-widget>
|
||||
</div>
|
||||
|
||||
<div class="split-left" style="width: 30%; box-sizing: border-box; height: 150px;">
|
||||
<div class="split-left" style="width: 30%; box-sizing: border-box; height: 150px; min-height: 0;">
|
||||
<spine-widget
|
||||
identifier="tank"
|
||||
atlas="assets/tank-pma.atlas"
|
||||
@ -1823,7 +1824,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
||||
-->
|
||||
<div class="section vertical-split">
|
||||
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box;">
|
||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||
More examples for <code>clip</code> attribute.
|
||||
</div>
|
||||
|
||||
@ -2659,6 +2660,426 @@ const darkPicker = document.getElementById("dark-picker");
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// start section //
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
<div class="section vertical-split">
|
||||
|
||||
<div class="split-top split">
|
||||
<div class="split-left">
|
||||
You can make your <code>HTMLElement</code>s follow slots. This feature is convenient when you need to generate dynamic text or content that integrates with your animation.
|
||||
|
||||
<p>
|
||||
Invoke the `followSlot` function that takes as input:
|
||||
<ol>
|
||||
<li>The <code>Slot</code> or the slot name to follow</li>
|
||||
<li>The <code>HTMLElement</code> that follows the slot</li>
|
||||
<li>
|
||||
An object with the following options:
|
||||
<ul>
|
||||
<li><code>followOpacity</code>: the element opacity is connected to the slot alpha</li>
|
||||
<li><code>followScale</code>: the element scale is connected to the slot scale</li>
|
||||
<li><code>followRotation</code>: the element rotation is connected to the slot rotation</li>
|
||||
<li><code>followAttachmentAttach</code>: the element is shown/hidden depending if the slots contains an attachment or not</li>
|
||||
<li><code>hideAttachment</code>: the slot attachment is hidden as if the element replaced the attachment</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
<div class="split-right">
|
||||
<spine-widget
|
||||
identifier="potty"
|
||||
atlas="assets/cloud-pot-pma.atlas"
|
||||
skeleton="assets/cloud-pot.skel"
|
||||
animation="playing-in-the-rain"
|
||||
></spine-widget>
|
||||
</div>
|
||||
</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 widget = await spine.getSpineWidget("potty").loadingPromise;
|
||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div class="split-bottom">
|
||||
<pre><code id="code-display">
|
||||
<script>escapeHTMLandInject(`
|
||||
<spine-widget
|
||||
identifier="potty"
|
||||
atlas="assets/cloud-pot-pma.atlas"
|
||||
skeleton="assets/cloud-pot.skel"
|
||||
animation="playing-in-the-rain"
|
||||
></spine-widget>
|
||||
|
||||
<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>
|
||||
|
||||
...
|
||||
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty").loadingPromise;
|
||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
})();`);</script>
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// end section //
|
||||
///////
|
||||
-->
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// start section //
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
<div class="section vertical-split">
|
||||
|
||||
<div class="split-top split">
|
||||
<div class="split-left">
|
||||
`followSlot` works even with other spine widgets! It works even if you drag it :D
|
||||
</div>
|
||||
<div class="split-right">
|
||||
<spine-widget
|
||||
identifier="potty2"
|
||||
atlas="assets/cloud-pot-pma.atlas"
|
||||
skeleton="assets/cloud-pot.skel"
|
||||
animation="rain"
|
||||
isdraggable
|
||||
></spine-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<spine-widget identifier="potty2-1" atlas="assets/raptor-pma.atlas" skeleton="assets/raptor-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-widget>
|
||||
<spine-widget identifier="potty2-2" atlas="assets/spineboy-pma.atlas" skeleton="assets/spineboy-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-widget>
|
||||
<spine-widget identifier="potty2-3" atlas="assets/celestial-circus-pma.atlas" skeleton="assets/celestial-circus-pro.skel" animation="wings-and-feet" style="height:200px; width: 200px;"></spine-widget>
|
||||
<spine-widget identifier="potty2-4" atlas="assets/goblins-pma.atlas" skeleton="assets/goblins-pro.skel" skin="goblingirl" animation="walk" style="height:200px; width: 200px;"></spine-widget>
|
||||
|
||||
<script>
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty2").loadingPromise;
|
||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div class="split-bottom">
|
||||
<pre><code id="code-display">
|
||||
<script>escapeHTMLandInject(`
|
||||
<spine-widget
|
||||
identifier="potty2"
|
||||
atlas="assets/cloud-pot-pma.atlas"
|
||||
skeleton="assets/cloud-pot.skel"
|
||||
animation="playing-in-the-rain"
|
||||
></spine-widget>
|
||||
|
||||
<spine-widget identifier="potty2-1" atlas="assets/raptor-pma.atlas" skeleton="assets/raptor-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-widget>
|
||||
<spine-widget identifier="potty2-2" atlas="assets/spineboy-pma.atlas" skeleton="assets/spineboy-pro.skel" animation="walk" style="height:200px; width: 200px;"></spine-widget>
|
||||
<spine-widget identifier="potty2-3" atlas="assets/celestial-circus-pma.atlas" skeleton="assets/celestial-circus-pro.skel" animation="wings-and-feet" style="height:200px; width: 200px;"></spine-widget>
|
||||
<spine-widget identifier="potty2-4" atlas="assets/goblins-pma.atlas" skeleton="assets/goblins-pro.skel" skin="goblingirl" animation="walk" style="height:200px; width: 200px;"></spine-widget>
|
||||
|
||||
...
|
||||
|
||||
(async () => {
|
||||
const widget = await spine.getSpineWidget("potty2").loadingPromise;
|
||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true });
|
||||
})();`);</script>
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// end section //
|
||||
///////
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// start section //
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
<div class="section vertical-split">
|
||||
|
||||
<div class="split-top split">
|
||||
<div class="split-left">
|
||||
A login UI made using the chibi stickers and a button made using Spine.
|
||||
<p>
|
||||
The chibi sticker does the following:
|
||||
<ul>
|
||||
<li>It looks at the cursor when no input field is selected</li>
|
||||
<li>Look at the caret when username input field is selected</li>
|
||||
<li>Cover its eyes when password input field is selected</li>
|
||||
<li>React in two different ways depending on the password</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The button does the following:
|
||||
<ul>
|
||||
<li>Starts some animation when cursor enters, leaves, stays, or click the button</li>
|
||||
<li>Appends a div containing the <code>LOGIN</code> text to a slot</li>
|
||||
<li>Submits the form on click</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<span id="ruler" style="visibility: hidden; white-space: nowrap; position: absolute"></span>
|
||||
<div style="background-color: white; width: 350px; padding: 30px; text-align: center;">
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<div style="width: 150px; height:150px; border-radius: 5%; border: 1px solid rgb(113, 113, 113); background-color: rgb(211, 211, 211); margin-bottom: 30px;">
|
||||
<spine-widget
|
||||
identifier="spineboy-login"
|
||||
atlas="assets/pwd/chibi-stickers-pro-pwd-test.atlas"
|
||||
skeleton="assets/pwd/chibi-stickers.json"
|
||||
skin="spineboy"
|
||||
bounds-x="-177"
|
||||
bounds-y="238"
|
||||
bounds-width="364"
|
||||
bounds-height="412"
|
||||
animation="interactive/head/idle"
|
||||
clip
|
||||
isinteractive
|
||||
></spine-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group" style="margin-bottom: 15px;"><select id="skinSelect"></select></div>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="input-group" style="margin-bottom: 15px;">
|
||||
<input style="width: 100%; padding: 10px; box-sizing: border-box;" type="text" id="username" name="username" placeholder="Username" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="input-group" style="margin-bottom: 15px;">
|
||||
<input style="width: 100%; padding: 10px; box-sizing: border-box;" type="password" id="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px; color: black">Password is <code>password</code></div>
|
||||
|
||||
<div style="height: 75px; cursor: pointer;">
|
||||
<div id="button-text" style="font-size: xx-large; cursor: pointer; user-select: none; display: none;"> LOGIN </div>
|
||||
<spine-widget
|
||||
identifier="button-login"
|
||||
atlas="assets/pwd/button.atlas"
|
||||
skeleton="assets/pwd/button.json"
|
||||
animation="idle"
|
||||
isinteractive
|
||||
fit="fill"
|
||||
></spine-widget>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const mouseX = Smooth(0, 200);
|
||||
const mouseY = Smooth(0, 200);
|
||||
|
||||
(async () => {
|
||||
|
||||
const form = document.getElementById('loginForm');
|
||||
const widgetButton = spine.getSpineWidget("button-login");
|
||||
await widgetButton.loadingPromise;
|
||||
|
||||
widgetButton.skeleton.color.set(.85, .85, .85, 1);
|
||||
widgetButton.cursorBoundsEventCallback = (event) => {
|
||||
if (event === "enter") {
|
||||
widgetButton.state.setAnimation(0, "enhance-in", false);
|
||||
widgetButton.state.setAnimation(1, "shadow-in", false);
|
||||
widgetButton.state.setAnimation(2, "jump", true);
|
||||
};
|
||||
if (event === "leave") {
|
||||
widgetButton.state.setAnimation(0, "enhance-out", false).timeScale = 2;
|
||||
widgetButton.state.setAnimation(1, "shadow-out", false);
|
||||
widgetButton.state.setEmptyAnimation(2, 0).mixDuration = .25;
|
||||
};
|
||||
if (event === "down") {
|
||||
widgetButton.state.setAnimation(0, "enhance-in", false).timeScale = 2;
|
||||
if (form.reportValidity()) {
|
||||
if (passwordInput.value === "password") {
|
||||
widget.state.setAnimation(0, "interactive/pwd/hooray", 0);
|
||||
widget.state.addAnimation(0, "interactive/head/idle", true);
|
||||
} else {
|
||||
widget.state.setAnimation(0, "interactive/pwd/sad", 0);
|
||||
widget.state.addAnimation(0, "interactive/head/idle", true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const textSlot = widgetButton.skeleton.findSlot("CLICK ME");
|
||||
textSlot.setAttachment(null);
|
||||
|
||||
|
||||
const divText = document.getElementById("button-text");
|
||||
|
||||
widgetButton.followSlot("CLICK ME", divText, { followScale: false });
|
||||
|
||||
const widget = spine.getSpineWidget("spineboy-login");
|
||||
await widget.loadingPromise;
|
||||
|
||||
// default settings
|
||||
widget.state.data.defaultMix = 0.15;
|
||||
|
||||
// Skin selection
|
||||
const skinSelect = document.getElementById('skinSelect');
|
||||
widget.skeleton.data.skins.forEach(({ name }) => {
|
||||
if (name === "default") return;
|
||||
skinSelect.add(new Option(name, name, name === "spineboy"));
|
||||
});
|
||||
skinSelect.value = "spineboy";
|
||||
skinSelect.addEventListener('change', (e) => widget.skin = e.target.value);
|
||||
|
||||
// click on spineboy
|
||||
widget.cursorBoundsEventCallback = (event) => {
|
||||
if (event === "down") {
|
||||
widget.state.setAnimation(0, "interactive/pwd/touch", false);
|
||||
widget.state.addAnimation(0, "interactive/head/idle", true);
|
||||
}
|
||||
if (event === "enter") widget.state.setAnimation(0, "emotes/wave", true);
|
||||
if (event === "leave") widget.state.setAnimation(0, "interactive/head/idle", false);
|
||||
}
|
||||
|
||||
// Head follow logic
|
||||
let focusInput = false;
|
||||
|
||||
// Password input field
|
||||
const passwordInput = document.getElementById('password');
|
||||
passwordInput.addEventListener('focus', () => {
|
||||
focusInput = true;
|
||||
resetBlendTracks();
|
||||
widget.state.setAnimation(0, "interactive/pwd/hide", 0);
|
||||
});
|
||||
passwordInput.addEventListener('blur', () => {
|
||||
focusInput = false;
|
||||
widget.state.setAnimation(0, "interactive/head/idle", 0);
|
||||
});
|
||||
|
||||
// Username input field
|
||||
const usernameInput = document.getElementById('username');
|
||||
usernameInput.addEventListener('input', () => {
|
||||
setBlendTracks();
|
||||
});
|
||||
usernameInput.addEventListener('focus', () => {
|
||||
focusInput = true;
|
||||
setBlendTracks();
|
||||
});
|
||||
usernameInput.addEventListener('blur', () => {
|
||||
focusInput = false;
|
||||
setBlendTracks();
|
||||
});
|
||||
|
||||
// Animation left/down blending
|
||||
const ALPHA_LEFT_RANGE = 4;
|
||||
const ALPHA_DOWN = .8;
|
||||
const ALPHA_DOWN_RANGE = 2;
|
||||
const moveVector = [0, 0];
|
||||
const leftTrack = widget.state.setAnimation(1, "interactive/head/left", true);
|
||||
const downTrack = widget.state.setAnimation(2, "interactive/head/down", true);
|
||||
leftTrack.mixBlend = spine.MixBlend.add;
|
||||
downTrack.mixBlend = spine.MixBlend.add;
|
||||
|
||||
const setBlendTracks = () => {
|
||||
moveVector[0] = getAlphaLeft();
|
||||
moveVector[1] = ALPHA_DOWN;
|
||||
}
|
||||
const resetBlendTracks = () => {
|
||||
moveVector[0] = 0;
|
||||
moveVector[1] = 0;
|
||||
}
|
||||
resetBlendTracks();
|
||||
|
||||
const getWidthOfText = (txt) => {
|
||||
const ruler = document.getElementById("ruler");
|
||||
ruler.innerHTML = txt;
|
||||
return ruler.offsetWidth;
|
||||
}
|
||||
|
||||
const getAlphaLeft = () => {
|
||||
const { width } = usernameInput.getBoundingClientRect();
|
||||
const length = getWidthOfText(usernameInput.value);
|
||||
const alpha = Math.min(1, length / width) * 2
|
||||
return 1 - alpha;
|
||||
};
|
||||
|
||||
widget.afterUpdateWorldTransforms = () => {
|
||||
if (focusInput) {
|
||||
blendTo(moveVector[0], moveVector[1]);
|
||||
} else {
|
||||
const { x, y, width, height} = widget.getHTMLElementReference().getBoundingClientRect();
|
||||
const l = -((widget.overlay.cursorCanvasX - (x + width)) / window.innerWidth) * ALPHA_LEFT_RANGE;
|
||||
const d = ((widget.overlay.cursorCanvasY - (y + height)) / window.innerHeight) * ALPHA_DOWN_RANGE;
|
||||
blendTo(l, d);
|
||||
}
|
||||
};
|
||||
|
||||
const blendTo = (x, y) => {
|
||||
leftTrack.alpha = mouseX(x);
|
||||
downTrack.alpha = mouseY(y);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="split-bottom">
|
||||
<pre><code id="code-display">
|
||||
<script>escapeHTMLandInject(`
|
||||
TODO`
|
||||
);</script>
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// end section //
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
|
||||
<script>
|
||||
spine.SpineWebComponentWidget.SHOW_FPS = true;
|
||||
|
||||
@ -55,6 +55,7 @@ import {
|
||||
Slot,
|
||||
RegionAttachment,
|
||||
MeshAttachment,
|
||||
Bone,
|
||||
} from "./index.js";
|
||||
|
||||
interface Point {
|
||||
@ -708,7 +709,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback () {
|
||||
connectedCallback (): void {
|
||||
if (this.disposed) {
|
||||
throw new Error("You cannot attach a disposed widget");
|
||||
};
|
||||
@ -1075,6 +1076,38 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
|
||||
* Other utilities
|
||||
*/
|
||||
|
||||
public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followAttachmentAttach: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = [];
|
||||
public followSlot(slotName: string | Slot, element: HTMLElement, options: { followAttachmentAttach?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) {
|
||||
const {
|
||||
followAttachmentAttach = false,
|
||||
followRotation = true,
|
||||
followOpacity = true,
|
||||
followScale = true,
|
||||
hideAttachment = false,
|
||||
} = options;
|
||||
|
||||
const slot = typeof slotName === 'string' ? this.skeleton?.findSlot(slotName) : slotName;
|
||||
if (!slot) return;
|
||||
|
||||
if (hideAttachment) {
|
||||
slot.setAttachment(null);
|
||||
}
|
||||
|
||||
element.style.position = 'absolute';
|
||||
element.style.top = '0px';
|
||||
element.style.left = '0px';
|
||||
element.style.display = 'none';
|
||||
|
||||
this.boneFollowerList.push({ slot, bone: slot.bone, element, followAttachmentAttach, followRotation, followOpacity, followScale, hideAttachment });
|
||||
this.overlay.slotFollowerElementsHolder.appendChild(element);
|
||||
}
|
||||
public unfollowSlot(element: HTMLElement): HTMLElement | undefined {
|
||||
const index = this.boneFollowerList.findIndex(e => e.element === element);
|
||||
if (index > -1) {
|
||||
return this.boneFollowerList.splice(index, 1)[0].element;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateAnimationViewport (animation?: Animation): Rectangle {
|
||||
const renderer = this.overlay.renderer;
|
||||
const { skeleton } = this;
|
||||
@ -1230,6 +1263,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
|
||||
private root: ShadowRoot;
|
||||
|
||||
private div: HTMLDivElement;
|
||||
public slotFollowerElementsHolder: HTMLDivElement;
|
||||
private canvas: HTMLCanvasElement;
|
||||
private fps: HTMLSpanElement;
|
||||
private fpsAppended = false;
|
||||
@ -1263,12 +1297,21 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
|
||||
this.root.appendChild(this.div);
|
||||
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.slotFollowerElementsHolder = document.createElement("div");
|
||||
|
||||
this.div.appendChild(this.canvas);
|
||||
this.canvas.style.position = "absolute";
|
||||
this.canvas.style.top = "0";
|
||||
this.canvas.style.left = "0";
|
||||
|
||||
this.div.appendChild(this.slotFollowerElementsHolder);
|
||||
this.slotFollowerElementsHolder.style.position = "absolute";
|
||||
this.slotFollowerElementsHolder.style.top = "0";
|
||||
this.slotFollowerElementsHolder.style.left = "0";
|
||||
this.slotFollowerElementsHolder.style.whiteSpace = "nowrap";
|
||||
this.slotFollowerElementsHolder.style.setProperty("pointer-events", "none");
|
||||
this.slotFollowerElementsHolder.style.transform = `translate(0px,0px)`;
|
||||
|
||||
this.canvas.style.setProperty("pointer-events", "none");
|
||||
this.canvas.style.transform = `translate(0px,0px)`;
|
||||
// this.canvas.style.setProperty("will-change", "transform"); // performance seems to be even worse with this uncommented
|
||||
@ -1425,6 +1468,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
|
||||
}
|
||||
}
|
||||
|
||||
private tempFollowBoneVector = new Vector3();
|
||||
private startRenderingLoop () {
|
||||
if (this.running) return;
|
||||
|
||||
@ -1660,6 +1704,41 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
|
||||
renderer.end();
|
||||
}
|
||||
|
||||
const updateFollowSlotsPosition = () => {
|
||||
this.skeletonList.forEach((widget) => {
|
||||
if (widget.skeleton && widget.onScreen) {
|
||||
widget.boneFollowerList.forEach(({ slot, bone, element, followAttachmentAttach, followRotation, followOpacity, followScale, hideAttachment }) => {
|
||||
|
||||
this.worldToScreen(this.tempFollowBoneVector, bone.worldX + widget.worldX, bone.worldY + widget.worldY);
|
||||
|
||||
if (Number.isNaN(this.tempFollowBoneVector.x)) return;
|
||||
|
||||
let x = this.tempFollowBoneVector.x - this.overflowLeftSize;
|
||||
let y = this.tempFollowBoneVector.y - this.overflowTopSize;
|
||||
|
||||
if (!this.scrollable) {
|
||||
x += window.scrollX;
|
||||
y += window.scrollY;
|
||||
}
|
||||
|
||||
element.style.transform = `translate(calc(-50% + ${x.toFixed(2)}px),calc(-50% + ${y.toFixed(2)}px))`
|
||||
+ (followRotation ? ` rotate(${-bone.getWorldRotationX()}deg)` : "")
|
||||
+ (followScale ? ` scale(${bone.getWorldScaleX()}, ${bone.getWorldScaleY()})` : "")
|
||||
;
|
||||
|
||||
element.style.display = ""
|
||||
|
||||
if (followAttachmentAttach && !slot.attachment) {
|
||||
element.style.opacity = "0";
|
||||
} else if (followOpacity) {
|
||||
element.style.opacity = `${slot.color.a}`;
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const loop = () => {
|
||||
if (this.disposed || !this.isConnected) {
|
||||
this.running = false;
|
||||
@ -1671,6 +1750,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
|
||||
this.translateCanvas();
|
||||
updateWidgets();
|
||||
renderWidgets();
|
||||
updateFollowSlotsPosition();
|
||||
}
|
||||
|
||||
requestAnimationFrame(loop);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user