多層嵌套的CSS 3D動畫技術詳解

IE9及其以下版本完全不支持CSS 3D transforms技術,Opera 12也不支持。具體支持信息請查看caniuse.com

CSS動畫是當前一種非常火爆的技術,我說的並不是一些簡單的顏色變換或長短屬性變換,我說的是3D變換技術;純CSS實現的翻滾旋轉立方體就是最典型的例子。網上能找到很多關於CSS動畫的代碼,但對於一個程序員來說,真正理解其爲什麼會動起來的原理是非常重要的。下面讓我來一步一步的帶你理解網頁中相互嵌套的3D動畫是如何實現的!

假設我們在一個門框內有一扇門:

HTML代碼非常簡單:

<div class='container'>
  <div class='frame'>
    <div class='door'></div>
  </div>
</div>

爲了打開這扇門,我們給它添加一個door--opencss類:

<div class='container'>
  <div class='frame'>
    <div class='door door--open'></div>
  </div>
</div>

現在,我們對它使用3D變換技術(通過對它的左側應用transform-origin)屬性:

.door--open {
  transform-origin: 0 0 /*whatever y value you wish*/;
  transform: rotateY(-45deg);
}

對於使用CSS 3D變換,你唯一需要添加的CSS前綴可能只有-webkit-。IE9是完全不支持的,但IE10+是不需要使用前綴的。Opera 12及其之前版本完全不支持CSS變換技術,之後的版本在使用-webkit-前綴後是支持的。火狐瀏覽器從V16版(2012年)起不需要使用前綴。

效果:

現在的效果看起來並不是很真實。更真實實現這種效果的CSS屬性叫做perspective(透視),它會讓東西看起來近處的大,遠處的小。

perspective屬性必須應用到需要做3D變換的元素的父元素上。在WebKit瀏覽器裏,只要是它的祖先元素都行,但在火狐或IE裏只能是父元素。

現在我們要往門框元素上添加一個frame--realistic類:

<div class='container'>
  <div class='frame frame--realistic'>
    <div class='door door--open'></div>
  </div>
</div>

現在我們在其上設置perspective透視屬性。透視屬性的值約小,它就會顯得離你的眼睛越近,這樣,越近的東西會顯得越大,越遠的越小。

.frame--realistic {
  perspective: 20em;
}

我們就得到了下面的效果:

這樣看起來就好多了!但我們可以做得更好!比如,我們可以讓這扇門動起來,並且具有3D效果。我們只需要在HTML和CSS裏將door-open類換成door--ani類:

.door--ani {
  transform-origin: 0 0;
  animation: doorani 1.3s ease-in-out infinite alternate;
}
@keyframes doorani {
  from { transform: rotateY(-43deg); }
  to { transform: rotateY(43deg); }
}

效果:

現在,我們還想讓這扇門的門框也以3D的形式動起來。這很簡單,不是嗎?只需要在門框上添加一個frame--ani類,設定一個動畫動作,將perspective透視屬性移動到它的父元素上:

HTML代碼變成了這樣:

<div class='container container--realistic'>
  <div class='frame frame--ani'>
    <div class='door door--ani'></div>
  </div>
</div>

我們還需要添加下面的CSS代碼:

.container--realistic {
  perspective: 20em;
}
.frame--ani {
  animation: frameani 2s ease-in-out infinite alternate;
}
@keyframes frameani {
  from { transform: rotateY(-30deg); }
  to { transform: rotateY(30deg); }
}

可是,我們得到的效果卻是:

看起來有些怪。看起來門的動畫效果被門框的擺動抵消了。的確,事情就是這樣,因爲transform-style屬性(用來告訴瀏覽器一個具有3D變換屬性的子元素是否附隨父元素的3D變換屬性)的缺省值是flat

這個問題可以通過將其父元素設置transform-style: preserve-3d來糾正。這樣,我們就可以看到更自然的效果了:

但是,IE10/11只支持transform-styleflat值。有時我們會利用這種技術將父元素和子元素通過3D變換串聯起來。

例如,我有一個稍微傾斜的立方體(沒有頂部和底部面)。HTML代碼是:

<div class='container container--realistic'>
  <div class='cube'>
    <div class='face'></div>
    <div class='face'></div>
    <div class='face'></div>
    <div class='face'></div>
  </div>
</div>

相關的CSS:

.cube {
  position: relative;
  width: 5em; height: 5em;
  transform-style: preserve-3d;
  transform: rotateY(30deg) rotateX(10deg);
}
.face {
  position: absolute;
  width: 100%; height: 100%;
}
.face:nth-child(1) {
  transform: /*rotateY(0deg)*/ translateZ(2.5em /* half the side length, 5em in this case */);
}
.face:nth-child(2) {
  transform: rotateY( 90deg)   translateZ(2.5em);
}
.face:nth-child(3) {
  transform: rotateY(180deg)   translateZ(2.5em);
}
.face:nth-child(4) {
  transform: rotateY(270deg)   translateZ(2.5em);
}

使用這些代碼(這裏有更詳細的解釋),我們得到了下面的效果:

如果你使用的是IE,我們需要在對每個面實施3D變換前先清空變換屬性(如果這個立方體的父類也有變換特徵,也需要先清空。)。我將立方體的父元素也處理了,就像下面:

.cube--ie {
  perspective: 20em;
  transform: none;
}
.face--ie:nth-child(1) {
  transform: rotateY(30deg) rotateX(10deg) 
             translateZ(2.5em);
}
.face--ie:nth-child(2) {
  transform: rotateY(30deg) rotateX(10deg) 
             rotateY( 90deg) translateZ(2.5em);
}
.face--ie:nth-child(3) {
  transform: rotateY(30deg) rotateX(10deg)
             rotateY(180deg) translateZ(2.5em);
}
.face--ie:nth-child(4) {
  transform: rotateY(30deg) rotateX(10deg)
             rotateY(270deg) translateZ(2.5em);
}

於是,在IE裏也得到了同樣的效果:

雖然不是很方便,但也不是很糟。代碼不是很多,也不是很亂…然而,當我們想旋轉這個立方體時卻出現了問題。我們需要使用transform-style: preserve-3d屬性,我們簡單的增加了一個cube--ani類,這段CSS代碼是:

.cube--ani {
  animation: rot 4s linear infinite;
}
@keyframes rot {
  to { transform: rotateY(-330deg) rotateX(370deg); }
}

可是,對於IE10/11來說,我們無法對立方體自身施加3D變換,我們只能對每個面單獨實施3D變換。這就是說,我們要對所有的面設置變換屬性。這就是說….每個面都要!

.cube--ie {
  animation: none;
}
.cube--ani .face--ie:nth-child(1) {
  animation: rot1 4s linear infinite;
}
@keyframes rot1 {
  to {
    transform: rotateY(-330deg) rotateX(370deg) 
               translateZ(2.5em);
  }
}
.cube--ani .face--ie:nth-child(2) {
  animation: rot2 4s linear infinite;
}
@keyframes rot2 {
  to {
    transform: rotateY(-330deg) rotateX(370deg) 
               rotateY(90deg) translateZ(2.5em);
  }
}
.cube--ani .face--ie:nth-child(3) {
  animation: rot3 4s linear infinite;
}
@keyframes rot3 {
  to {
    transform: rotateY(-330deg) rotateX(370deg) 
               rotateY(180deg) translateZ(2.5em);
  }
}
.cube--ani .face--ie:nth-child(4) {
  animation: rot4 4s linear infinite;
}
@keyframes rot4 {
  to {
    transform: rotateY(-330deg) rotateX(370deg) 
               rotateY(270deg) translateZ(2.5em);
  }
}

這一大片,就是爲了實現這個效果:

如果這麼多的代碼只是爲了這4個面,那當需要面對100多個面時,你能想象是多恐怖的一堆代碼嗎?

你也許會想到上面的門也有這種問題,門的父元素有高度和寬度,是可見。如何在IE裏實現?唯一能讓門和門框在IE裏一起動起來的方案就是修改HTML代碼,讓門和門框變成兄弟元素,單獨對它們施加動畫效果。

 

(英文:How Nesting 3D Transformed Elements Works.)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章