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

## TL;DR

こんなのを作りました！
![](<https://user-images.githubusercontent.com/45185896/56851104-4a7f6180-6946-11e9-8e55-cef55680bd2f.gif>)
ラジオボタンを🌼と🦋で装飾するライブラリです！

<br>

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

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



## きっかけ

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

（[こっちの記事](<https://qiita.com/azukisiromochi/items/4d07e1b247baed01f11b>)……、もう少しで `1000いいね` なんだ……。よかったらこっちも読んでね）

![](<https://camo.qiitausercontent.com/1873d5cdb463a6c07fd7f9ca71acbc1122fa30d9/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3331373934362f64396232373033612d343965362d393037662d663631342d6666323235313563393765372e676966>)

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

<br>

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



## どういうパッケージなの？

### ブラウザで使用する場合

```html
<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で使用する場合

```shell
$ npm install butterfly-radio
```

インストールして、

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

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

```html
<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](<https://www.npmjs.com/package/butterfly-radio>)



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

:link: [butterfly-radio | GitHub](<https://github.com/azukisiromochi/butterfly-radio>)



## 実装関連の気付き

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



### CSSアニメーション

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

（本当の本当に初心者なので大したことは書いてありません！）

<br>

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


### Custom Elements

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

```html
<ライブラリの専用タグ>
  <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](<https://developer.mozilla.org/ja/docs/Web/Web_Components/Custom_Elements>)*

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



#### 使い方

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

例えば、 

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

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

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

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

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

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

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

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

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

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



##### constructor

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

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

（ `Shadow DOM` については後述）

```javascript
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

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

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

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



##### attributeChangedCallback

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

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

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

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

```javascript
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` ）することで、カスタム要素の利用が可能になります。

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



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

:link: [Custom Elements v1で独自のHTML要素を定義する | Subterranean Flower Blog](<https://sbfl.net/blog/2016/09/01/custom-elements-v1/>)



### Shadow DOM

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

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



#### Shadow DOM　is　何？

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

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



#### 使い方

`Shadow DOM` は `attachShadow` メソッドを利用することで追加できます。

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

```javascript
constructor() {
  super();

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

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

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

<br>

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

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

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

<br>

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

```javascript
this._shadowRoot.appendChild(el);
```

を行なっていました。

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

ここで実際に生成されたカスタム要素内の `Shadow DOM` を見てみましょう。
![Shadow DOM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/e5e41b75-fccc-a6f0-b142-d6aa866f268e.png)
`<butterfly-radio>` 要素の直下に `#shadow-root (open)` ができていて、その配下にラジオボタンなどの要素ができています。

`#shadow-root (open)` 内の `<style>` タグを開くと、
![Shadow DOM2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/09f58aac-de75-9649-bd02-014a5d1d9417.png)
`constructor` でシャドウルートに追加したスタイルがバッチリ表示されます。

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



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

:link: [Shadow DOM v1でHTMLの内容と構造を分離する | Subterranean Flower Blog](<https://sbfl.net/blog/2016/07/17/shadow-dom-v1/>)



### npmパッケージ



#### アカウント作成

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

[Sign Up](<https://www.npmjs.com/signup>) ページからサクッとアカウントを作ってしまいましょう！



#### package.jsonの作成

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

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

```shell
$ npm init
```

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

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

（つまりは任意項目）



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

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

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

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

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

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

<br>

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

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

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



#### npmに公開

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

```shell
$ npm publish ./
```

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



#### npmパッケージの更新（バージョンアップ）

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

その場合は、

```shell
$ npm version patch
v1.0.1
```

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

その後に、

```shell
$ npm publish ./
```

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



##### ちなみに……

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

```shell
$ npm version minor
v1.1.0

$ npm version major
v2.0.0
```



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

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

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

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



#### まだわからない点

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

`macha` とかでやるのか？　とか、

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

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

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



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



:link: [初めてのnpm パッケージ公開](<https://qiita.com/TsutomuNakamura/items/f943e0490d509f128ae2>)

:link: [3分でできるnpmモジュール](<https://qiita.com/fnobi/items/f6b1574fb9f4518ed520>)



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

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

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

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

<br>

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

付けては見たものの、いらないよね？　消そうかな（笑）



## まとめ

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

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

<br>

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

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



### Thanks！

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

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