Spine Web Components
spine-webcomponents パッケージは、Spineアニメーションをウェブページに直接埋め込むためのウェブコンポーネント(カスタムHTML要素)を提供します。
HTMLページに <spine-skeleton> タグを追加すると、ライブラリは共有のWebGLキャンバス オーバーレイを作成し、複数のSpineスケルトンをレンダリングします。この設計により、ブラウザのWebGLコンテキスト数の制限を回避します。
Spine player と異なり、ウェブコンポーネントには再生を制御する組み込みUIは含まれていません。代わりに、HTML を通じて詳細に設定できる多くの属性を公開します。
エクスポート
Spine Web Componentsは、Spine playerと同じエクスポート形式を使用します。さらに、同じJSONファイル内に複数のスケルトンを含めることにも対応しています。
セットアップ
<spine-skeleton> ウェブコンポーネントをウェブサイトに追加する手順は簡単です。以下に概要を示します。
JavaScript の追加
spine-webcomponents パッケージには spine-webcomponents.js という JavaScript ファイルが含まれており、2 つの HTML カスタム要素 <spine-skeleton> と <spine-overlay>、および関連するユーティリティ関数群が定義されています。
上の例では、ファイルは高速な NPM CDN である UNPKG からロードされます。URLにはバージョン番号(4.2など)が含まれており、これはスケルトンをエクスポートするのに用いた Spine エディターのバージョンと一致している必要があります。パッチバージョンにワイルドカード(*)を使用すると、そのメジャー.マイナーバージョンの最新のJavaScriptコードが取得されます。
UNPKG CDN から縮小版をロードする場合は .min.js を使用してください:
あるいは、UNPKGからダウンロードして自己ホストするか、GitHub リポジトリ のソースからビルドして使用することもできます。リポジトリにはNPMやYarnでの利用方法に関する手順も含まれています。
spine-skeleton の使用
JavaScriptファイルをインポートした後は、追加のJavaScriptを書かなくてもHTML内でウェブコンポーネントを直接使用できます:
atlas="/files/spine-widget/assets/spineboy-pma.atlas"
skeleton="/files/spine-widget/assets/spineboy-pro.skel"
></spine-skeleton>
<spine-skeleton> 要素はスケルトンデータを /files/spineboy/export/spineboy-pro.skel から、アトラスを /files/spineboy/export/spineboy.atlas からロードします。アトラスは画像ファイル(spineboy.png)を参照しており、.atlas ファイルからの相対パスで /files/spineboy/export/spineboy.png がロードされます。
<spine-overlay> 要素はDOMの末尾に自動的に追加されます。このコンポーネントはページ全体に広がる透明な WebGL キャンバスを作成し、親コンテナ内の正しい位置にすべての <spine-skeleton> をレンダリングするために使用されます。ほとんどのユーザーはオーバーレイについて気にする必要はありません。
ウェブコンポーネントは親要素に合わせてスケールされたスケルトンをレンダリングします。
| 1 | 2 |
| 3 | 4 |
<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>!!
設定
<spine-skeleton> 要素は多数の設定属性を持ち、要件に合わせてカスタマイズできます。
JSON、バイナリ、アトラスの URL
必須の2つの属性 skeleton と atlas は、それぞれスケルトンの .json またはバイナリ .skel ファイルと .atlas ファイルのソースパスを指定します。これらのパスは相対または絶対 URL のいずれでも構いません。
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>
別ドメインの絶対 URL を使用する場合、ブラウザがアセットを読み込めないことがあります。これはアセットをホストするサーバーで CORS を有効にすることで解決できます。
データの埋め込み
URLからデータをロードする代わりに、.json/.skel、.atlas、および .png ファイルを raw-data 属性で直接埋め込むことができます。raw-data 属性はキーにアセット名、値に Base64 エンコードされた内容を持つ文字列化された JSON オブジェクトを受け取ります。skeleton と atlas 属性は、このオブジェクト内で使用した対応するアセット名を参照するように設定してください。raw-data 属性はこの構成を有効にします。
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 または class 属性を使用してください。これらの属性で任意のスタイルを適用できます。
.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スケルトンキー
リソースのリクエストを最小化するため、複数のスケルトンを単一のJSONファイルに埋め込むことができます。そのようなJSONを使用する場合、どのスケルトンを表示するかをウェブコンポーネントの json-skeleton-key 属性で指定します。spine-webcomponents のアセットマネージャーは、同じページで複数回使用されていても各アセットを効率的に一度だけロードします。
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 属性で指定できます:
atlas="/files/spine-widget/assets/spineboy-pma.atlas"
skeleton="/files/spine-widget/assets/spineboy-pro.skel"
animation="walk"
></spine-skeleton>
デフォルトスキン
デフォルトでは、ウェブコンポーネントはスケルトンのデフォルトスキン(Spineエディター上ではスキンに属していないアタッチメントを含んでいるスキン)を使用します。アクティブなスキンは skin 属性で明示的に設定できます:
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 はカンマ区切りのスキン名リストを受け付けます。指定された順序でスキンは組み合わされて新しいスキンが作られます。同じスロットに複数のスキンが影響を与える場合、リストの最後のスキンが優先されます。
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 属性に応じてスケルトンアニメーションをコンテナ内にフィットさせようとします。以下にいくつかの例を示します:
contain: コンテナ要素内にスケルトン全体が収まるようできるだけ大きく表示(デフォルト)fill: アスペクト比を保たずにコンテナ要素を埋める形で表示scaleDown: スケルトンがコンテナに収まるように縮小する(拡大はしない)none: コンテナ要素のサイズを無視してスケルトンを表示(ここではscale属性を `0.05` に設定)追加の fit モード:
width: コンテナの幅を埋める(縦方向にオーバーフローしてもよい)height: コンテナの高さを埋める(横方向にオーバーフローしてもよい)cover: コンテナ全体を覆うように、可能な限り小さく表示origin: バウンズに関係なくスケルトンの原点をコンテナの中心に合わせる
バウンズ
ウェブコンポーネントはスケルトンのバウンズ(bounds)を用いてコンテナ内にフィットさせます。スケルトンのバウンズはアニメーション(または複数のアニメーション)の軸方向外接矩形(AABB)であり、アニメーションが指定されていない場合はセットアップポーズのバウンズになります。指定されたフィットモードに従ってバウンズが親要素に収まるように、スケルトンの scaleX と scaleY が設定されます。そのためこれらのプロパティは手動で変更できません。scaleX と scaleY を直接制御したい場合は fit="none" または fit="origin" を設定し、以下に示すように JavaScript を通じてスケルトンオブジェクトにアクセスしてください。
スケール
スケルトンローダーのスケールは scale 属性で設定します。スケーリングについては Spine Runtimes Guide を参照してください。
この例では fit モードを none に設定してスケールの変化を確認しやすくしています(デフォルトの フィットモード の場合は scaleX と scaleY が上書きされます)。
scale="0.3"scale="0.2"scale="0.1"軸
x-axis と y-axis を使って、コンテナ要素の幅や高さに対する割合(パーセンテージ)でスケルトンを水平方向や垂直方向にシフトできます。
fit="none"scale=".2"x-axis=".25"fit="origin"scale=".2"fit="origin"scale=".2"y-axis="-.5"オフセット
offset-x と offset-y を使って、指定したピクセル数だけスケルトンを水平・垂直方向に移動できます。
offset-x="0" offset-y="0" offset-x="-100" offset-y="50" パディング
pad-left、pad-right、pad-top、pad-bottom を使ってコンテナ要素に仮想的なパディングを追加できます。左と右はコンテナ幅に対する割合、上と下はコンテナ高さに対する割合で指定します。
pad-left="0" pad-right="0" pad-top="0" pad-top="0" pad-left=".25" pad-right=".25" pad-top=".25" pad-top=".25" 識別子
ウェブコンポーネントに識別子(Identifier)を割り当てると、spine.getSkeleton 関数で取得できるようになり、JavaScript で Skeleton と AnimationState オブジェクトにアクセスしやすくなります。
<spine-skeleton> はアセットを取得するために非同期処理を行います。whenReady メソッドを使って Skeleton と AnimationState にアクセスできるタイミングを知ることができます。
atlas="/files/spine-widget/assets/raptor-pma.atlas"
skeleton="/files/spine-widget/assets/raptor-pro.skel"
identifier="raptor"
></spine-skeleton>
raptor.skeleton.color.set(1, 0, 0, 1);
クリップ
clip 属性を設定すると、コンテナ要素の外側にあるすべての描画が隠されます。
ただし、これを有効にするとスケルトン間のバッチングが壊れてしまう点に注意してください。
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>
カスタムバウンズ
特定のアニメーションの細部にフォーカスしたり、ズームアウト、カメラ移動をシミュレートするなどの目的でカスタムのバウンズを指定できます。
bounds-x、bounds-y、bounds-width、bounds-height 属性がカスタムバウンズを定義します。
以下の例ではCelesteの顔にフォーカスしていますが、スケルトンがコンテナ要素からはみ出すのを防ぐために clip 属性を設定しています。
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>
バウンズの自動計算
animation 属性を変更してアニメーションを切り替えると、ウェブコンポーネントは新しいアニメーションをロードした時、新規作成されたかのように切り替わります。ただし、新しいバウンズは auto-calculate-bounds 属性が設定されていない限り再計算されません。このデフォルトの動作はアニメーション間でスケルトンの寸法を一定に保ちたい場合には最適です。animation-bounds 属性と組み合わせると便利です。
auto-calculate-bounds設定無し auto-calculate-bounds設定有り 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>
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 属性は AnimationState のデフォルトのミックス時間を定義します。これは、animations 属性や AnimationState オブジェクトを用いる JavaScript コードでアニメーションを切り替える際に、アニメーション間をミックスする既定の秒数です。
default-mix="0" (デフォルト) default-mix="1" 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>
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 属性を使用できます。以下は先ほどの例をウェブコンポーネントの属性のみで再現したものです:
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>
animations 属性は角括弧で囲まれたグループの文字列を受け取ります(例: [...][...][...])。
各グループは再生されるアニメーションを定義し、パラメータはカンマ区切りで指定されます:
- track: アニメーションを再生するトラック番号
- animation name: アニメーション名
- loop:
trueを指定するとループ(省略時はループしない) - delay: 前のアニメーションが開始してから待つ秒数。
0は前のアニメーションが終了するまで待つ(省略時は0) - mixDuration: 前のアニメーションからこのアニメーションへミックスする秒数(省略時は
default-mixを使用。トラック最初のアニメーションには適用されない)
トラックの最後のアニメーション終了後にトラックをループさせたい場合は、特殊なグループ [loop, trackNumber, repeatDelay] を追加できます。ここで:
- loop: ループ命令であることを示す識別子
- trackNumber: ループするトラック番号
- repeatDelay: 最後のアニメーションが完了してからループを繰り返すまでの秒数(省略時は
0)
同じトラック番号に対する最初のグループは setAnimation に渡されます。同じトラックに対する以降のグループは addAnimation に渡されます。
setEmptyAnimation や addEmptyAnimation を使用するには、アニメーション名に #EMPTY# を指定してください。この場合 loop パラメータは無視されます。
以下の 2つの例を参照してください。
このSpineboyは animations 属性に以下の値を使用しています:
[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]
すべてのアニメーションは単一のトラックで再生されます。シーケンスの内訳は以下の通りです:
[loop, 0]: トラック0が終端に達したら先頭に戻るよう指示[0, idle, true]: idle アニメーションをループに設定[0, run, false, 2, 0.25]: 2秒後に run アニメーションをキューに追加し、0.25秒のミックスを使用[0, run]: 追加の run アニメーションをキュー(ループなし)[0, run]: さらにもう一つの run をキュー[0, run-to-idle, false, 0, 0.15]: run→idle の遷移をキュー(遅延なし、0.15秒のミックス)[0, idle, true]: 再び idle をループ[0, jump, false, 0, 0.15]: jump をキュー(遅延なし、0.15秒のミックス)[0, walk, false, 0, 0.05]: walk をキュー(遅延なし、0.05秒のミックス)[0, death, false, 0, 0.05]: death をキュー(遅延なし、0.05秒のミックス)
Celesteは animations 属性に以下の値を使用しています:
[loop, 1]
[1, #EMPTY#]
[1, eyeblink, false, 2]
この例では2つのトラックを使用しています。トラック0は wings-and-feet を再生し、トラック1はループして空のアニメーションの後に eyeblink を2秒の遅延で再生します。
上のテキストエリアは実験のために編集できます。編集して アニメーションを更新 をクリックしてみてください。例えば遅延を 2 から 0.5 に変更すると、瞬きがより頻繁になります。トラック0で、5秒後に swing をループ有りで開始し、0.5秒のミックスを行なうには、次を追加します: [0, swing, true, 5, 0.5]
アニメーションバウンズ
複数のアニメーションに基づくバウンズを定義するには、animation-bounds 属性を使用できます。この属性はアニメーションのリストを受け取り、それらすべてを包含するバウンズを計算します。
この方法によりアニメーション間で一貫したスケールが維持され、より大きなバウンズを持つアニメーションに切り替えた際にスケルトンがコンテナからはみ出すのを防げます。
animation-bounds無し animation-bounds="walk,jump" スピナー
spinner 属性を指定するとアセットのロード中にスピナーを表示できます。デフォルトではアセットのロード中には何も表示されません。下のボタンを押すと、1秒のロード遅延をシミュレートし、spinner 属性を切り替えることができます。
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="スピナー表示切り替え: OFF" onclick="toggleSpinner(this)" />
<input type="button" value="リロード" onclick="reloadWidget(this)" />
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 ? "スピナー表示切り替え: OFF" : "スピナー表示切り替え: ON";
}
オフスクリーン動作
オフスクリーン(画面外)にあるウェブコンポーネントはレンダリングされません。オフスクリーンの間、デフォルトでは AnimationState のupdate、Skeleton のupdate、skeleton.apply、skeleton.updateWorldTransform 関数は呼び出されません。これは offscreen=pause に相当します。
オフスクリーンでも更新関数を実行したい場合は offscreen=update を設定してください。
可視性に関係なくすべての関数を常に実行したい場合は offscreen=pose を設定してください。
pause update pose このページをリロードして3つのスケルトンがすべてビューポート内にあるとアニメーションは同期して開始されます。ただし最初のスケルトンは offscreen="pause" が設定されているため、画面外にスクロールすると状態が停止します。ビューポートに戻ると、その間経過した時間は進まず再開するため、他の2つと非同期になります。他の2つは同期を維持しますが、物理演算が関わる場合などにわずかな差異が生じることがあります。
オフスクリーンでスケルトンが停止するのを防ぐには update 動作を使用することを推奨します。これにより通常最も CPU を消費する updateWorldTransform の呼び出しを避けつつ更新を維持できます。
カスタム更新
ウェブコンポーネントの skeleton と state は他のランタイムと同様に更新および適用されます。
updateWorldTransform の呼び出しの前後にカスタムロジックを注入するには、beforeUpdateWorldTransforms と afterUpdateWorldTransforms を設定します。
デフォルトの更新動作を完全に置き換えるには update プロパティに関数を割り当ててください。これにより状態とスケルトンの両方の更新が置き換えられ、オフスクリーン最適化も上書きされます。この場合、update、apply、および updateWorldTransform の呼び出し管理は開発者の責任になります。 onScreen プロパティはコンポーネントが画面上にあるときに true になる便宜用のフラグです。
3 つの関数はすべて同じシグネチャを持ちます: (delta: number, skeleton: Skeleton, state: AnimationState) => void
ドラッグ
drag 属性を設定するとウェブコンポーネントでドラッグ機能を有効にできます。CPU 使用率が増加する可能性があるため、ドラッグ可能な挙動が必要な場合のみこの機能を使用することを推奨します。
ポインタ位置
ポインタ位置をさまざまな座標空間で取得するために、次のプロパティが使用できます:
spine-skeleton では:
pointerWorldXとpointerWorldY: スケルトン原点に対するポインタの x,y 座標(Spineのワールド座標)worldXとworldY: キャンバス / WebGL コンテキスト原点に対するスケルトン原点の x,y 座標(Spineのワールド座標)
spine-overlay では:
pointerCanvasXとpointerCanvasY: キャンバスの左上隅に対するポインタの x,y 座標(スクリーン座標)pointerWorldXとpointerWorldY: キャンバス / WebGL コンテキスト原点に対するポインタの x,y 座標(Spineのワールド座標)
これらのプロパティによりウェブコンポーネントとのインタラクティブな挙動が可能になります。以下の例ではフクロウの目がポインタを追いかけます。
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>
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;
}
インタラクションコールバック
interactive 属性を設定すると、ポインタ操作のコールバックをウェブコンポーネントにアタッチできます。
コールバックはウェブコンポーネントのバウンズ内での操作や、特定のスロットに対する操作の両方に反応できます。サポートされるイベント (PointerEventType) は down、up、enter、leave、move、drag です。
コールバックの追加方法:
pointerEventCallback: (event: PointerEventType) => voidを設定して、ウェブコンポーネントのバウンズ内でのポインタ操作を処理するaddPointerSlotEventCallback (slotRef: number | string | Slot, slotFunction: (slot: Slot, event: PointerEventType) => void)を呼び出して、指定したスロットのアタッチメント領域内でのポインタ操作を処理する
以下の例では:
pointerEventCallbackはenterでjumpを、leaveでwaveをトリガーします。addPointerSlotEventCallbackはhead-baseスロット(顔)にコールバックを追加します。アタッチメントがdownイベントを受け取ると、2つのティントセレクターで選択された色に基づいて通常色とダーク色が更新されます。
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;"/>
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 属性を設定するとデバッグモードが有効になり、以下の視覚的な指標が表示されます:
- スケルトンのワールド原点(緑)
- ルートボーンの位置(赤)
- バウンズ の矩形とその中心(青)
- ウェブコンポーネントがドラッグ可能に設定されている場合のドラッグ領域(半透明の赤)
この例では、原点と重ならないようにルートが移動されています。
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>
.then(({ skeleton }) => skeleton.getRootBone().x += 50);
ページ
複数のアトラスページがある場合(例: スキンごとに1ページある等)で、その一部のページだけを表示すればよいときは、pages 属性でロードするアトラスページを指定できます。ロードするページのインデックスをカンマ区切りで指定してください。
pages="0,6"pages="0,4"pages="0,1"テクスチャをプログラムからロードするには、pages 属性を空に設定します: pages=""。これによりスケルトンとアトラスデータはロードされますがテクスチャはロードされないため、後で手動でテクスチャをロードすることができます。
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)" />
const dragon = await spine.getSkeleton("dragon").whenReady;
if (!dragon.pages.includes(pageIndex)) {
dragon.pages.push(pageIndex);
dragon.loadTexturesInPagesAttribute();
}
}
スロットに追従
HTML要素をスロットに追従させることもできます。これはテキストなどの動的コンテンツをアニメーションに組み込むのに便利です。
followSlot 関数は次のパラメータで呼び出します:
-
追従する
Slotインスタンスまたはスロット名 -
追従させる
HTMLElement -
次のプロパティを持つオプションオブジェクト
followOpacity: 要素の不透明度をスロットのアルファにリンクしますfollowScale: 要素のスケールをスロットのスケールにリンクしますfollowRotation: 要素の回転をスロットの回転にリンクしますfollowVisibility: スロットにアタッチメントが表示されているかに応じて要素を表示/非表示にしますhideAttachment: 要素が視覚的にアタッチメントを置き換えるかのようにスロットのアタッチメントを非表示にします
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>
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 は他のSpine Web Components とも連携して機能します。ドラッグ中でも動作します。
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>
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);
高度な使用法
プログラムによる作成
<spine-skeleton> 要素は createSkeleton 関数を使ってプログラム的に作成できます。この関数はウェブコンポーネントの属性の camelCase 表記に対応したプロパティを持つオブジェクトを受け取ります。
<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>
または標準のDOM操作を使ってHTMLとして直接要素を挿入することもできます。
<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>
デフォルトではアセットは直ちにロードされます。ロードを遅らせたい場合は manualStart: "false" を設定してください。作成後は非同期の appendTo メソッドでDOMに追加し、適切なタイミングで要素の start() を呼んでロードを開始します。Skeleton や AnimationState への操作は whenReady が解決されるまで保留してください。
破棄
DOMからウェブコンポーネントを削除しても自動的に破棄されるわけではありません。再利用するために保持されることが想定されているからです。明示的に破棄するにはその要素の dispose() メソッドを呼び出してください。この操作は安全で、他のウェブコンポーネントによってまだ使用中のリソースは解放されません。
すべての spine-webcomponents リソースを破棄するには、オーバーレイインスタンスの dispose() を呼び出してください。
dispose.html には dispose 関数の使用例が示されています。
手動オーバーレイ
ページに <spine-skeleton> を追加すると、スケルトンをレンダリングする WebGL キャンバスを含む <spine-overlay> が自動的に追加されます。このオーバーレイはブラウザのビューポート全体に広がります。
代わりに特定の HTML 要素に <spine-overlay> を手動で追加することもできます。この場合、オーバーレイは親要素のサイズを継承します。手動で定義したオーバーレイに <spine-skeleton> をレンダリングするには、スケルトンとオーバーレイの両方に同じ overlay-id を設定してください。
オーバーレイがどの要素内に配置されても、常に最後の子要素として再配置されます。これによりオーバーレイが他の要素の上に表示され続けます。不要なDOMのデタッチや再アタッチを避けるため、オーバーレイは目的のコンテナ内で最後に配置することを推奨します。
手動オーバーレイ作成が有用なケース:
- スクロール可能なコンテナ
- コンテナ要素が完全に表示されるまでスケルトンがオーバーフローする可能性がある
- 低リフレッシュレートのディスプレイではスクロールにラグが生じることがある
- 固定/スティッキーなコンテナ
- スケルトンのスクロールがぎくしゃくしたり不均一になる可能性がある
- カスタムオーバーレイ配置
- 小さなオーバーレイが必要な場合や、オーバーレイを直接
<body>に追加したくない場合に便利
- 小さなオーバーレイが必要な場合や、オーバーレイを直接
以下の例でこれらの問題を観察できます。最初のスクロールリストはデフォルトオーバーレイを使用し、2番目はスクロール可能な <div> 内に専用オーバーレイを配置しています。ボタンをクリックするとコンテナ <div> の position を fixed にして、スクロール動作の違いを確認できます。
<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>
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 = "position を fixed に設定";
} else {
popupOverlay.style.position = 'fixed';
popupOverlay.style.top = 'calc(50% - 125px)';
popupOverlay.style.background = 'white';
openPopupButton.innerText = "fixed 設定を解除";
}
positionFixed = !positionFixed;
});
各オーバーレイは独自のWebGLコンテキストを作成するため、利用可能なWebGLコンテキストの最大数にカウントされる点に注意してください。