yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

Vue.jsでカレンダー作ってみた

Vue.jsでカレンダーライブラリを使わず自作のカレンダーを作り始めてみた。create-nuxt-appでプロジェクト作って、とりあえず

  • 月単位で日付表示
  • 月選択

まで実装できたので実装内容書いてみる。

見た目

ソースコード全体

GitHub - ikeyu0806/vue-calendar: Vue.js/GraphQLで開発した自作カレンダー

ざっくりと流れは

jsのDateオブジェクトで表示対象の月の日付情報を取得 →そこを起点にfor文で日付を加算していき月末までの日付を保持する配列を作る(カレンダーの行番号をkey、日付をvalueとした連想配列) →v-forに渡して描画

その他細かいところの実装でいうと

  • 現在の日付、曜日、月、年と先月末の日付をvueのdataオプションに持たせて「前の月」、「次の月」ボタンを押すと表示する月を切り替えられるようにした。
  • v-forのkeyを使って判定して日曜日の列のみ赤字で表示させた。こんな感じ

<th v-for="week in weeks" :key="week.id" :class="{sunday: week.id === 0}">{{ week.value }}</th>

<template>
  <div>
    <v-row justify="center" align="center">
      <v-col><h3 id="date-title">{{ dateTitle }}</h3></v-col>
      <v-col></v-col>
      <v-col></v-col>
      <v-col></v-col>
      <v-col>
        <div class="month-select">
          <span @click="prevMonth"><v-btn outlined>前の月</v-btn></span>
          <span @click="nextMonth"><v-btn outlined>次の月</v-btn></span>
        </div>
      </v-col>
    </v-row>
    <br />
    <v-row justify="center" align="center">
      <table>
        <thead>
          <tr>
            <th v-for="week in weeks" :key="week.id" :class="{sunday: week.id === 0}">{{ week.value }}</th>
          </tr>
          <tr v-for="(week, index) in calendars" :key="index">
            <td v-for="(day, index) in week" :key="index" :class="{sunday: index === 0}">
              {{ day.date }}
            </td>
          </tr>
        </thead>
      </table>
    </v-row>
  </div>
</template>

<style>
/* eslint-disable */
#date-title {
  margin-left: 60px;
}
.month-select {
  margin-right: 10px;
}
.sunday {
  color: red;
}
th {
    border: 1px solid #ddd;
    padding: 30px;
    text-align: center;
}
td {
    border: 1px solid #ddd;
    padding: 30px;
    text-align: center;
}
</style>

<script>
export default {
  data () {
    return {
      weeks: [
        { id: 0, value: 'SUNDAY' },
        { id: 1, value: 'MONDAY' },
        { id: 2, value: 'TUESDAY' },
        { id: 3, value: 'WEDNESDAY' },
        { id: 4, value: 'THURSDAY' },
        { id: 5, value: 'FRIDAY' },
        { id: 6, value: 'SATURDAY' }
      ],
      startDay: new Date(this.date().getFullYear(), this.date().getMonth(), 1).getDay(),
      startDate: new Date(this.date().getFullYear(), this.date().getMonth(), 1),
      currentMonth: this.date().getMonth(),
      currentYear: this.date().getFullYear(),
      lastMonthEndDate: new Date(this.date().getFullYear(), this.date().getMonth(), 0).getDate()
    }
  },
  computed: {
    dateTitle () {
      // jsのgetMonthが0-11なので1加算
      const month = this.currentMonth + 1
      const result = this.currentYear + '年' + month + '月'
      return result
    },
    calendars () {
      return this.renderCalendar()
    }
  },
  methods: {
    prevMonth () {
      this.currentMonth === 0 ? this.currentMonth = 11 : this.currentMonth--
      this.currentMonth === 11 && this.currentYear--
      this.lastMonthEndDate = new Date(this.currentYear, this.currentMonth, 0).getDate()
      const dt = new Date(this.currentYear, this.currentMonth, 1)
      this.startDate = dt
      this.startDay = dt.getDay()
      this.lastMonthEndDate = new Date(dt.getFullYear(), dt.getMonth(), 0).getDate()
    },
    nextMonth () {
      this.currentMonth === 11 ? this.currentMonth = 0 : this.currentMonth++
      this.currentMonth === 0 && this.currentYear++
      this.lastMonthEndDate = new Date(this.currentYear, this.currentMonth, 0).getDate()
      const dt = new Date(this.currentYear, this.currentMonth, 1)
      this.startDate = dt
      this.startDay = dt.getDay()
      this.lastMonthEndDate = new Date(dt.getFullYear(), dt.getMonth(), 0).getDate()
    },
    date () {
      return new Date()
    },
    year () {
      return this.date().getFullYear()
    },
    month () {
      return this.date().getMonth()
    },
    endDate () {
      return new Date(this.year(), this.month(), 0)
    },
    endDayCount () {
      return this.endDate().getDay()
    },
    renderCalendar () {
      const startDay = this.startDay
      const currentDate = this.startDate
      const lastMonthEndDate = this.lastMonthEndDate
      const calendars = []
      let lastDateCount = startDay - 1
      for (let i = 0; i < 5; i++) {
        const weekRow = []
        for (let day = 0; day < 7; day++) {
          if (i > 0 || (i === 0 && day >= startDay)) {
            weekRow.push({
              date: currentDate.getDate()
            })
            currentDate.setDate(currentDate.getDate() + 1)
          } else {
            weekRow.push({
              // 曜日を使ってつじつま合わせ
              date: lastMonthEndDate - lastDateCount
            })
            lastDateCount--
          }
        }
        calendars.push(weekRow)
      }
      return calendars
    }
  },
  components: {
  }
}
</script>

Vueインスタンス部分

Vue経験が浅いのでcomputed、data、methodをそれぞれどう振り分けるかパッとわからなかったけど

  • 「前の月」、「次の月」ボタンを押した時にデータバインディングしたいところ→data
  • dataオプションと同じようにデータの変更に反映するけどdataよりロジックが介入してくるところ→computed
  • computedから呼び出したいjsメソッド→methods

という風に振り分けた。この辺はざっくり組んだので後々リファクタリングするかも

style

create-nuxt-appする時にデザインテンプレートを選択できるので使ったことがないvuetifyを選択してみた。 この時点ではv-rowとv-colを使ったグリッドシステムのみ使用。カレンダーの見た目はCSS書いて作っている。

今後やりたいこと

  • ユーザログイン機能を作りたい
  • モーダルを表示させてそこから予定を登録したい
  • 登録された予定を表示
  • バックエンドを作ってデータを永続化したい
  • component分割してみたい

あたりかな。。他にもやろうと思えばいくらでも欲しい機能はあるけどボチボチ追加していく予定。