# ドラム式デートピッカー「VueDrumrollDateTimePicker」を掲載しました!

みなさんはデートピッカーってご存知ですか? 読んで字のごとく「日付を選択するもの」なんですが、世の中には色んな種類のデートピッカーが存在します。

もちろんVueにも様々な種類のデートピッカーがあって洒落たものも多くあるんですが・・・


何故かドラム式のデートピッカーが無い。


何故いままで無かったのか不明ですが、ないなら作ればいいじゃない! ということで作ってみました!

デモページはこちら!

# 特徴

VueDrumrollDateTimePicker は、今までありそうで無かったVue製のドラム式デートピッカーです。

使いやすいUIとシンプルで柔軟なカスタマイズ性を目指して開発を行いました。

このライブラリの魅力

  • 使いやすいUI

    • シンプルで使いやすいドラム式UI
    • クリック・ドラッグ・タッチ操作に対応
    • 軽量な動作
  • 導入の容易さ

    • 純粋なVueコンポーネントなので、インストールが簡単
    • v-modelだけで動作する
    • シンプルなprops
  • 柔軟なカスタマイズ性

    • サイズは親コンポーネントのfont-sizeに追従
    • 直置き/ダイアログ表示の切り替えができる
    • ダイアログ表示時のinputタグを別のコンポーネントに置き換え可能

# 技術的なポイント

# 軽量化の探求

このライブラリを開発する上で、描画を少しでも速くするために関数型コンポーネント という機能を積極的に活用して開発を行うことにしました。

これは通常のVueコンポーネントと違い、状態を持たない代わりに描画コストの軽いコンポーネントで、ReactのSFCと似た概念のものです。

僕はReactをいくらか触ったことがあり、SFCを扱ったこともあるんですが、 やはり似ていると言っても構文は大きく違うため、習得難易度は高めでした。


またVueにはいくつかのレンダリング方法が用意されており、その一つに render関数 を利用するものがあります。 これは従来の template 構文を利用する描画と比べ、より細かな制御を可能とする方法となります。

render関数では、 v-ifv-forv-on などと言ったディレクティブが使えません。 全ての描画処理を手作業で書いていく必要があり、Vueの深い理解が必要になりますが、 その分無駄な処理を省くことができるため、可読性よりも軽量化が重要なライブラリの開発においてはベターな選択肢です。


上記のような描画技術を利用しなかった場合との比較はできませんが、非難読化状態のgzip時で 31kb というサイズに納めることができました。 (Vue CLIではライブラリの難読化まではしてくれないみたいです。)

# 月またぎの処理

これは大変だった部分なんですが、やはり日付関係で一番難しいのは月またぎ、日またぎなどの境界部分です。

1月31日を選択してる状態で、月を "2" に変更する。 そしたら日は "28(うるう年なら29)" になっていて欲しいわけですが、この実装が難しかった。

1日〜月末までのデータは言わずもがな配列で保持しており、 2月に入ると配列の要素数がガクッと減る

今まで31番目を指していたデータは行き場を失ってしまい、明後日の方向を指してしまう。

はい、日付処理の実装中によくあるバグですね。

この現象を回避するために、僕は 空要素を一度噛ませる という手法を取りました。 実際のコードをお見せします。

export default {
  // ...中略
  computed: {
     // ...中略
     days () {
        // 桁揃えをしつつ時刻を配列に追加
        const days = []
        for (let date = 1; date <= this.numberOfDays; date++) {
          days.push({
            name: date <= this.date ? ('0' + date).slice(-DIGIT) : '',
            value: date,
          })
        }
        if (this.date !== this.numberOfDays) {
          this.$nextTick(() => setTimeout(() => {
            this.numberOfDays = this.date
          }, 100))
        }
        return days
      },
  },
  watch: {
    value (newValue) {
      const newDate = dayjs(newValue).endOf('month').date()
      if (newDate !== this.date) this.date = newDate
    },
  },
  // ...中略
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

this.numberOfDays は変更前の月の日数。変更前が1月なら 31 が入ります。 this.date は変更後の月の日数。変更後が2月なら 28 または 29 が入ります。

月の値が 1 から 2 に変わった時、 days[*].name の値は [01,02,03...28,29,30,31] から [01,02,03...28,'','',''] へと変化します。

この時、 配列の要素数は31個のまま変わらないこと がポイントです。 31番目を指し示していたデータは行き場を失わず、単に表示が空文字列になるだけです。

このタイミングで this.value はまだ 31 です。データが更新されるタイミングがシビアです。

そして、描画が完了した後に100msの時間を置いて this.numberOfDays の日数が this.date に置き換えられます。 ここで配列の要素数は28(29)個になり、 this.value は日時の補正がかかって月末の 28(29) に切り替わる。

配列内の値と this.value の値が一致したため、月末をきちんと表示することが出来るというロジックになっています。


またこのバグには、月またぎによる不具合と同時に依存ライブラリのバグも含まれており、 非同期処理のタイミングズレも起きていました。

これはライブラリの製作者に修正を送って取り込んでもらいましたので、 時期バージョンアップの際に修正されているはずです。

# TODO

ここまで書いてご紹介をしてみましたが、まだまだこのライブラリは完全ではないので、TODOがいくつも残ってます。 今上げられるものでいうと

  • min-year/max-year ではなく、 min-date/max-date に対応
  • formatに合わせてピッカーのUIも可変にする(特にDD-MM-YYYYなどに対応)
  • valueを直接更新した場合の挙動を修正
  • 年の無限スクロール(できるかな・・・)
  • README.mdの作成(英語/日本語)
  • npmライブラリへのpublish

などがあります。 ライブラリ開発の道は長いですね。

# さいごに

TalkAvatarに続いて2作品目の自作ライブラリ公開となりますが、 Vue学びたての頃の前作と比べて、ずいぶんレベルアップしたのがソースコードからも見ていただけるんじゃないかなと思います。

また、今回はProductsページへの掲載もあって、 プログラム開発だけでなく、ドキュメント整理や当ブログのページ・記事作成など様々な経験をすることができました。 あと、依存ライブラリの修正なんてのも貴重な経験です。

個人開発はプログラミングに限らず、たくさんの学びを得られるチャンスでもあります。 まずは小さなプロジェクトから、ぜひ始めてみて下さい!