Now and Nawoo

NFTの制作記録、技術メモ → C#, Solidity, Blockchain, Bitcoin, Ethereum, NFT

100時間後に死ぬNFT のコード解説(2) SVG

「100時間後に死ぬNFT」のコード解説の続き、SVG編です。

前回の記事
ソースコードはこちら

絵文字

ワニの画像や(墓石に供えられた)花の画像は絵文字を使っています。 一般的なSVGではpath要素を使って画像を表現しますが、それだとファイルサイズが大きくなり、フルオンチェーンNFTでは厳しいです。 絵文字なら <text font-size="50">🐊</text> だけです。サイズ調整もfont-sizeを変更すればいいので簡単です。

ただし、絵文字は環境によって見た目が変わります。
左から Windows - Android - iPad で確認した画像です。(iPadが妙にリアル・・・)

f:id:nawoo5:20211126085551p:plain:w200 f:id:nawoo5:20211126085559p:plain:w200 f:id:nawoo5:20211126085610p:plain:w200

(そして、今気づいたのですが、フォントもだいぶ違いますね。)

アニメーション

SVGでアニメーションさせる方法はいろいろあるようですが、今回は<animateMotion>を使いました。

<text font-size="90">🐊
<animateMotion path="M-100,0 C-100-100 100,100 100,0 C100-100 -100,100 -100,0Z" dur="3s" repeatCount="indefinite"/>
</text>

これでワニ(text要素)が、指定したpathに沿って動きます。dur="3s"はアニメーション時間で、3秒かけてpathを1周します。repeatCount="indefinite"で無期限リピートさせています。

ライフが半分以下になると動きが徐々に遅くなりますが、これは残りライフによってdur="3s"からdur="60s"まで段階的に変えています。

animateMotion - SVG: Scalable Vector Graphics | MDN

クリッピング

<clipPath id="r">
  <path d="M-200-200h400v400h-400z"/>
</clipPath>
<g clip-path="url(#r)">
  <text .../>
</g>

ワニが大きくなると動いたときにSVGの範囲をはみ出してしまうので、範囲外は描画しないようにクリッピングしています。 <clipPath id="xxx">で範囲を定義して、clip-path="url(#xxx)"で適用します。

クリッピングとマスキング - SVG: Scalable Vector Graphics | MDN

拡大縮小

ワニや墓石のサイズを変えるためにtransformを使っています。 transform="scale(1.5)"とすると1.5倍のサイズになります。

<g transform="scale(1.5)">...</g>

注意が必要なのは、拡大縮小は原点を中心に行われるので、原点が左上(デフォルト)だと拡大したときに右下方向に広がってしまいます。 そこで今回は画像の中央を原点とするため<svg viewBox="0 0 400 400"...>ではなく、<svg viewBox="-200 -200 400 400"...>としています。 (左上原点のままでtranslateを使って位置を修正したり、matrixを使う方法もありますが、ちょっと面倒です。)

transform - SVG: Scalable Vector Graphics | MDN

テキストの幅を調整

今回は、ユーザーが名前を付けるシステムなので、長い名前を付ける人がいるかもしれません。墓石に刻まれた名前が石をはみ出してしまわないようにtextLengthでテキスト幅を指定しています。それだけだと文字が重なってしまうので、lengthAdjust="spacingAndGlyphs"を指定します。

<text textLength="100" lengthAdjust="spacingAndGlyphs">...</text>

反対に名前が短い場合には横に引き伸ばされてしまうので、名前が5文字未満ならデフォルト表示、5文字以上なら textLengthlengthAdjust を指定しています。

f:id:nawoo5:20211126093520p:plain:w400

lengthAdjust - SVG: Scalable Vector Graphics | MDN

文字の縁取り

名前や年齢、HPのテキストは背景色で縁取りをしています。 (ワニが大きくなりすぎたときに文字と重なってしまうため。)

テキストの縁取りはstokeで色を指定するのですが、そのままだと縁が文字の内部を覆ってしまうので、paint-order: stroke を指定しておきます。

text{
  paint-order: stroke;
  stroke: wheat;
  stroke-width: 3px;
}

f:id:nawoo5:20211126092422p:plain:w300

paint-order - CSS: カスケーディングスタイルシート | MDN

圧縮(最適化)

無駄な容量を減らすためにSVGの圧縮(最適化)を行っています。 例えば、<rect>ではなく<path>を使うことで短くできます。

<rect fill="#ffffff" x="100" y="200" width="300" height="150" />

↓最適化後 (表示される図形は全く同じです)

<path fill="#fff" d="M100 200h300v150H100z"/>

少しの差ですが積み重なるとかなり圧縮できます。まあ、そのかわり読みやすさや編集しやすさは失われてしまいます。

最適化はこちらのサイトで行いました → SVG Viewer
SVGを貼り付けてOPTIMIZEをクリックすると最適化されます。(PRETTIFY をクリックすると整形されて読みやすくなります。)

おわりに

フルオンチェーンNFTには欠かせないSVGですが、調べてみるとなかなか奥が深いですね。まだまだ知らない機能がいっぱいです。

次回、コード解説(3) 失敗・トラブル編 に続きます。