質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.00%

Vue.js での methods 内の時間のカウントダウン

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,045

Bonito_Bonito

score 65

Vue.js を使用し、開催イベントの終了までの残り時間を1秒ごとカウントダウンした表示を行いたく、下記のような記述をしているのですが、カウントダウンされずに読み込んだ時点での残り時間が表示されてしまいます。

各イベントの終了時刻は end_time に入っており、moment.js の diff により現在の時間からイベント終了時刻までを取って来て表示、というところまではできたのですが、(下記 noweventlist.vue の count() )その中の setInterval がうまく動作していない、という状況にあります。

now-event-list.json

[
  {
    "checkedin": false,
    "begin_time": "2018-07-15T09:00:00.000Z",
    "end_time": "2018-07-14T09:00:00.000Z",
  },
  {
    "checkedin": true,
    "begin_time": "2018-07-14T09:00:00.000Z",
    "end_time": "2018-07-14T09:00:00.000Z",
  },
  {
    "checkedin": true,
    "begin_time": "2018-07-13T09:00:00.000Z",
    "end_time": "2018-07-14T09:00:00.000Z",
  },
  {
    "checkedin": true,
    "begin_time": "2018-07-12T09:00:00.000Z",
    "end_time": "2018-07-13T09:00:00.000Z",
  }
]

index.pug

body
  #app
    .p-pane-target-container
      .p-pane-target(:id='pane1[0].id' :class='{"is-visible":currentId1==pane1[0].id}')
        .c-label 現在開催中のイベント
        noweventlist(:noweventlistchild='noweventlist')

noweventlist.vue

<template lang="pug">
div
  section.p-event-list.is-now-event(v-for='item in noweventlistchild')
    .c-date-thumb
      .c-date-thumb__year {{item.begin_time|year}}
      .c-date-thumb__month {{item.begin_time|month}}
      .c-date-thumb__date {{item.begin_time|date}}
      .c-date-thumb__day {{item.begin_time|day}}
    span.c-badge.p-event-list__badge.u-bg--secondary(v-if='item.unrd>0') {{item.unrd}}
    .p-event-list__detail
      .p-event-list__check-in-status
        i.glyph.glyph-check-circle
        span(v-if='item.checkedin') チェックインしています
        span(v-else) チェックインしていません
      .p-event-list__remain 残り時間 あと
        span(v-html='count(item.end_time)')
</template>

<script>
import moment from 'moment'
export default {
  props: ['noweventlistchild'],
  methods: {
    count(time) {
      let diff = moment(time).diff(moment())
      // この箇所がうまく動作しません①
      setInterval(()=>{
        diff = moment(time).diff(moment())
      },1000)
      let duration = moment.duration(diff)
      const days = Math.floor( duration.asDays())
      const hours = duration.hours()
      const minutes = duration.minutes()
      const seconds = duration.seconds()
      let remain = `${days}${hours}時間${minutes}${seconds}秒`
      // 上記①でなくこのように記述しても動作しません
      setInterval(()=>{
        remain = `${days}${hours}時間${minutes}${seconds}秒`
      },1000)
      return remain
    },
  },
  filters: {
    format_test(time) {
      return moment(time).format('YYYY/MM/DD HH:mm')
    },
    year(time) {
      return moment(time).format('YYYY')
    },
    month(time) {
      return moment(time).format('MM')
    },
    date(time) {
      return moment(time).format('DD')
    },
    day(time) {
      return moment(time).format('dddd')
    },
  },
}
</script>

app.js

import Vue from 'vue'
import axios from 'axios'
import noweventlist from './components/noweventlist.vue'

// ==============================
// Vue インスタンス
// ==============================
const app = new Vue({
  el: '#app',
  // 使うコンポーネント
  components: {
    noweventlist,
  },
  data() {
    return {
      // 現在のイベントリスト格納用
      noweventlist: [],
    }
  },

  created() {
    // ------------------------------
    // 現在のイベントリスト表示用データ取得
    // ------------------------------
    axios.get('../../__source/_data/now-event-list.json')
    .then(function(response) {
      this.noweventlist = response.data
    }.bind(this)).catch(function(e){
      console.error(e)
    })
  },
})

Vue.js はまだ知見が足りないためご助力いただけますと幸いです。
また、記述面や意味がわからない等のご指摘もいただけますと幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

pugを全然知らないので間違ってたらすみません。

span(v-html='count(item.end_time)')これ、HTMLだと
<span v-html="count(item.end_time)"></span>って理解であってますよね?

v-html{{ }}で文字列を埋め込む代わりに、HTMLとして埋め込むだけです。
で、countメソッドの中身を見てみると、setIntervalの結果に関わらず、とりあえずreturn remain;してありますので、この時点の値がv-htmlによってHTMLとして解釈されて埋め込まれておしまいです。
あとからcountメソッド内のローカル変数を書き換えたところで、いったん埋め込んだ値を変更することにはなりません。

Vueで双方向バインディングをやるなら、メソッドではダメです。

ページが表示された時点ですでにカウントダウンは動作してていいって前提で書きます。

mountedフックを使うといいと思います。

このコンポーネントがマウントされた時点では、まだnoweventlistchildの中身が空っぽのようなので、watchを使ってプロパティを監視するように修正しました。

export default {
  props: ['noweventlistchild'],
  data() {
    return {
      counts: []  // カウンタを持つ配列
    };
  },
  methods: {
    getDuration(endTime) {
      let diff = moment(endTime).diff(moment());
      return moment.duration(diff);
    },
    formatRemain(duration) {
      const days = Math.floor( duration.asDays());
      const hours = duration.hours();
      const minutes = duration.minutes();
      const seconds = duration.seconds();

      return `${days}${hours}時間${minutes}${seconds}秒`;
    }
  },
  watch: {
    noweventlistchild(new, old) {
      // プロパティの配列でループ
      new.forEach((item, index) => {
        // dataに定義したcounts配列に初期値をぶっこむ
        this.counts.push(this.formatRemain(this.getDuration(item.end_time)));

        // 個別にsetIntervalをセットする
        setInterval(() => {
          // 自分のindexのカウンタを更新する
          this.counts.splice(index, 1, this.formatRemain(this.getDuration(item.end_time)));
        }, 1000);
      });
    }
  }

申し訳ないですが動作確認はできていないので、脳内イメージでコーディングしています。
こんな感じでカウントダウンが動作するはずです。
なんか間違ってたら適宜修正してください。

watch使えば、プロパティに渡ってきた配列の変更を検知してカウンタを追加したり削除したりすることもできると思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/18 07:21

    ありがとうございます!
    1秒ごとにカウントもされているのですが、「残り時間 あと[ "-66日-13時間-16分-35秒", "-67日-13時間-16分-35秒","〜","〜"・・・ ]」のようにオブジェクトの中身が全て出てしまいます。。
    (span {{counts}} の箇所です)

    キャンセル

  • 2018/09/18 07:25

    v-for='(item, index) in noweventlistchild' こうやって、
    {{counts[index]}} としてみてください。

    キャンセル

  • 2018/09/18 07:30

    あ、なるほど…できました!
    何から何まで、非常に助かりましたm(_ _)m

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.00%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる