Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

Vue.jsでoffsetWidth, offsetHeightが取得できない時は

はじめに

offsetWidthoffsetHeightなどの幅や高さを取得する関数たちは、display: noneになっている場合、0を返します。

Vue.jsで要素の表示非表示にv-showを用いている場合、v-showはその要素にdisplay: noneを付与して制御を行うので、非表示中のoffsetWidthは0として返ってくることになります。

<template>
  <div>
    <p v-show="false">ここのoffsetWidthは0pxです!</p>
  </div>
</template>

ハマりやすいポイント

offsetWidthなどは、自分の要素だけでなく、親要素にdisplay: noneが付与されていても0を返します。 注意したいのは、親コンポーネントでv-showを用いている(コンポーネントの表示制御を行っているなど)場合です。


Parent.vue

<template>
  <div>
    <div v-show="flag">
      <Child />
    </div>
  </div>
</template>

<script>
import Child from "./Child";

export default {
  name: "Parent",
  components: {
    Child,
  },
  data() {
    return {
      flag: false,
    };
  },
  mounted() {
    setTimeout(() => {
      this.flag = true;
    }, 1000); // 描画されて1秒後にtrueになる
  },
};
</script>

Child.vue

<template>
  <p ref="target">hello, world!</p>
</template>

<script>
export default {
  name: "Child",
  mounted() {
    console.log(`pタグのoffsetWidthは ${this.$refs.target.offsetWidth}px です!`); // pタグのoffsetWidthは 0px です!
  },
};
</script>

この場合、v-showでは表示非表示に関わらず描画が行われ、その後display: noneで制御を行います。そのため子コンポーネントのmountedのタイミングではv-showfalseなので、結果は0pxになってしまいます。

2つの解決方法

offsetWidthを取得できるようにする方法を2種類紹介します。

フラグを受け取りv-showtrueになった直後に取得する

親で用いているフラグを受け取ってwatchすることで、trueになった瞬間にoffsetWidthを取得します。

mountedのタイミングでは取得できませんが、watchと組み合わせれば任意のタイミングで利用できるようになります。


Parent.vue

<template>
  <div>
    <div v-show="flag">
      <Child :flag="flag" /> <!-- flagを子コンポーネントに渡す -->
    </div>
  </div>
</template>

<script>
// 省略
</script>

Child.vue

<template>
  <p ref="target">hello, world!</p>
</template>

<script>
export default {
  name: "Child",
  props: {
    flag: Boolean,
  },
  watch: {
    flag(newFlag) {
      if (newFlag) {
        console.log(`pタグのoffsetWidthは ${this.$refs.target.offsetWidth}px です!`); // 取得できた!
      }
    },
  },
};
</script>

v-ifに変えて遅延描画にする

切り替えコストが上がってしまいますが、v-ifの遅延描画を活用することで、mountedフックで取得できるようになります。


Parent.vue

<template>
  <div>
    <div v-if="flag"> <!-- v-ifに変更する -->
      <Child />
    </div>
  </div>
</template>

<script>
// 省略
</script>

Child.vue

<template>
  <p ref="target">hello, world!</p>
</template>

<script>
export default {
  name: "Child",
  mounted() {
    console.log(`pタグのoffsetWidthは ${this.$refs.target.offsetWidth}px です!`); // 取得できた!
  },
};
</script>

まとめ

offsetWidthが取得できない場合の解説と解決方法の紹介でした。親コンポーネントを探しに行かないといけない点は盲点だったので、同じ問題にハマらないように気をつけたいですね!