やくだつブログ

[Nuxt.js, Vue.js] Vue コンポーネントで Window のリサイズ/スクロールを監視するとてもよい方法

Vue で windowresize, scroll などを扱いたいときって非常に悩ましいですね。いったいどこにイベントハンドラを記述すればよいのかしら。

コンポーネントの mounted() メソッドで window.addEventListener('resize', ...) としている解説記事などが見受けられますが、わたくしの美意識的にこれはあんまりいい方法だとは思えません。都度いちいち書いていかなくちゃいけないですし。どうせならどこかで一元管理したいですね。

そこで Vue v2.6.0 から追加された Vue.observable とインスタンスプロパティを組み合わせてみてはどうでしょうというご提案。

Vue.observable に渡されたオブジェクトはリアクティブになって返ってきます。それを Vue.prototype に追加してグローバルに使ってしまおうということです。

サンプル

// /plugins/window.js
import Vue from 'vue'

Vue.use({
  install(Vue) {
    const $window = Vue.observable({
      width: 0,
      height: 0,
      pageYOffset: 0
    })

    // SSR 時にエラーが出るため process.browser で分岐
    // Nuxt を使用しなければこの分岐は削除してください
    if (process.browser) {
      const onScroll = () => {
        $window.pageYOffset = global.pageYOffset
      }
      const onResize = () => {
        $window.width = document.documentElement.clientWidth
        $window.height = global.innerHeight
      }
      global.addEventListener('scroll', onScroll)
      global.addEventListener('resize', onResize)
      // 一度だけスクロールハンドラとリサイズハンドラを直接呼んで初期値をセット
      onScroll()
      onResize()
    }

    Vue.prototype.$window = $window
  }
})

ウィンドウサイズとスクロール量を保持したリアクティブな $window オブジェクトを作成し、 Vue.prototype.$window に代入しました。これでどの Vue component からも this.$window で参照できますのでコンポーネントで watch すれば OK だ!

// /pages/**.vue, /components/**.vue などどこでも
export default {
  watch: {
    '$window.width'() {
      console.log(`width: ${this.$window.width}`)
    },
    '$window.height'() {
      console.log(`height: ${this.$window.height}`)
    },
    '$window.pageYOffset'() {
      console.log(`pageYOfset: ${this.$window.pageYOffset}`)
    }
  }
}

あとは nuxt.config.jspluginswindow.js を追加するだけです。

export default {
  // ...省略
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [{ src: '~/plugins/window.js' }],
  // ...省略
}

プロダクションで使用するならばリサイズハンドラとスクロールハンドラは throttle-debounce などで間引いたほうがよいですがだいたいこんな感じでいいと思います。

戻る