Help us understand the problem. What is going on with this article?

『母の日に最適?』ラジオボタンを🌼と🦋に変更するnpmパッケージを作ってみた

More than 1 year has passed since last update.

TL;DR

こんなのを作りました!

ラジオボタンを🌼と🦋で装飾するライブラリです!


ずっとやってみたかったけど、できていなかった npmパーケージの公開 というものをやってみたので、そこで知り得た情報を共有するのがメインの記事です。

その他にも、 CSSアニメーションや、 JavaScriptの Custom ElementsShadow DOM について浅く触れています。

きっかけ

以前作った配色Webサービスで、ボタンに同じようなエフェクトをかけました。

こっちの記事……、もう少しで 1000いいね なんだ……。よかったらこっちも読んでね)

これが、なぜか自らの感性に突き刺さりまして、ラジオボタンとかにも適用できたら素敵じゃね? といった流れです。


それとは別に、npmパッケージってどうやって公開するんだろう? という疑問を解決したい欲求があったので、組み合わせてこの形になりました。

どういうパッケージなの?

ブラウザで使用する場合

<butterfly-radio name="flower">
  <input type="radio" name="flower-color" id="red" value="1"><label for="red">Red flower</label>
  <input type="radio" name="flower-color" id="green" value="2"><label for="green">Green flower</label>
  <input type="radio" name="flower-color" id="blue" value="3"><label for="blue">Blue flower</label>
</butterfly-radio>

<script src="butterfly-radio.js"></script>

butterfly-radio.js を読み込んで、ラジオボタンを <butterfly-radio> タグで囲むだけのお手軽設計です。

(プログラミングをしたことのない人でも気軽に使えるくらいお手軽にしたかった)

Node.jsで使用する場合

$ npm install butterfly-radio

インストールして、

const butterflyRadio = require("butterfly-radio");
butterflyRadio();

インポートした関数を実行すれば、

<butterfly-radio name="flower">
  <input type="radio" name="flower-color" id="red" value="1"><label for="red">Red flower</label>
  <input type="radio" name="flower-color" id="green" value="2"><label for="green">Green flower</label>
  <input type="radio" name="flower-color" id="blue" value="3"><label for="blue">Blue flower</label>
</butterfly-radio>

ブラウザの場合と同じように利用できます。

公開されている場所

npmパッケージとして公開済みですので興味のある方はどうぞ!

:link: butterfly-radio | npm

ブラウザで利用したいだけなら、GitHubからダウンロードして build 配下のJSファイルを読み込めばOKです!

:link: butterfly-radio | GitHub

実装関連の気付き

冒頭に書いたとおり、 CSSアニメーションであったり、 JavaScript周り、npmパッケージの公開にいたるまで、雑多にまとめられていますが必要に応じてご覧ください。

CSSアニメーション

配色Webサービスのときは anime.js というライブラリを利用して作成していたのですが、依存ライブラリを持ちたくなかったのでCSSアニメーションに挑戦してみました。

(本当の本当に初心者なので大したことは書いてありません!)


…………っと思ったのですが、 内容薄かったので記事にするの止めましたッ!! :innocent::innocent::innocent:

Custom Elements

ラジオボタンを装飾するライブラリをできる限り簡単に表現するために、

<ライブラリの専用タグ>
  <input type="radio" name="flower-color" id="red" value="1">
  <label for="red">Red flower</label>
</ライブラリの専用タグ>

みたいな感じで、 <ライブラリの専用タグ> で囲むだけでできたらなぁ、という考えを持っていました。

色々と調べていると、 Custom Elements というJavaScriptを用いて専用タグをつくるAPIを見つけたわけです。

Custom Elements is 何?

Custom Elements は、独自のカスタム HTML 要素を作成するための機能です。 Custom Elements は、独自のスクリプト動作と CSS スタイリングを持つことができます。これは web components 一部ですが、単独でも使用できます。

via. Custom Elements | MDN web docs

まさに、やりたかったことを実現してくれるAPIでした!

使い方

カスタム要素はJavaScriptの class を利用して作成します。

例えば、

<butterfly-radio name="hoge"></butterfly-radio>

というカスタム要素を作るために ButterflyRadio クラスを作るとすると……

class ButterflyRadio extends HTMLElement {
  constructor() {
    super();
  }

  static get observedAttributes() {
    return ['name'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    const name = this.getAttribute('name');

    // 独自タグのname属性を用いた処理

  }
}

のような感じで定義できます。

HTMLElementextends することでHTML要素に必要な関数を実装することができます。

関数・プロパティ 概要
constructor コンストラクター。要素の作成またはアップグレード時に呼び出されます
observedAttributes 変更を監視する属性を決定します。上の例では name 属性に対して監視を行なっています。
attributeChangedCallback 要素の属性が変更、追加、削除、または置換されたときに呼び出されます。

Custom ElementsHTMLElement には他にも様々な機能がたくさんありますが、今回作った <butterfly-radio> では、この3つを主に利用しています。

constructor

<butterfly-radio> タグ内には <input> タグと <label> タグしかなく、アニメーションするための画像やスタイルがありません。

そのため、コンストラクターでアニメーション用のスタイルを Shadow DOM を用いてカスタム要素内のみに適用しています。

Shadow DOM については後述)

constructor() {
    super();

    this._shadowRoot = this.attachShadow({mode: 'open'});
    this._shadowRoot.innerHTML = `
<style>
:host {
position: relative;
}

.butterfly {
position: inherit;
top: -3em;
width: 2.5em;
height: 2.5em;
transition: all 1000ms 0s ease-in;
}

// any...

</style>
`;
  }
observedAttributes

上のコードのまんまです。

static get observedAttributes() {
  return ['name'];
}

<butterfly-radio> タグで使用している name 属性を返すように実装しています。

attributeChangedCallback

ラジオボタンがクリックっされたときの振る舞いを実装しています。

attributeChangedCallback は監視対象の name 属性の変更を検知するので、実質的には、初回のみ呼ばれることになります。

その子要素であるラジオボタン( <input> 要素)に対しては addEventListener を実装することで、イベントハンドラを定義していきます。

また、アニメーション用の画像の作成・挿入も、ここで、ラジオボタン単位に行なっています。

attributeChangedCallback(attrName, oldVal, newVal) {
    const name = this.getAttribute('name');

    Array.from(this.children).forEach((el) => {
      if (el instanceof HTMLInputElement) {
        if (el.type === 'radio') {
          // ラジオボタンに対してclass属性などを付与

          // Make butterfly image.
          const _img = document.createElement('img');
          // 作成した🦋画像に対してclass属性などを付与


          // Add change event to radio.
          el.addEventListener('change', (event) => {
            if (el === event.target) {
              if (event.target.value) {

                // ラジオがONになったときのイベント

              }
            }
          }, false);

          // Add elem to shadow root.
          this._shadowRoot.appendChild(_img);
          this._shadowRoot.appendChild(el);
        }
      }

      // Add label to shadow root.
      if (el instanceof HTMLLabelElement) {
        // ラベルに対して必要に応じて属性などを付与

        this._shadowRoot.appendChild(el);
      }
    });
  }

カスタム要素を定義する(利用可能に)

クラスを作成しただけではただのクラスオブジェクトに過ぎず、 <butterfly-radio> タグとしては利用できません。

利用可能な新たな要素として登録( define )することで、カスタム要素の利用が可能になります。

customElements.define('butterfly-radio', ButterflyRadio);

参考にさせていただいたサイト

:link: Custom Elements v1で独自のHTML要素を定義する | Subterranean Flower Blog

Shadow DOM

今回はライブラリということもあり、その他の資産に影響を与えたくない……、つまりはスコープを最小限にしたい、という気持ちがありました。

そこで初めて知ったのが、 Shadow DOM というAPIです。

Shadow DOM is 何?

Web コンポーネントにおいてカプセル化 (構造やスタイル、挙動を隠し、同じページの他のコードと分離すること) は重要です。これにより他の場所でのクラッシュを防ぎ、またコードが綺麗になります。Shadow DOM API はこの隠され分離された DOM を付加するための方法を提供しています。この記事では Shadow DOM を使う基本を記述しています。

via. shadow DOM の使い方 | MDN web docs

まさに、やりたかったことを実現してくれるAPIでした!(2回目)

使い方

Shadow DOMattachShadow メソッドを利用することで追加できます。

先程のコンストラクター内では、

constructor() {
  super();

  this._shadowRoot = this.attachShadow({mode: 'open'});
  // any...
}

といった感じで Shadow DOM を追加しています。

ちなみに、 Shadow DOM が追加された側(ここでは <butterfly-radio> 要素)をHost(ホスト)と呼び、追加した Shadow DOM のことをShadow Root(シャドウルート)と呼ぶそうです。


シャドウルートを追加したら、あとはスタイルや要素を追加していくことで、隠された空間(シャドウ)にDOMを追加したり、操作したりできます。

let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
position: relative;
}
</style>
`;

このように :host にスタイルを用意すればHostに適用することもできます。


<butterfly-radio> のコードでは、 attributeChangedCallback メソッドの各所で、

this._shadowRoot.appendChild(el);

を行なっていました。

ラジオボタン、ラベル、🦋画像、のそれぞれの要素を、全て this._shadowRoot.appendChild(el); でシャドウルートに追加しているわけです。

ここで実際に生成されたカスタム要素内の Shadow DOM を見てみましょう。
Shadow DOM.png
<butterfly-radio> 要素の直下に #shadow-root (open) ができていて、その配下にラジオボタンなどの要素ができています。

#shadow-root (open) 内の <style> タグを開くと、
Shadow DOM2.png
constructor でシャドウルートに追加したスタイルがバッチリ表示されます。

ここでしか適用されないスタイルの出来上がりです!

参考にさせていただいたサイト

:link: Shadow DOM v1でHTMLの内容と構造を分離する | Subterranean Flower Blog

npmパッケージ

アカウント作成

何をするにもアカウントがないとできません。

Sign Up ページからサクッとアカウントを作ってしまいましょう!

package.jsonの作成

npmパッケージたる所以、 package.json を作成します。

これは手作りするのではなく、

$ npm init

コマンドを実行すれば、対話形式で作成することができます。

あまり詰まるところはないと思いますが、 test command はテストの仕方がよくわからない(下の方でも書いている)のもあり、デフォルトで作成したあとにファイル編集で削除しています。

(つまりは任意項目)

エクスポート用のJSファイルを作成

npmパッケージ、つまりはNode.js配下で動くスクリプトの場合、ライブラリを require("butterfly-radio") のようにインポートして利用します。

その際は、 ./butterfly-radio/index.js を参照するようになっていて、それができるようにNode.js用のエクスポートを行った index.js ファイルを作成します。

// モジュールを定義(オブジェクトそのものを返すだったり、関数を返すだったり、ライブラリによってまちまち)
const myModhle = () => hoge();

// モジュールをエクスポート
module.exports = myModhle;

hoge() のところで ./lib 配下の別JSファイルを require して本実装は ./lib の配下にまとめてしまうこともあるそうです。


私の場合は上の章『Custom Elements』で書いたように、

const butterflyRadio = () => customElements.define('butterfly-radio', ButterflyRadio);
module.exports = butterflyRadio;

ButterflyRadio クラスをカスタム要素として登録する関数をモジュールとして定義しています。

npmに公開

細かいことを言うと、たくさんやることがあります(下記『参考にさせていただいたサイト』を参照するとわかりやすいです)が、最小限の構成だと、このタイミングでnpmに公開ができます。

$ npm publish ./

作成したライブラリのトップディレクトリ( package.json があるところ)で実行すればOKです。

npmパッケージの更新(バージョンアップ)

例えばライブラリに更新を加えるなど、npmパッケージもバージョンアップをしたい場合があります。

その場合は、

$ npm version patch
v1.0.1

npm version patch コマンドを利用して、パッケージのバージョンアップを行いましょう。

その後に、

$ npm publish ./

npm publish すれば最新バージョンを公開できます!

ちなみに……

マイナーバージョンアップ(v1.0.0 => v1.1.0)や、メジャーバージョンアップ(v1.0.0 => v2.0.0)などは、別のコマンドで行うことができます。

$ npm version minor
v1.1.0

$ npm version major
v2.0.0

gitリポジトリをタグ付けしておくと便利

npmパッケージの公開、バージョンアップに合わせてタグ付けをしておくと良いです。

$ git tag
v1.0.1
$ git push origin tags/v1.0.1

git tag コマンドで自動的にバージョンごとのタグを作ってくれるようです。

まだわからない点

テストのやり方が、いまいちわかりませんでした。

macha とかでやるのか? とか、

そうなったらライブラリにテストコードは含めるのか? とか、

:innocent::innocent::innocent: です :innocent:

このあたりはしっかりと調べて、READMEにバッジを貼れるようにしたいなと思っています。

参考にさせていただいたサイト

:link: 初めてのnpm パッケージ公開

:link: 3分でできるnpmモジュール

このライブラリの微妙なところ

ラジオボタンのアニメーションを固定時間にしているために、すばやく切り替えると🦋が不在になることがあります(笑)

🌼は色がついているので、ONとOFFを間違えることはないですが、正直微妙です。

CSSアニメーション超初心者なので、調整する方法を引き続き思案していこうと思います!


あとはカスタム要素内の name 属性……。

付けては見たものの、いらないよね? 消そうかな(笑)

まとめ

ずっとJavaメインでバックエンドばかりやっていた私が、前回のWebサービスや、今回のnpmパッケージと、フロントエンドに挑戦してみました。

(技術面が乏しいため記事の内容が薄いかもしれませんが……、寛大な目で見ていただけると嬉しいです :innocent:


やはり コード書いたらすぐに動く というのはモチベーションを維持するという意味でも良いですよね!

『フロントは難しそうだから……』と尻込みする気持ちもわかりますが、 簡単なものを作って、それが動く姿を見ればテンション激上がり だと思うので、気軽にやってみてください!

Thanks!

最後まで読んでいただき、ありがとうございました!!

タイトルのように『母の日』に……、とはなかなかいかないと思いますが、ブログやWebサイトを🌼や🦋 で装飾してみたいという方は、ぜひ使ってみてください!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした