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>、および関連するユーティリティ関数群が定義されています。

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

上の例では、ファイルは高速な NPM CDN である UNPKG からロードされます。URLにはバージョン番号(4.2など)が含まれており、これはスケルトンをエクスポートするのに用いた Spine エディターのバージョンと一致している必要があります。パッチバージョンにワイルドカード(*)を使用すると、そのメジャー.マイナーバージョンの最新のJavaScriptコードが取得されます。

UNPKG CDN から縮小版をロードする場合は .min.js を使用してください:

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

あるいは、UNPKGからダウンロードして自己ホストするか、GitHub リポジトリ のソースからビルドして使用することもできます。リポジトリにはNPMやYarnでの利用方法に関する手順も含まれています。

spine-skeleton の使用

JavaScriptファイルをインポートした後は、追加のJavaScriptを書かなくてもHTML内でウェブコンポーネントを直接使用できます:

html
<spine-skeleton
   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
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>!!

設定

<spine-skeleton> 要素は多数の設定属性を持ち、要件に合わせてカスタマイズできます。

JSON、バイナリ、アトラスの URL

必須の2つの属性 skeletonatlas は、それぞれスケルトンの .json またはバイナリ .skel ファイルと .atlas ファイルのソースパスを指定します。これらのパスは相対または絶対 URL のいずれでも構いません。

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>

別ドメインの絶対 URL を使用する場合、ブラウザがアセットを読み込めないことがあります。これはアセットをホストするサーバーで CORS を有効にすることで解決できます。

データの埋め込み

URLからデータをロードする代わりに、.json/.skel.atlas、および .png ファイルを raw-data 属性で直接埋め込むことができます。raw-data 属性はキーにアセット名、値に Base64 エンコードされた内容を持つ文字列化された JSON オブジェクトを受け取ります。skeletonatlas 属性は、このオブジェクト内で使用した対応するアセット名を参照するように設定してください。raw-data 属性はこの構成を有効にします。

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 または class 属性を使用してください。これらの属性で任意のスタイルを適用できます。

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スケルトンキー

リソースのリクエストを最小化するため、複数のスケルトンを単一のJSONファイルに埋め込むことができます。そのようなJSONを使用する場合、どのスケルトンを表示するかをウェブコンポーネントの json-skeleton-key 属性で指定します。spine-webcomponents のアセットマネージャーは、同じページで複数回使用されていても各アセットを効率的に一度だけロードします。

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 属性で指定できます:

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

デフォルトスキン

デフォルトでは、ウェブコンポーネントはスケルトンのデフォルトスキン(Spineエディター上ではスキンに属していないアタッチメントを含んでいるスキン)を使用します。アクティブなスキンは skin 属性で明示的に設定できます:

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 はカンマ区切りのスキン名リストを受け付けます。指定された順序でスキンは組み合わされて新しいスキンが作られます。同じスロットに複数のスキンが影響を与える場合、リストの最後のスキンが優先されます。

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 属性に応じてスケルトンアニメーションをコンテナ内にフィットさせようとします。以下にいくつかの例を示します:

contain: コンテナ要素内にスケルトン全体が収まるようできるだけ大きく表示(デフォルト)
fill: アスペクト比を保たずにコンテナ要素を埋める形で表示
scaleDown: スケルトンがコンテナに収まるように縮小する(拡大はしない)
none: コンテナ要素のサイズを無視してスケルトンを表示(ここではscale属性を `0.05` に設定)

追加の fit モード:

  • width: コンテナの幅を埋める(縦方向にオーバーフローしてもよい)
  • height: コンテナの高さを埋める(横方向にオーバーフローしてもよい)
  • cover: コンテナ全体を覆うように、可能な限り小さく表示
  • origin: バウンズに関係なくスケルトンの原点をコンテナの中心に合わせる

バウンズ

ウェブコンポーネントはスケルトンのバウンズ(bounds)を用いてコンテナ内にフィットさせます。スケルトンのバウンズはアニメーション(または複数のアニメーション)の軸方向外接矩形(AABB)であり、アニメーションが指定されていない場合はセットアップポーズのバウンズになります。指定されたフィットモードに従ってバウンズが親要素に収まるように、スケルトンの scaleXscaleY が設定されます。そのためこれらのプロパティは手動で変更できません。scaleXscaleY を直接制御したい場合は fit="none" または fit="origin" を設定し、以下に示すように JavaScript を通じてスケルトンオブジェクトにアクセスしてください。

スケール

スケルトンローダーのスケールは scale 属性で設定します。スケーリングについては Spine Runtimes Guide を参照してください。

この例では fit モードを none に設定してスケールの変化を確認しやすくしています(デフォルトの フィットモード の場合は scaleXscaleY が上書きされます)。

scale="0.3"
scale="0.2"
scale="0.1"

x-axisy-axis を使って、コンテナ要素の幅や高さに対する割合(パーセンテージ)でスケルトンを水平方向や垂直方向にシフトできます。

fit="none"
scale=".2"
x-axis=".25"
fit="origin"
scale=".2"
fit="origin"
scale=".2"
y-axis="-.5"

オフセット

offset-xoffset-y を使って、指定したピクセル数だけスケルトンを水平・垂直方向に移動できます。

offset-x="0"
offset-y="0"
offset-x="-100"
offset-y="50"

パディング

pad-leftpad-rightpad-toppad-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 で SkeletonAnimationState オブジェクトにアクセスしやすくなります。

<spine-skeleton> はアセットを取得するために非同期処理を行います。whenReady メソッドを使って SkeletonAnimationState にアクセスできるタイミングを知ることができます。

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 属性を設定すると、コンテナ要素の外側にあるすべての描画が隠されます。

ただし、これを有効にするとスケルトン間のバッチングが壊れてしまう点に注意してください。

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>

カスタムバウンズ

特定のアニメーションの細部にフォーカスしたり、ズームアウト、カメラ移動をシミュレートするなどの目的でカスタムのバウンズを指定できます。

bounds-xbounds-ybounds-widthbounds-height 属性がカスタムバウンズを定義します。

以下の例ではCelesteの顔にフォーカスしていますが、スケルトンがコンテナ要素からはみ出すのを防ぐために clip 属性を設定しています。

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>

バウンズの自動計算

animation 属性を変更してアニメーションを切り替えると、ウェブコンポーネントは新しいアニメーションをロードした時、新規作成されたかのように切り替わります。ただし、新しいバウンズauto-calculate-bounds 属性が設定されていない限り再計算されません。このデフォルトの動作はアニメーション間でスケルトンの寸法を一定に保ちたい場合には最適です。animation-bounds 属性と組み合わせると便利です。

auto-calculate-bounds設定無し
auto-calculate-bounds設定有り
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 属性は AnimationState のデフォルトのミックス時間を定義します。これは、animations 属性や AnimationState オブジェクトを用いる JavaScript コードでアニメーションを切り替える際に、アニメーション間をミックスする既定の秒数です。

default-mix="0" (デフォルト)
default-mix="1"
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 属性を使用できます。以下は先ほどの例をウェブコンポーネントの属性のみで再現したものです:

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>

animations 属性は角括弧で囲まれたグループの文字列を受け取ります(例: [...][...][...])。

各グループは再生されるアニメーションを定義し、パラメータはカンマ区切りで指定されます:

  • track: アニメーションを再生するトラック番号
  • animation name: アニメーション名
  • loop: true を指定するとループ(省略時はループしない)
  • delay: 前のアニメーションが開始してから待つ秒数。0 は前のアニメーションが終了するまで待つ(省略時は 0
  • mixDuration: 前のアニメーションからこのアニメーションへミックスする秒数(省略時は default-mix を使用。トラック最初のアニメーションには適用されない)

トラックの最後のアニメーション終了後にトラックをループさせたい場合は、特殊なグループ [loop, trackNumber, repeatDelay] を追加できます。ここで:

  • loop: ループ命令であることを示す識別子
  • trackNumber: ループするトラック番号
  • repeatDelay: 最後のアニメーションが完了してからループを繰り返すまでの秒数(省略時は 0

同じトラック番号に対する最初のグループは setAnimation に渡されます。同じトラックに対する以降のグループは addAnimation に渡されます。

setEmptyAnimationaddEmptyAnimation を使用するには、アニメーション名に #EMPTY# を指定してください。この場合 loop パラメータは無視されます。

以下の 2つの例を参照してください。

このSpineboyは animations 属性に以下の値を使用しています:

[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]

すべてのアニメーションは単一のトラックで再生されます。シーケンスの内訳は以下の通りです:

  • [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 属性に以下の値を使用しています:

[0, wings-and-feet, true]
[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 属性を切り替えることができます。

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="スピナー表示切り替え: OFF" onclick="toggleSpinner(this)" />
<input type="
button" value="リロード" 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 ? "スピナー表示切り替え: OFF" : "スピナー表示切り替え: ON";
}

オフスクリーン動作

オフスクリーン(画面外)にあるウェブコンポーネントはレンダリングされません。オフスクリーンの間、デフォルトでは AnimationState のupdate、Skeleton のupdate、skeleton.applyskeleton.updateWorldTransform 関数は呼び出されません。これは offscreen=pause に相当します。

オフスクリーンでも更新関数を実行したい場合は offscreen=update を設定してください。

可視性に関係なくすべての関数を常に実行したい場合は offscreen=pose を設定してください。

pause
update
pose

このページをリロードして3つのスケルトンがすべてビューポート内にあるとアニメーションは同期して開始されます。ただし最初のスケルトンは offscreen="pause" が設定されているため、画面外にスクロールすると状態が停止します。ビューポートに戻ると、その間経過した時間は進まず再開するため、他の2つと非同期になります。他の2つは同期を維持しますが、物理演算が関わる場合などにわずかな差異が生じることがあります。

オフスクリーンでスケルトンが停止するのを防ぐには update 動作を使用することを推奨します。これにより通常最も CPU を消費する updateWorldTransform の呼び出しを避けつつ更新を維持できます。

カスタム更新

ウェブコンポーネントの skeletonstate他のランタイムと同様に更新および適用されます。

updateWorldTransform の呼び出しの前後にカスタムロジックを注入するには、beforeUpdateWorldTransformsafterUpdateWorldTransforms を設定します。

デフォルトの更新動作を完全に置き換えるには update プロパティに関数を割り当ててください。これにより状態とスケルトンの両方の更新が置き換えられ、オフスクリーン最適化も上書きされます。この場合、updateapply、および updateWorldTransform の呼び出し管理は開発者の責任になります。 onScreen プロパティはコンポーネントが画面上にあるときに true になる便宜用のフラグです。

3 つの関数はすべて同じシグネチャを持ちます: (delta: number, skeleton: Skeleton, state: AnimationState) => void

ドラッグ

drag 属性を設定するとウェブコンポーネントでドラッグ機能を有効にできます。CPU 使用率が増加する可能性があるため、ドラッグ可能な挙動が必要な場合のみこの機能を使用することを推奨します。

ポインタ位置

ポインタ位置をさまざまな座標空間で取得するために、次のプロパティが使用できます:

spine-skeleton では:

  • pointerWorldXpointerWorldY: スケルトン原点に対するポインタの x,y 座標(Spineのワールド座標)
  • worldXworldY: キャンバス / WebGL コンテキスト原点に対するスケルトン原点の x,y 座標(Spineのワールド座標)

spine-overlay では:

  • pointerCanvasXpointerCanvasY: キャンバスの左上隅に対するポインタの x,y 座標(スクリーン座標)
  • pointerWorldXpointerWorldY: キャンバス / WebGL コンテキスト原点に対するポインタの x,y 座標(Spineのワールド座標)

これらのプロパティによりウェブコンポーネントとのインタラクティブな挙動が可能になります。以下の例ではフクロウの目がポインタを追いかけます。

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;
}

インタラクションコールバック

interactive 属性を設定すると、ポインタ操作のコールバックをウェブコンポーネントにアタッチできます。

コールバックはウェブコンポーネントのバウンズ内での操作や、特定のスロットに対する操作の両方に反応できます。サポートされるイベント (PointerEventType) は downupenterleavemovedrag です。

コールバックの追加方法:

  • pointerEventCallback: (event: PointerEventType) => void を設定して、ウェブコンポーネントのバウンズ内でのポインタ操作を処理する
  • addPointerSlotEventCallback (slotRef: number | string | Slot, slotFunction: (slot: Slot, event: PointerEventType) => void) を呼び出して、指定したスロットのアタッチメント領域内でのポインタ操作を処理する

以下の例では:

  • pointerEventCallbackenterjump を、leavewave をトリガーします。
  • addPointerSlotEventCallbackhead-base スロット(顔)にコールバックを追加します。アタッチメントが down イベントを受け取ると、2つのティントセレクターで選択された色に基づいて通常色とダーク色が更新されます。

通常色のティント:
ティントブラック:
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 属性を設定するとデバッグモードが有効になり、以下の視覚的な指標が表示されます:

  • スケルトンのワールド原点(緑)
  • ルートボーンの位置(赤)
  • バウンズ の矩形とその中心(青)
  • ウェブコンポーネントがドラッグ可能に設定されている場合のドラッグ領域(半透明の赤)

この例では、原点と重ならないようにルートが移動されています。

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);

ページ

複数のアトラスページがある場合(例: スキンごとに1ページある等)で、その一部のページだけを表示すればよいときは、pages 属性でロードするアトラスページを指定できます。ロードするページのインデックスをカンマ区切りで指定してください。

pages="0,6"
pages="0,4"
pages="0,1"

テクスチャをプログラムからロードするには、pages 属性を空に設定します: pages=""。これによりスケルトンとアトラスデータはロードされますがテクスチャはロードされないため、後で手動でテクスチャをロードすることができます。

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();
   }
}

スロットに追従

HTML要素をスロットに追従させることもできます。これはテキストなどの動的コンテンツをアニメーションに組み込むのに便利です。

followSlot 関数は次のパラメータで呼び出します:

  • 追従する Slot インスタンスまたはスロット名

  • 追従させる HTMLElement

  • 次のプロパティを持つオプションオブジェクト

    • followOpacity: 要素の不透明度をスロットのアルファにリンクします
    • followScale: 要素のスケールをスロットのスケールにリンクします
    • followRotation: 要素の回転をスロットの回転にリンクします
    • followVisibility: スロットにアタッチメントが表示されているかに応じて要素を表示/非表示にします
    • hideAttachment: 要素が視覚的にアタッチメントを置き換えるかのようにスロットのアタッチメントを非表示にします

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 は他のSpine Web Components とも連携して機能します。ドラッグ中でも動作します。

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);

高度な使用法

プログラムによる作成

<spine-skeleton> 要素は createSkeleton 関数を使ってプログラム的に作成できます。この関数はウェブコンポーネントの属性の camelCase 表記に対応したプロパティを持つオブジェクトを受け取ります。

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>

または標準のDOM操作を使ってHTMLとして直接要素を挿入することもできます。

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>

デフォルトではアセットは直ちにロードされます。ロードを遅らせたい場合は manualStart: "false" を設定してください。作成後は非同期の appendTo メソッドでDOMに追加し、適切なタイミングで要素の start() を呼んでロードを開始します。SkeletonAnimationState への操作は whenReady が解決されるまで保留してください。

破棄

DOMからウェブコンポーネントを削除しても自動的に破棄されるわけではありません。再利用するために保持されることが想定されているからです。明示的に破棄するにはその要素の dispose() メソッドを呼び出してください。この操作は安全で、他のウェブコンポーネントによってまだ使用中のリソースは解放されません。

すべての spine-webcomponents リソースを破棄するには、オーバーレイインスタンスの dispose() を呼び出してください。

dispose.html には dispose 関数の使用例が示されています。

手動オーバーレイ

ページに <spine-skeleton> を追加すると、スケルトンをレンダリングする WebGL キャンバスを含む <spine-overlay> が自動的に追加されます。このオーバーレイはブラウザのビューポート全体に広がります。

代わりに特定の HTML 要素に <spine-overlay> を手動で追加することもできます。この場合、オーバーレイは親要素のサイズを継承します。手動で定義したオーバーレイに <spine-skeleton> をレンダリングするには、スケルトンとオーバーレイの両方に同じ overlay-id を設定してください。

オーバーレイがどの要素内に配置されても、常に最後の子要素として再配置されます。これによりオーバーレイが他の要素の上に表示され続けます。不要なDOMのデタッチや再アタッチを避けるため、オーバーレイは目的のコンテナ内で最後に配置することを推奨します。

手動オーバーレイ作成が有用なケース:

  1. スクロール可能なコンテナ
    • コンテナ要素が完全に表示されるまでスケルトンがオーバーフローする可能性がある
    • 低リフレッシュレートのディスプレイではスクロールにラグが生じることがある

  2. 固定/スティッキーなコンテナ
    • スケルトンのスクロールがぎくしゃくしたり不均一になる可能性がある

  3. カスタムオーバーレイ配置
    • 小さなオーバーレイが必要な場合や、オーバーレイを直接 <body> に追加したくない場合に便利

以下の例でこれらの問題を観察できます。最初のスクロールリストはデフォルトオーバーレイを使用し、2番目はスクロール可能な <div> 内に専用オーバーレイを配置しています。ボタンをクリックするとコンテナ <div> の position を fixed にして、スクロール動作の違いを確認できます。

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 = "position を fixed に設定";
   } else {
      popupOverlay.style.position = 'fixed';
      popupOverlay.style.top = 'calc(50% - 125px)';
      popupOverlay.style.background = 'white';
      openPopupButton.innerText = "fixed 設定を解除";
   }
   positionFixed = !positionFixed;
});

各オーバーレイは独自のWebGLコンテキストを作成するため、利用可能なWebGLコンテキストの最大数にカウントされる点に注意してください。