Unformed Building

Internet Explorer 10のCSS 3D Transforms

公開:
更新:

パーマリンク

とうとうWindows 7向けのIE 10が公開されましたね!(これを書いている時点ではRelease Previewですが)
IE 10ではCSS 3D Transforms, Animations, Transitionsなどが-ms-なしで使えるようになりました。素晴らしい!

というわけでさっそく試してみたわけですが、どうもおかしい。表示はできるんですが、アニメーションやトランジションしたときに立体的に見えない。
調べてみるとMSDNに次のような記述が。

Note  The W3C specification defines a keyword value of preserve-3d for this property, which indicates that flattening is not performed. At this time, Internet Explorer 10 does not support the preserve-3d keyword. You can work around this by manually applying the parent element’s transform to each of the child elements in addition to the child element’s normal transform.

http://msdn.microsoft.com/en-us/library/ie/hh673529%28v=vs.85%29.aspx#the_ms_transform_style_property

つまり、IE 10ではtransform-style: preserve-3dをサポートしていないから子要素それぞれにしてくださいね、ということらしいです。
transform-styleの初期値はflatですから、IE 10はflat固定になるわけです。

分かりやすいと思われる例を。
4つのパネルがくるくる回るだけのサンプルを作ってみました。
上がtransform-stylepreserve-3dを指定したもの、下がflatを指定したものになります。

transform-style: preserve-3dのテスト

<div class="stage1">
  <div class="view">
    <div class="panel p1">1</div>
    <div class="panel p2">2</div>
    <div class="panel p3">3</div>
    <div class="panel p4">4</div>
  </div>
</div>

<div class="stage2">
  <!-- 中身はstage1と同じ -->
</div>
.stage1,
.stage1 > .view {
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
}

.stage2,
.stage2 > .view {
  -webkit-transform-style: flat;
  transform-style: flat;
}

.stage1,
.stage2 {
  position: relative;
  margin: 100px auto 0;
  width: 100px;
  height: 100px;
  -webkit-perspective: 500px;
  perspective: 500px;
}

.view {
  position: absolute;
  width: 100%;
  height: 100%;
}

.panel {
  position: absolute;
  padding: 7px;
  border: 3px solid red;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  width: 100px;
  height: 100px;
  background: rgba(255, 0, 0, 0.5);
  color: white;
  font-size: 80px;
  line-height: 1;
  text-align: center;
}

.p1 {
  -webkit-transform: translateZ(200px);
  transform: translateZ(200px);
}

.p2 {
  -webkit-transform: rotateY(90deg) translateZ(200px);
  transform: rotateY(90deg) translateZ(200px);
}

.p3 {
  -webkit-transform: rotateY(180deg) translateZ(200px);
  transform: rotateY(180deg) translateZ(200px);
}

.p4 {
  -webkit-transform: rotateY(270deg) translateZ(200px);
  transform: rotateY(270deg) translateZ(200px);
}

@-webkit-keyframes rotation {
  from { -webkit-transform: rotateY(0); }
  to   { -webkit-transform: rotateY(360deg); }
}

@keyframes rotation {
  from { transform: rotateY(0); }
  to   { transform: rotateY(360deg); }
}

.view {
  -webkit-animation: rotation 4s linear infinite;
  animation: rotation 4s linear infinite;
}

IE 10では下のようになってしまうわけですね。
これをどうやって各ブラウザに対応させるかを考えてみました。
(Operaはまだ3D Transformsに対応していませんが、標準構文を書いておけば大丈夫だと思います)

ここからは上記サンプルの.stage1を変更しながら説明します。

transform-style: preserve-3dのことは忘れてIE 10に合わせる

transform-styleにはflatしかないんだ、という考えで作ります。
.panelの親をアニメーションさせるのではなく、自身をアニメーションさせる指定を行います。

/* 値をflatに(指定しなくても同じ) */
.stage1,
.stage1 > .view {
  -webkit-transform-style: flat;
  transform-style: flat;
}

.stage1 {
  position: relative;
  margin: 100px auto 0;
  width: 100px;
  height: 100px;
  /* 削除 */
  /*
  -webkit-perspective: 500px;
  perspective: 500px;
  */
}

.view {
  position: absolute;
  width: 100%;
  height: 100%;
  /* 追加 --*/
  -webkit-perspective: 500px;
  perspective: 500px;
  /*-- 追加 */
}

.panel {
  position: absolute;
  /* あまり関係ないので以下略(指定は同じ) */
}

.p1 {
  -webkit-transform: translateZ(200px);
  transform: translateZ(200px);
}

.p2 {
  -webkit-transform: rotateY(90deg) translateZ(200px);
  transform: rotateY(90deg) translateZ(200px);
}

.p3 {
  -webkit-transform: rotateY(180deg) translateZ(200px);
  transform: rotateY(180deg) translateZ(200px);
}

.p4 {
  -webkit-transform: rotateY(270deg) translateZ(200px);
  transform: rotateY(270deg) translateZ(200px);
}

/* 削除 */
/*
@-webkit-keyframes rotation {
  from { -webkit-transform: rotateY(0); }
  to   { -webkit-transform: rotateY(360deg); }
}

@keyframes rotation {
  from { transform: rotateY(0); }
  to   { transform: rotateY(360deg); }
}
*/

/* 追加 --*/
@-webkit-keyframes rotation {
  from { -webkit-transform: rotateY(0) translateZ(200px); }
  to   { -webkit-transform: rotateY(360deg) translateZ(200px); }
}

@keyframes rotation {
  from { transform: rotateY(0) translateZ(200px); }
  to   { transform: rotateY(360deg) translateZ(200px); }
}
/*-- 追加*/

/* 削除 - アニメーションは.panelに指定する */
/*
.view {
  -webkit-animation: rotation 4s linear infinite;
  animation: rotation 4s linear infinite;
}
*/

/* 追加 --*/
.panel {
  -webkit-animation: rotation 4s linear infinite;
  animation: rotation 4s linear infinite;
}

/* 各パネルの初期位置をずらすため、animation-delayで調節 */
.p2 {
  -webkit-animation-delay: -1s;
  animation-delay: -1s;
}

.p3 {
  -webkit-animation-delay: -2s;
  animation-delay: -2s;
}

.p4 {
  -webkit-animation-delay: -3s;
  animation-delay: -3s;
}
/*-- 追加 */

安全ではありますが……複雑なアニメーションや変形を行っている場合はかなり大変かもしれません。

処理を分岐する

FirefoxとWebkitは今までどおりでいいわけですから、IE 10と別に指定できるようにしてみます。
そうするとこで、IE 10には上記の指定をし、それ以外は今までどおりという指定ができます。3D Transformsとアニメーションやトランジションを組み合わせている部分でのIE 10はIE 9以下と同じように扱う、ということもできますね。

Modernizrで判定

この判定にはtransform-style: preserve-3dに対応しているかどうかを調べればいいので、Modernizrを使って判定してみました。

Modernizr.addTest('csstransformspreserve3d', function () {

  var prop,
      val,
      cssText,
      ret;

  prop = 'transform-style';
  if ('webkitTransformStyle' in document.documentElement.style) {
    prop = '-webkit-' + prop;
  }
  val = 'preserve-3d';
  cssText = '#modernizr { ' + prop + ': ' + val + '; }';

  Modernizr.testStyles(cssText, function (el, rule) {
    ret = window.getComputedStyle ? getComputedStyle(el, null).getPropertyValue(prop) : '';
  });

  return (ret === val);
});

こんな感じでしょうか。

あとはCSSを書くときに、.no-csstransformspreserve3d .stage1.csstransformspreserve3d .stage1のようにセレクタを分けて書いていきます。

このコードはGistに置いてあるのでよかったら改良してください。

CSSだけで分岐 1

Firefox 17から@supportsが使えるので(layout.css.supports-rule.enabletrueにする必要があります)、まずはIE 10向けの指定を普通に書いた上で、

@supports (transfrom-style: preserve-3d) { ... }

のように上書きします。ちなみにOperaも12.10から使えます。
Webkitは近いうちに対応するようなので、それも考えると最終的には以下のような感じでしょうか。

/*
 IE 10向けの指定
*/

/*
 @suuportsが使えないWebkit向けの指定
 -webkit-が必要なのでIE 10とは被らない
*/

@supports (transform-style: preserve-3d) or
          (-webkit-transform-style: preserve-3d) {
  /* IE 10向けに指定したアニメーションなどを上書きして削除する指定 */
  /* 標準構文と-webkit-付きを今までどおりに指定 */
}

何回も同じようなの書いてしまうことになりますが……。
というかもっといい書き方ができるんじゃないかと思います。

現状だとWebkit向けにはメディアクエリで-webkit-min-device-pixel-ratio: 0を使えば振り分けられますが……やるとしても@supportsと併用したほうが安全な気がします。

CSSだけで分岐 2

IE 10だけを振り分けるメディアクエリを使うやり方ですが、こういうのは好きじゃないのでできれば避けたいです。
IE 11で予期しない動作をする可能性がないわけではないですし。
使うとしても応急処置的なものですね。

結局どうするの?

IE 10に合わせておくのが無難かと思われます。

分岐させる場合には、多数のIE 10がIE 11に置き換わって他のブラウザと同じように動作するようになったときのことを考えると Modernizerを使って判別しつつJavaScriptオフの環境向けにはIE 10と同じ指定をしておくのが現実的かなーと思います。
もしくは@supportsで頑張るか。

IE 10で@supports使えたら楽だったんですけどね……。