追記: この記事でやっていることをプラグイン化しました! → https://github.com/laineus/vue-scroll-hook
やりたいこと
わかりづらいので、具体的にやりたかったことを書きます。
自分が作っていたアプリでは、「ページを下のほうにスクロールしたらトップへ戻る
ボタンを出したい」といった、「スクロールしたときに何かする」的な処理が結構ありました。
そのたびに、↓こんな感じにcreated
とdestroyed
でイベントを登録・解除するのがめんどいな、と。
<script>
export default {
created () {
window.addEventListener('scroll', this.onScroll)
},
destroyed () {
window.removeEventListener('scroll', this.onScroll)
},
methods: {
onScroll () {
// Do something
}
}
}
</script>
そこで、↓こんな感じに書くだけで、スクロールしたときのフックを定義できるようにしました。
<script>
export default {
onScroll () {
// Do something
}
}
</script>
実装
公式ドキュメントをぱっと見した限り、ディレクティブをカスタマイズする手段はあるようですが、上記のようなフックのカスタマイズは見つかりませんでした。
なので、GlobalなMixinを作って実現しました。
import Vue from 'vue'
// ..
import GlobalMixin from './GlobalMixin'
Vue.mixin(GlobalMixin)
// ..
↓onScrollが定義されていた場合、windowのscrollイベントに登録し、destroyed
で削除します。
<script>
export default {
mounted () {
// Register onScroll event
if (!this.$options.onScroll) return
requestAnimationFrame(() => { // ページ遷移の際にVueRouterが実行するy=0へのスクロールを無視するための処理です
if (this._isDestroyed) return // この数フレームの間にコンポーネントが破棄される可能性を考慮しています
window.addEventListener('scroll', this.$options.onScroll)
})
},
destroyed () {
// Unregister onScroll event
if (!this.$options.onScroll) return
window.removeEventListener('scroll', this.$options.onScroll)
}
}
</script>
これだけです。
(余計なお世話かもしれませんが、↑このmixinはむやみに拡張しないでください)
そして、冒頭のように各コンポーネントでonScroll
という名前でイベントを定義するだけです。
<script>
export default {
data () {
return {
showButton: false
}
},
onScroll () {
this.showButton = window.scrollY > 100
}
}
</script>
余談
公式のカスタムディレクティブの例でまさにスクロール時のイベントを定義するサンプルがあったんですが、ちょっとしっくりこないです…。
<div id="app">
<h1 class="centered">Scroll me</h1>
<div
v-scroll="handleScroll"
class="box"
>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A atque amet harum aut ab veritatis earum porro praesentium ut corporis. Quasi provident dolorem officia iure fugiat, eius mollitia sequi quisquam.</p>
</div>
</div>
@scroll
的な感じで、.box
がスクローラブルな要素で、そのonscrollに定義される感じがするけど、結局window
全体のonscrollに定義してるし、そもそも外からイベントを定義したいケースも思いつかない。
handleScroll
の引数に渡したelementを直接弄ってるのも手続き的で気持ちよくないです…。