Internet Explorer 10 の CSS 3D Transforms

Category : CSS

とうとう Windows 7 向けの IE10 が公開されましたね! (これを書いている時点では Release Preview ですが)
IE10 では 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.

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

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

<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;
}

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

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

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

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

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 にする必要があります)、まずは IE10 向けの指定を普通に書いた上で、

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

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

/*
 IE 10 向けの指定
*/

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

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

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

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

CSS だけで分岐 2

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

結局どうするの?

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

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

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

Leave a Reply