かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript margin 含めた width / height を取得したい

要素マージンを含めた width / height を取得するのがちょいと大変だったのでメモ。

html { font-size: 16px; }

.div {
  margin: 0.3rem 0.2rem; // 4.8px 3.2px 相当
  border: .3rem solid #000; // 4.8px 相当
  width: 6.2rem; // 99.2px 相当
  height: 6.2rem; // 99.2px 相当
}

margin を含んだ値で取得できる API は無いっぽい

  • clientWith / clientHeight … border, margin を含まない Int 値
  • offsetWidth / offsetHeight … border は含み margin は含まない Int 値
  • getBoundingClientRect … border は含み margin は含まない Float 値

clientWith / clientHeight は border, margin を含まない

border, margin を含まず、四捨五入で丸められた Int 値が返される

const elm = document.querySelector('.div');
elm.clientWidth; // => 99 Math.round(99.2)
elm.clientHeight; // => 99 Math.round(99.2)

offsetWidth / offsetHeight は border を含むが margin は含まない

border を含んだ値で、四捨五入で丸められた Int 値が返される

const elm = document.querySelector('.div');
elm.offsetWidth; // => 112 Math.round(99.2 + 4.8 * 2)
elm.offsetHeight; // => 112 Math.round(99.2 + 4.8 * 2)

getBoundingClientRect は border を含むが margin は含まない

border を含んだ値で Float値で取得できる

const elm = document.querySelector('.div');
const { width, height } = elm.getBoundingClientRect();
width; // => 108.78125 ( 99.2 + 4.8 * 2 相当)
height; // =>  108.78125 ( 99.2 + 4.8 * 2 相当)

margin は style から取得する

window.getComputedStyle(element) で要素の持つ CSS を取得してその中から margin の値を使う。

const elm = document.querySelector('.div');
const styles = window.getComputedStyle(elm);
const {marginTop, marginRight, marginBottom, marginLeft} = styles;
// => "4.8px", "3.2px", "4.8px", "3.2px" 

// 単位を含む文字列で取得されるので parseFloat で数値に変換する
Object.fromEntries(
  Object.entries({
    marginTop,
    marginRight,
    marginBottom,
    marginLeft,
  })
  .map(([key, value]) => [key, parseFloat(value) || 0])
);
// => { marginTop: 4.8, marginRight: 3.2, marginBottom: 4.8, marginLeft: 3.2 }

getBoundingClientRect で border を含む width / height を取得して、getComputedStyle で取得できた margin の値を合算する

clientWith / clientHeight, offsetWidth / offsetHeight は丸められた整数値になるので、emrem 指定の場合 margin を足すと丸められた少数の誤差の累積で表示されているサイズとの誤差が大きくなりそうなので getBoundingClientRect を使うのが良さそう。

const getBoundingSize = (element) => {
  const { width, height } = element.getBoundingClientRect();
  return { width, height };
};

const getElementMargin = (element) => {
  const styles = window.getComputedStyle(element);
  return ['top', 'right', 'bottom', 'left'].reduce((obj, key) => {
    return {
      ...obj,
      [key]: parseFloat(styles.getPropertyValue(`margin-${key}`)) || 0,
    };
  });
};

const getOuterSize = (element) => {
  const { top, right, bottom, left } = getElementMargin(element);
  const { width, height } = getBoundingSize(element);
  return {
    width: width + right + left,
    height: height + top + bottom,
  };
};

const elm = document.querySelector('.div');
const { width, height } = getOuterSize(elm);
width; // => 115.18125 ( 99.2 + 4.8 * 2 + 3.2 * 2 相当)
height; // => 118.38125 ( 99.2 + 4.8 * 2 + 4.8 * 2 相当)

example

See the Pen How to get a Element size with margin by KIKIKI (@kikiki_kiki) on CodePen.

パッケージ化してみました。

感想

margin を含んだ値で取得できる API が無いっぽいのは意外だった。
位置を取得できる offsetTop や clientTop も margin を含まないっぽいので、margin を含んだ値を使いたいときは style から取得するしかなさそうな印象でした。
jQueryouterWidth, outerHeight 便利だったんですね… (しみじみ…)


[参考]

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

  • 作者:Ethan Brown
  • 発売日: 2017/01/20
  • メディア: 単行本(ソフトカバー)

プロテイン MyProtein のミルクティが一番好きかも