背景
大前提として、DOMを直接操作する前にまず打てる手を色々と考えていただきたいです
さて、Nuxt.jsを使っていて、どうしてもDOMを直接操作したい場合があります。
たとえば、ページ遷移直後に、遷移先の内部リンク( /hoge#id
)に自動でスクロールしたい場合などが考えられます。
このような場合、ライフサイクルイベントと直接DOM操作を駆使して動かす以外に方法がないのでは?と思い、実際にやってみました。
tl;dr
mountedとnextTickを使います。
mountedはスクロールするためにDOMの操作が必要になるので使用します。
ただし、mountedは子コンポーネントのマウントを保証しないので、nextTickを使い、レンダリングが終わるまで(操作対象のDOMがあることを保証して)コールバック関数を実行しないようにすることが重要です。
もし内部リンクとなるidを指定したコンポーネント内からさらにコンポーネントを呼び出している場合は、子コンポーネントの中でmountedとnextTickを使ってください。
動かしてみる
記法はvue-class-componentに従っているのでよしなに脳内変換をお願いします
<template>
<div id="id">
<p>Please scroll</p>
</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
@Component
export default class Child extends Vue {
private mounted() {
this.$nextTick(function() {
const element = document.getElementById('id')
if (!element) {
return
}
// elementの位置座標を取得
const rect = element.getBoundingClientRect()
window.scrollTo(rect.left, rect.top)
})
}
}
</script>
<style>
</style>
内部リンクとなるidがない場合はスクロールはせず終了します。
さらに
上記のままだと、決まったidにしか反応できないので、以下のようにすれば内部リンクが増えたときも便利です。
<template>
<div id="id">
<p>Please scroll</p>
</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
@Component
export default class Child extends Vue {
private mounted() {
this.$nextTick(function() {
// url内のhash(#id)を取得する
const hash = this.$route.hash
if (hash.length === 0) {
return
}
// hash.slice(1)でhashから「#」を取り除いておく
const element = document.getElementById(hash.slice(1))
if (!element) {
return
}
const rect = element.getBoundingClientRect()
window.scrollTo(rect.left, rect.top)
})
}
}
</script>
<style>
</style>
vue-routerの$route.hash
を使えばurl内にあるhashを取得してくれるので、hashの動的な変更に合わせて内部リンクへのスクロールが可能です。
最後に
しつこいようですが、直接DOMを操作する前になにかできることはないか考えてみるのがベストだと思います。少しでもお役に立てれば幸いです