window.getComputedStyle() で要素のスタイルを取得する

JavaScriptでHTML要素のスタイルを知りたいという時、どうしていますか？
jQueryを使っているのであれば `jQuery.css(element, 'property')` や `jQuery(selector).css('property')` で取得している人もいるかと思いますが、jQueryを使わずとも `window.getComputedStyle()` というメソッドで取得可能です。（`jQuery.css()` もこれを使っています）

なにそれなにそれー！

#window.getComputedStyle() とは？

指定した要素の現在の状態のスタイル情報を `CSSStyleDeclaration`（Firefoxでは `CSS2Properties`）という名前の読み取り専用の（配列のように扱う事もできる）オブジェクトを返すメソッドです。すっごーい！
MDNでも見てみましょう。

> 要素の算出スタイルを返します。算出スタイルは、要素に対して適用される全ての CSS プロパティにおいて最終的に算出された値です。
[window.getComputedStyle - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/getComputedStyle)

あれあれ？ なんだか直訳したかのようなよくわからない感じの日本語だ……
というか、日本語版は最終更新が 2008年01月27日 と、9年も前（2017年3月現在）のまま止まっていてなんだか心配になってきた……
そんなわけで、英語版の方も見てみましょう。

> The Window.getComputedStyle() method gives the values of all the CSS properties of an element after applying the active stylesheets and resolving any basic computation those values may contain.
> [Window.getComputedStyle() - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle)
>> window.getComputedStyle() メソッドは、要素に適用されたスタイルの値を基本的な値に計算しなおした後、すべてのCSSプロパティの値を返します。（意訳）

なるほどー！ こっちの方がわかりやすいですね。

###対応しているブラウザ

現在主流のブラウザはすべて対応していますが、IE8以下では使えません。~~IE11未満は滅んでいいです。~~

##使い方

例えば、以下のようなHTMLがあったとします。（いろいろ省略）

```html:HTML
<style>
#sampleBlock {
  width: 150px;
  height: 150px;
  padding: 10px;
  background-color: #0b398c;
  color: #fff;
  font-size: 1em;
  font-weight: bold;
  text-align: center;
  box-sizing: border-box;
}

#sampleBlock::before {
  content: "たーのしー！";
}
</style>

<div id="sampleBlock"></div>
```

これを以下のように書くことで `div#sampleBlock` のスタイルが取得できます。（以下の例はChromeで実行した場合です）

```js:JavaScript
var sampleBlock = document.getElementById('sampleBlock');

// ブラウザが使用できるCSSのプロパティがオブジェクトになって返ってくる
var style = window.getComputedStyle(sampleBlock);

// それぞれの値を取得可能（キャメルケースで取得）
console.log(style.display);     // "block"
console.log(style.width);       // "150px"
console.log(style.margin);      // "0px"
console.log(style.paddingTop);  // "10px"
console.log(style.color);       // "rgb(255, 255, 255)"
console.log(style.background);  // "rgb(11, 57, 140) none repeat scroll 0% 0% / auto padding-box border-box"
console.log(style.fontSize);    // "16px"

// CSSStyleDeclaration.getPropertyValue() で取得（チェインケースで取得）
console.log(style.getPropertyValue('display'));      // "block"
console.log(style.getPropertyValue('width'));        // "150px"
console.log(style.getPropertyValue('margin'));       // "0px"
console.log(style.getPropertyValue('padding-top'));  // "10px"
console.log(style.getPropertyValue('color'));        // "rgb(255, 255, 255)"
console.log(style.getPropertyValue('background'));   // "rgb(11, 57, 140) none repeat scroll 0% 0% / auto padding-box border-box"
console.log(style.getPropertyValue('font-size'));    // "16px"

// 擬似要素の値も取得できる
var pseudo = window.getComputedStyle(sampleBlock, '::before');

console.log(pseudo.content);                      // "たーのしー！"
console.log(pseudo.getPropertyValue('content'));  // "たーのしー！"
```

`background-color: #0b398c;` としか書いていないのに、`window.getComputedStyle()` は計算して `background: rgb(11, 57, 140) none repeat scroll 0% 0% / auto padding-box border-box;` という値を返してくれます。
他にも、`font-size: 1em;` と相対的なサイズ指定も計算されて `font-size: 16px;` となっているのがわかります。
計算が得意なフレンズなんだね！

`CSSStyleDeclaration.getPropertyValue()` を使う事で、CSSで普段使っているチェインケース（ハイフンでつなぐ書き方）でも取得できるのがいいですね。

第2引数に `::before` や `::after` など擬似要素名を指定してやると、擬似要素に適用されているスタイルも取得できます。すっごーい！

##中身はどうなっているのか

`window.getComputedStyle()` が返すオブジェクトはChromeの場合以下のようになっています。（数が多すぎるので省略しています）

```js:window.getComputedStyle()が返すオブジェクト
// [object CSSStyleDeclaration]
style = {
  0: "animation-delay",
  1: "animation-direction",

  // 省略

  261: "rx",
  262: "ry",
  alignContent: "stretch",
  alignItems: "stretch",

  // 省略

  left: "auto",
  length: 263,
  letterSpacing: "normal",

  // 省略

  zIndex: "auto",
  zoom: "1",
}
```

`length` プロパティがあり、CSSのプロパティ名（こちらはスネークケース）が配列のように入っているのもわかります。

##Firefox以外のブラウザは cssText も取得できる

全てのCSSをテキストにした `cssText` も取得できますが、Firefoxでは空文字になってしまいます。

```js:JavaScript
console.log(style.cssText);
```

```text:実行結果（Chromeの場合）
animation-delay: 0s; animation-direction: normal; animation-duration: 0s; animation-fill-mode: none; animation-iteration-count: 1; animation-name: none; animation-play-state: running; animation-timing-function: ease; background-attachment: scroll; background-blend-mode: normal; background-clip: border-box; background-color: rgb(11, 57, 140); background-image: none; background-origin: padding-box; background-position: 0% 0%; background-repeat: repeat; background-size: auto; border-bottom-color: rgb(255, 255, 255); border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; border-bottom-style: none; border-bottom-width: 0px; border-collapse: separate; border-image-outset: 0px; border-image-repeat: stretch; border-image-slice: 100%; border-image-source: none; border-image-width: 1; border-left-color: rgb(255, 255, 255); border-left-style: none; border-left-width: 0px; border-right-color: rgb(255, 255, 255); border-right-style: none; border-right-width: 0px; border-top-color: rgb(255, 255, 255); border-top-left-radius: 0px; border-top-right-radius: 0px; border-top-style: none; border-top-width: 0px; bottom: auto; box-shadow: none; box-sizing: border-box; break-after: auto; break-before: auto; break-inside: auto; caption-side: top; clear: none; clip: auto; color: rgb(255, 255, 255); content: ; cursor: auto; direction: ltr; display: block; empty-cells: show; float: none; font-family: "ヒラギノ角ゴ Pro W3"; font-kerning: auto; font-size: 16px; font-stretch: normal; font-style: normal; font-variant: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: normal; font-weight: bold; height: 150px; image-rendering: auto; isolation: auto; left: auto; letter-spacing: normal; line-height: normal; list-style-image: none; list-style-position: outside; list-style-type: disc; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; max-height: none; max-width: none; min-height: 0px; min-width: 0px; mix-blend-mode: normal; object-fit: fill; object-position: 50% 50%; offset-distance: 0px; offset-path: none; offset-rotate: auto 0deg; offset-rotation: auto 0deg; opacity: 1; orphans: 2; outline-color: rgb(255, 255, 255); outline-offset: 0px; outline-style: none; outline-width: 0px; overflow-anchor: auto; overflow-wrap: normal; overflow-x: visible; overflow-y: visible; padding-bottom: 10px; padding-left: 10px; padding-right: 10px; padding-top: 10px; pointer-events: auto; position: static; resize: none; right: auto; speak: normal; table-layout: auto; tab-size: 8; text-align: center; text-align-last: auto; text-decoration: none; text-indent: 0px; text-rendering: auto; text-shadow: rgb(255, 255, 255) 0px 0px 0.001px; text-size-adjust: auto; text-overflow: clip; text-transform: none; top: auto; touch-action: auto; transition-delay: 0s; transition-duration: 0s; transition-property: all; transition-timing-function: ease; unicode-bidi: normal; vertical-align: baseline; visibility: visible; white-space: normal; widows: 2; width: 150px; will-change: auto; word-break: normal; word-spacing: 0px; word-wrap: normal; z-index: auto; zoom: 1; -webkit-appearance: none; backface-visibility: visible; -webkit-background-clip: border-box; -webkit-background-origin: padding-box; -webkit-border-horizontal-spacing: 0px; -webkit-border-image: none; -webkit-border-vertical-spacing: 0px; -webkit-box-align: stretch; -webkit-box-decoration-break: slice; -webkit-box-direction: normal; -webkit-box-flex: 0; -webkit-box-flex-group: 1; -webkit-box-lines: single; -webkit-box-ordinal-group: 1; -webkit-box-orient: horizontal; -webkit-box-pack: start; -webkit-box-reflect: none; column-count: auto; column-gap: normal; column-rule-color: rgb(255, 255, 255); column-rule-style: none; column-rule-width: 0px; column-span: none; column-width: auto; align-content: stretch; align-items: stretch; align-self: stretch; flex-basis: auto; flex-grow: 0; flex-shrink: 1; flex-direction: row; flex-wrap: nowrap; justify-content: flex-start; -webkit-font-smoothing: auto; -webkit-highlight: none; hyphens: manual; -webkit-hyphenate-character: auto; -webkit-line-break: auto; -webkit-line-clamp: none; -webkit-locale: auto; -webkit-margin-before-collapse: collapse; -webkit-margin-after-collapse: collapse; -webkit-mask-box-image: none; -webkit-mask-box-image-outset: 0px; -webkit-mask-box-image-repeat: stretch; -webkit-mask-box-image-slice: 0 fill; -webkit-mask-box-image-source: none; -webkit-mask-box-image-width: auto; -webkit-mask-clip: border-box; -webkit-mask-composite: source-over; -webkit-mask-image: none; -webkit-mask-origin: border-box; -webkit-mask-position: 0% 0%; -webkit-mask-repeat: repeat; -webkit-mask-size: auto; order: 0; perspective: none; perspective-origin: 75px 75px; -webkit-print-color-adjust: economy; -webkit-rtl-ordering: logical; shape-outside: none; shape-image-threshold: 0; shape-margin: 0px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0.180392); -webkit-text-combine: none; -webkit-text-decorations-in-effect: none; -webkit-text-emphasis-color: rgb(255, 255, 255); -webkit-text-emphasis-position: over; -webkit-text-emphasis-style: none; -webkit-text-fill-color: rgb(255, 255, 255); -webkit-text-orientation: vertical-right; -webkit-text-security: none; -webkit-text-stroke-color: rgb(255, 255, 255); -webkit-text-stroke-width: 0px; transform: none; transform-origin: 75px 75px; transform-style: flat; -webkit-user-drag: auto; -webkit-user-modify: read-only; user-select: text; -webkit-writing-mode: horizontal-tb; -webkit-app-region: no-drag; buffered-rendering: auto; clip-path: none; clip-rule: nonzero; mask: none; filter: none; flood-color: rgb(0, 0, 0); flood-opacity: 1; lighting-color: rgb(255, 255, 255); stop-color: rgb(0, 0, 0); stop-opacity: 1; color-interpolation: sRGB; color-interpolation-filters: linearRGB; color-rendering: auto; fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; marker-end: none; marker-mid: none; marker-start: none; mask-type: luminance; shape-rendering: auto; stroke: none; stroke-dasharray: none; stroke-dashoffset: 0px; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-width: 1px; alignment-baseline: auto; baseline-shift: 0px; dominant-baseline: auto; text-anchor: start; writing-mode: horizontal-tb; vector-effect: none; paint-order: fill stroke markers; d: none; cx: 0px; cy: 0px; x: 0px; y: 0px; r: 0px; rx: auto; ry: auto;
```

Firefoxでも使いたいのであれば、このようにするしかなさそうです。

```js:JavaScript
// window.getComputedStyle() の返すオブジェクトには
// CSSのプロパティが配列のように入っている
var
  cssText = '',
  length = style.length;

if (style.cssText === '') {
  for (var i = 0; i < length; i++) {
    // チェインケースなので、CSSStyleDeclaration.getPropertyValue で取得
    cssText += style[i] + ': ' + style.getPropertyValue(style[i]) + '; ';
  }
  cssText = cssText.trim();
} else {
  cssText = style.cssText;
}
```

#おわりに

`window.getComputedStyle()` に関する記事などが結構古い事が多いので、改めて書いてみました。
そのせいもあるのか、結構使いそうなメソッドなのに個人的に影が薄い印象です。