riotjs で svg タグの中を動的にマウントする時の tips


本記事は Riot.js Advent Calendar 2019 の第15日目の記事です.

Riot.js(以下、riot41)は非常にシンプルかつ軽量で入門の敷居も低く, とても書きやすいコンポーネント指向のUIライブラリです。

今回は, 以下の2つのアプリを riot4 で作ってみましたが, svg のレンダリングでちょっと躓いたのでその知見を書いていきたいと思います。

※riot4 の基本機能の紹介になりますのでご了承ください

作ったもの - バンドラ有り

こちらは ICSメディア さんの サンプル を riot4 で作り直したもの(riot-svg)になります. 今回は parcel-bundler を利用しました.

もう一つ riot-dom の方はちょっと改造していますので, 見る文にはこちらのほうが楽しいかと思いますw

作ったもの - バンドラ無し

vuejs ユーザーなら一度は見たことがあるかもしれませんが, vuejs の公式サンプルを riot4 で作り直したものになります.

アプリの構成

どちらも同じような躓き方をしていますので, 解説は1つ目のみにします. parcel を利用しており, こちらの方がより開発者向けかとも思いますので. 説明には関係ないコードは省略しておりますのでご注意ください.

まずはディレクトリ構成です.

ディレクトリ構成
./
 ├ src/
 │ ├ main.js
 │ ├ particle-data.js
 │ └ components/
 │   ├ app-components.riot ←親コンポーネント
 │   └ particle-components.riot ←子コンポーネント
 │
 ├ public/
 │ ├ index.html
 │ └ favicon.ico
 │ 
 ├ node_modules/
 ├ dist/ ←ビルド結果出力ディレクトリ
 ├ package.json
 └ yarn.lock

main.js にて親である app-component を読み込み, グローバルに登録2 せずそのままコンポーネントを作成しマウントしています.

さらに, app-component から子コンポーネント particle-component を読み込み, グローバルに登録し, マウントしますが, ここでグローバルへの登録の仕方が2通りあります.

▼1つ目: register メソッドの利用

一番シンプルな登録方法だと思います. riot 本体の register メソッドを利用します.

登録の仕方1
<particle-component /* attributes */ />
<script>
  // register メソッドの読み込み
  import { register } from 'riot'
  // 対象のコンポーネントの読み込み
  import ParticleComponent from './particle-component.riot'

  // 登録
  register('particle-component', ParticleComponent)
</script>
</app-component>

▼2つ目: components オブジェクトとしてセット

export default オブジェクトの1要素として登録する方法です. 個人的にはこちらの方が好きです.

登録の仕方2
<particle-component /* attributes */ />
<script>
  // 対象のコンポーネントの読み込み
  import ParticleComponent from './particle-component.riot'

  export default {
    // ここでセット
    components: {
      ParticleComponent
    }
  }
</script>

上記の通り2つ目の方法では, 読み込み時の名前は変更可能です. 読み込んだ ParticleComponent の中身をコンソールに出力してみますと,

このように, name キーでタグの名前が保持されており, riot4 がこちらを見てよしなにやってくれるからです.

〜閑話休題〜

改めて, 今回のアプリケーションの親・子コンポーネントの構成をご紹介します.

app-component.riot
<app-component>
  <particle-component
    width={ window.innerWidth }
    height={ window.innerHeight }
    particles={ state.particles } />
  <script>
    import ParticleComponent from './particle-component.riot'

    export default {
      components: {
        ParticleComponent
      }
    }
  </script>
</app-component>
particle-component.riot
<particle-component>
  <g each={ particle in props.particles }>
    <rect
      x={ particle.displayX }
      y={ particle.displayY }
      width="3"
      height="3">
    </rect>
  </g>
</particle-component>

じつはこの書き方だと動作しません.
なぜかと言うと, この状態でマウント・レンダリングすると, 以下のように svg タグが差し込まれまず, 画面としては画像が表示される部分が真っ白になります.

レンダリング後のhtml
<div id="root" is="app-component">
  <div>
    <particle-component width="669" height="613">
      <g><rect width="3" height="3" x="118" y="576"></rect></g>
      (...以下 g タグが続く)
    </particle-component>
  </div>
</div> 

ちょっと修正

直接的な修正の仕方としては, 以下のように svg タグを明示的に挿入するやり方です.

変更点1
 <particle-component>
+  <svg>
     <g each={ particle in props.particles }>
       <rect
         x={ particle.displayX }
         y={ particle.displayY }
         width="3"
         height="3">
       </rect>
     </g>
+  </svg>
+  <style>
+    svg {
+      100vw;
+      100vh;
+    }
+  </style>
 </particle-component>

これでも動くことは動きます. しかし riot4 の場合コンポーネントを作成すると, カスタムタグ(今回は particle-component)は消えずにそのままレンダリングされるため, そのカスタムタグで挟むことになってしまうのがイケてない…

これを回避するために, 以下のように修正します.

変更点
<app-component>
-  <particle-component
+  <svg 
+    is="particle-component"
     width={ window.innerWidth }
     height={ window.innerHeight}
     particles={ state.particles } />
  <script>
    import ParticleComponent from './particle-component.riot'

    export default {
      components: {
        ParticleComponent
      }
    }
  </script>
</app-component>

要は, HTML 標準のタグを riot4 のコンポーネントをして扱うということです. こちらは riot4 の基本機能3であり公式ドキュメントにも記載がありますので, そちらをご参照いただければと思いますー. (過去の俺よ…なんで自分で翻訳しておきながら, この存在を忘れてしまった…)

終わりに

riot4 はとても良い子なので, JavaScript の UI ライブラリで軽量かつ簡単に導入できるものを探しているなら, ぜひ一度検討していただけたら嬉しいです!

ではでは(=゚ω゚)ノ


  1. 最新の Riot.js のメジャーバージョンが 4 ですので, 明示的に riot4 と呼ぶことにします. 

  2. riot 固有の処理です. 詳しくは こちら をご参照ください. 

  3. HTML elements as components