怎樣用CSS做出3D效果的雲

c-xuan發表於2016-03-15

介紹

本文將介紹如何一步一步製作出一些3D效果的雲彩,會用到一些高階的屬性,主要是如何通過CSS的3D變換效果來實現,如果你想了解更多,這地方是個不錯的開始

靜態效果圖

本教程將會分成幾個部分,每一部分都會有詳細的步驟讓你理解HTML,CSS和Javascript都是如何工作的,每一步都有銜接,以及一個連結來測試單個部分的程式碼效果。教程裡的程式碼是最終demo的簡化版,但是主要區別在每一部分都有說明

  • 1.新建一個世界和視角
  • 2.向我們建立的世界裡新增物件
  • 3.往我們的物件上新增層
  • 4.讓3D效果執行起來
  • 5.最後總結

1.新建一個世界和視角

首先,我們需要兩個div元素: viewportworld。剩下的部分將會在後面動態的加入。

viewport 包含了整一個螢幕和一個攝影機。由於在CSS 3D變換中沒有攝影機本身,就假想你在通過一個透明的玻璃螢幕來看螢幕裡的視野,你也可以改變看視野的方向。我們將會把所有物件都放在裡面,然後對他們進行變換。

world 是一個用來固定所有物件的 div 盒子。 旋轉,轉化或者放大都變換都會改變我們的元素。為簡單起見,我將在需要使用瀏覽器字首(-webkit,-moz,-0,-ms等等)的地方使用沒有字首的CSS屬性

這就是我們需要的所有盒子模型:

<div id="viewport">
    <div id = "world" ></div>
</div>

下面是我們定義的兩個CSS樣式,這裡非常重要的是要將 world div 放在 viewport div裡面,否則將無法渲染場景。要記住你將旋轉一個放置在文件裡的元素,像其他2D元素那樣。

#viewport{
    bottom:0;
    left:0;
    overflow:hidden;
    perspective:400;
    position:absolute;
    right:0;
    top:0;
}

#world{
    height: 512px;
    left: 50%;
    margin-left: -256px;
    margin-top: -256px;
    position: absolute;
    top: 50%;
    transform-style: preserve-3d;
    width: 512px;
}

現在寫一些程式碼來初始化我們的物件,繫結 mousemove 事件以及定義 updateView() 函式。

/*
    Defining our variables
    world and viewport are DOM elements,
    worldXAngle and worldYAngle are floats that hold the world rotations,
    d is an int that defines the distance of the world from the camera 
*/
var world = document.getElementById( 'world' ),
    viewport = document.getElementById( 'viewport' ),
    worldXAngle = 0,
    worldYAngle = 0,
    d = 0;

/*
    Event listener to transform mouse position into angles 
    from -180 to 180 degress, both vertically and horizontally
*/
window.addEventListener( 'mousemove', function( e ) {
    worldYAngle = -( .5 - ( e.clientX / window.innerWidth ) ) * 180;
    worldXAngle = ( .5 - ( e.clientY / window.innerHeight ) ) * 180;
    updateView();
} );

/*
    Changes the transform property of world to be
    translated in the Z axis by d pixels,
    rotated in the X axis by worldXAngle degrees and
    rotated in the Y axis by worldYAngle degrees.
*/
function updateView() {
    world.style.transform = 'translateZ( ' + d + 'px ) \
    rotateX( ' + worldXAngle + 'deg) \
    rotateY( ' + worldYAngle + 'deg)';
}

world 是紅色的,viewport 有背景色來模擬天空,mousewheel滾輪事件來監聽攝影機的遠近距離。移動滑鼠來觀察紅色的 div 是如何改變方向的。

這是這一部分的演示連結

2.向我們建立的世界裡新增物件

現在我們開始新增真正的3D內容。我們加入一些新的 div 放置在 world 空間裡。有必要新增幾個絕對位置的 div 作為 world 的子節點,但是用3D變換來替代 lefttop, 他們預設會出現在 world 的中央位置。widthheight屬性無關緊要,因為這些新的容器是放置那些真實雲的層。如果正式應用裡最好將他們居中(通過設定 margin-leftmargin-top 屬性 來設定 widthheight 一半的負數值)。

.cloudBase {
    height: 20px;
    left: 256px;
    margin-left: -10px;
    margin-top: -10px;
    position: absolute;
    top: 256px;
    width: 20px;
}

我們新增 generate()createCloud() 方法來充實 world注意 random_{var} 不是真實的變數值,而是一個程式碼佔位符,應該返回一個給定範圍內的隨機數

/*
    objects is an array of cloud bases
    layers is an array of cloud layers
*/
    var objects = [],
    layers = [];

/*
    Clears the DOM of previous clouds bases 
    and generates a new set of cloud bases
*/
    function generate() {
        objects = [];
        layers = [];
        if ( world.hasChildNodes() ) {
            while ( world.childNodes.length >= 1 ) {
                world.removeChild( world.firstChild );   
            } 
        }
        for( var j = 0; j <; 5; j++ ) {
            objects.push( createCloud() );
        }
    }

/*
    Creates a single cloud base: a div in world
    that is translated randomly into world space.
    Each axis goes from -256 to 256 pixels.
*/
    function createCloud() {
        var div = document.createElement( 'div'  );
        div.className = 'cloudBase';
        var t = 'translateX( ' + random_x + 'px ) \
        translateY( ' + random_y + 'px ) \
        translateZ( ' + random_z + 'px )';
        div.style.transform = t;
        world.appendChild( div );
        return div;
    }

頁面中那些粉紅色方塊的層就是雲層模型了,這裡有一個 p 變數來更簡單的改變 viewport.style.perspective 的值。試著去改變這個變數來觀察我們的攝影機是如何變化的。這個值越大,視角的垂直度就會越大。再次提醒,那個 randowm_{var} 並不是真正的變數。

點我來觀察這部分的演示

3.往我們的物件上新增層

現在有趣的事情開始發生了,我們新增了幾個絕對位置的“雲層” div 盒子來表示雲,這些盒子將會用來裝載雲的貼圖。

.cloudLayer {
    height: 256px;
    left: 50%;
    margin-left: -128px;
    margin-top: -128px;
    position: absolute;
    top: 50%;
    width: 256px;
}

舊的 createCloud() 函式做了一些改動,新增了雲層的隨機數。

/*
    Creates a single cloud base and adds several cloud layers.
    Each cloud layer has random position ( x, y, z ), rotation (a)
    and rotation speed (s). layers[] keeps track of those divs.
*/
function createCloud() {

    var div = document.createElement( 'div'  );
    div.className = 'cloudBase';
    var t = 'translateX( ' + random_x + 'px ) \
        translateY( ' + random_y + 'px ) \
        translateZ( ' + random_z + 'px )';
    div.style.transform = t;
    world.appendChild( div );

    for( var j = 0; j < 5 + Math.round( Math.random() * 10 ); j++ ) {
        var cloud = document.createElement( 'div' );
        cloud.className = 'cloudLayer';

        cloud.data = { 
            x: random_x,
            y: random_y,
            z: random_z,
            a: random_a,
            s: random_s
        };
        var t = 'translateX( ' + random_x + 'px ) \
            translateY( ' + random_y + 'px ) \
            translateZ( ' + random_z + 'px ) \
            rotateZ( ' + random_a + 'deg ) \
            scale( ' + random_s + ' )';
        cloud.style.transform = t;

        div.appendChild( cloud );
        layers.push( cloud );
    }

    return div;
}

點我來觀察第三部分的演示

雲層就是那些藍色帶有一點白色邊邊的部分,看起來相當有層次感吶。移動滑鼠來觀察下每一層的位置是怎樣的以及如何旋轉的。

4.讓3D效果執行起來

接下來就是見證奇蹟的時刻!我們用 layers[] 來為世界裡每一個單獨的雲層建立一個引用。我們用 worldXangleworldYAngle 來表示整個空間的選擇變換。

如果我們將每個層都設定成相反的旋轉,這會在viewport裡重新調整他們:我們就有一個佈告板了。因為我們旋轉world先是X方向然後是Y方向,我們需要反著順序來旋轉每一個層,首先是Y方向再是X方向,變換的順序是非常重要的,如果你沒有正確的設定順序,元素的方向就都會不對了

/*
    Iterate layers[], update the rotation and apply the
    inverse transformation currently applied to the world.
    Notice the order in which rotations are applied.
*/
function update (){    
    for( var j = 0; j < layers.length; j++ ) {
        var layer = layers[ j ];
        layer.data.a += layer.data.speed;
        var t = 'translateX( ' + layer.data.x + 'px ) \
            translateY( ' + layer.data.y + 'px ) \
            translateZ( ' + layer.data.z + 'px ) \
            rotateY( ' + ( - worldYAngle ) + 'deg ) \
            rotateX( ' + ( - worldXAngle ) + 'deg ) \
            scale( ' + layer.data.s + ')';
        layer.style.transform = t;
    }

    requestAnimationFrame( update );

}

來回的移動滑鼠,你將會看到藍色的雲層現在已經變成垂直的了(他們總是面對這鏡頭),但是 world 和其他雲基體還是三維空間裡的物件。

點我來見證神奇的效果

最後總結

為了達到最後的效果,只需要將用來除錯的那些顏色去掉,把雲層 div 用一個 img 貼上雲的圖片。注意圖片得是帶有alpha通道的PNG格式,要不沒效果。

點我看看最後的效果

點我看看最後版本

當然,你可以隨意換其他你想要的圖片:煙霧痕跡,等離子體雲,綠葉,飛行的麵包機等等。只要把 backgroud-image 更改下就好了。混合不同比例的紋理材質會有神奇的效果。

隨機的新增元素是可以的,但是你也可以建立有序的結構,比如樹,鴨子形狀的雲或者複雜的大爆炸等。可以嘗試一些3D曲線,建立固定的雲的執行軌跡,創造一個多人遊戲來猜3D雲的形狀等等,充滿著無限可能。

NOTE:文中的程式碼都是簡化了的哦,如果想一步一步實際操作的話最好儲存下示例效果的連結頁面來檢視原始碼。

本文譯自 https://www.clicktorelease.com/blog/how-to-make-clouds-with-css-3d#

相關文章