エンジニアの卵の成長日記

https://blog.toru-takagi.dev/profile/

Riot.js製の画像スライダーカスタムタグを作成した話とその際の知見

背景

僕は、とあるプロダクトの開発メンバーで、

1月からの画面の刷新プロジェクトに入りました。

しかし、設計フェーズになってもデザインは確定せず...

ですが、企画の段階でほぼほぼ画像のスライダーがあることは確定していました。
(結局なくなりましたが...)

なので、使えそうなカスタムタグがありそうか調べてみたのですが、
jQueryやBootstrapを利用していたり、
拡張性がないなど、満足できるものがありませんでした。

無いなら自分で作ってしまおう!ということで、作ったので、 その際の知見?を記載していこうと思います笑

RiotImageSlider

github.com

jQuery, Bootstrapなどは利用せず、Riot.jsだけで動かすことができます。

プロダクトがIE11, Edge, Chromeに対応しているため、それらの環境でも動くようにしました。

自分の望む大きさのタグでimage-sliderタグを囲い、
表示したい画像をimage-sliderタグ内に入れるだけで動きます。

オプションを設定してあげることで、スライド速度やボタンの非表示などもできます。

汎用性の高いタグを作成することでの知見

カスタムタグはblock要素ではない

ユーザ側で大きさを指定できる方が汎用性は高いと思います。

そこで、カスタムタグの親の大きさに合わせて形が変わるように作ろうと思った時に、
最初上手くいきませんでした。

<body>
  <div style="width: 400px; height: 200px;">
    <not-block></not-block> ⇦width: 400px; height: 200px;にはなりませんでした。
  </div>
</body>

<not-block>
  <h2>not-blockタグ</h2>
  <style>
    :scope {
      width: 100%;
      height: 100%;
    }
  </style>
</not-block>

原因はカスタムタグ(not-blockタグ)がBlock要素ではなかったからです。

なので、display: block;を指定してあげると上手く動きます。

  <style>
    :scope {
      display: block;
      width: 100%;
      height: 100%;
    }
  </style>

documentから要素の検索を行わない

カスタムタグ内の要素に対してclickイベントの際の処理を追加する場合、
最初はdocument.querySelector()やdocument.getElementById()などで、
要素検索を行い、その要素にイベントが起きた際の処理を追加していました。

最初は上手く動いているように見えたのですが、
同じタグを複数表示した際に、問題が起きました。

<body>
  <custom-tag></custom-tag>
  <custom-tag>/custom-tag>
</body>

<custom-tag>
  <h2 class="custom-class">カスタムタグ</h2>
  <script>
    var tag = this
    tag.on('mount', function() {
      document.querySelector('.custom-class').addEventListener('click', tag.click)
    })
    tag.click = function() {
      console.log('click')
    }
  </script>
</custom-tagu>

一つ目の<custom-tag>をクリックした際に、
consoleには、clickが2回出てしまいます。

documentから参照をしているため、document.querySelectorは両方とも、
1つ目のcustom-tag内のcustom-classを参照してしまうのです。
(普通に考えると当たり前ですが、1つ表示を行いながら作っていた時は気づきませんでした)

riot.js.org

カスタムタグ内で既に用意しているタグなどであれば、
onclickとテンプレート変数などを利用すれば大丈夫ですが、
後述する<yield/>などを利用する場合、そうもいきません。

riot.js.org

Riot.jsの機能である、名前付き要素を利用することも考えたのですが、
mountイベント内では、まだ参照できませんでした。

結論としては、Riot.jsのリファレンス内でも使われている、
this.rootから検索するようにしました。
this.rootはカスタムタグを参照しているので、
カスタムタグ内でしか検索を行わなくなるので、自分の意図した通りの動きになりました。

<custom-tag>
  <h2 class="custom-class">カスタムタグ</h2>
  <script>
    var tag = this
    tag.on('mount', function() {
      tag.root.querySelector('.custom-class').addEventListener('click', tag.click)
    })
    tag.click = function() {
      console.log('click')
    }
  </script>
</custom-tag>

clearIntervalやcancelAnimationFrameを忘れないように

カスタムタグ内でsetIntervalやrequestAnimationFrameを利用することがあると思います。

カスタムタグをifなどで非表示した際に、カスタムタグの情報がすべて消えてくれる気がしてしまうのですが、
当たり前ですが、setIntervalなどはwindowオブジェクトのメソッドですので、
タグが消えても動いてしまいます。

思わぬバグを生まないためにも、タグが消えた際にはちゃんとクリアするようにしましょう。

<script>
  var tag = this
  tag.on('unmount', function() {
    clearInterval(tag.intervalId)
    window.cancelAnimationFrame(tag.requestAnimationId)
  })
</script>

<yield/>が便利

拡張性が高いカスタムタグを作るには、<yield/>はとても重要な機能です。

軽い説明に関しては、他に記事を書いているので、そちらの参照をお願いいたします。

kurowasi2525.hatenablog.com

最後に

デザインが確定せず、2日ほど暇な時間があったので、
自分が使いやすいと思う、スライダーを作ってみたのですが、
思ったより多くの学びがありました。

僕は、Riot.jsを始めたばかりなので、長年使った人から見ると、
何を当たり前のことを言っているんだと思われるかもしれません。

何か他に良い手法などがあれば、Twitterなどで教えていただけると幸いです。

twitter.com