CSS3 中的動畫之 3D 變形

3D 變換是 CSS3 非常重要的一項特性,有了它我們可以制作出很多 3D 效果,配合運動可以實現(xiàn)很炫的特技。學了它,網(wǎng)站更加炫酷了,前端er 們工資更高了,和 UI 妹妹們交流起來更加愉快了。
3D 變換還是在 transition 的基礎(chǔ)上進行變換,相比于平面的 X 和 Y 軸變換,多了一條 Z 軸變換。同時,有了景深(prespective)的概念。

rotate 屬性

認識 3D 變換,首先從 X、Y、Z 三條坐標軸說起,而對應(yīng)著三條坐標軸,有了 rotateX、rotateY、rotateZ 這三個函數(shù),分別表示在這三條軸上進行的旋轉(zhuǎn)操作。
三條坐標軸可以像這樣進行簡單的理解:

  • X 軸:平行于水平面的軸
  • Y 軸:垂直于水平面的軸
  • Z 軸:垂直于元素的軸

這篇文章有十分詳細的介紹,本文也主要參考了這篇文章的思路。

transform-style

transform 變形默認是在 2D 平面上,如果我們想進行 3D 變換,就需要進行明確指定:

transform-style:preserve-3d | flat (默認);

該屬性作用在容器元素上,表示其內(nèi)部的元素將以 3D 效果呈現(xiàn)。

prespective 景深

景深可以簡單理解為透視點,通過改變透視點的位置,可以看到不同的透視效果。CSS3 中的 prespective 也類似于透視點,但和常規(guī)透視點的區(qū)別是:CSS3 中的透視點在顯示器的前方,而不是后方。電腦顯示器遮擋了我們的視線,如果將透視點放在顯示器后方,是沒有什么意義的。
prespective 可以理解為站在多遠的距離上看 3D 效果,所謂近大遠小,當 prespective 足夠大時,我們看到的圖形就足夠小,當 prespective 足夠小時,我們看到的圖形就足夠大。
想象你的眼睛是一個攝像頭,3D 圖形是我們要拍攝的東西,當距離足夠遠時,拍攝到的物體就足夠小,甚至成為一個點。當距離足夠近時,拍攝到的物體就足夠大,當距離為零時,此時攝像頭貼近了物體,照片就被物體鋪滿了。
這就是為什么將 prespective 設(shè)置為足夠小時,圖形鋪滿整個顯示器的原因了。
prespective 屬性可以用在所有動畫元素的父級容器上,也可以用在某個特定的元素上,二者的效果是有差別的,一個表示對容器內(nèi)所有的元素應(yīng)用該景深,呈現(xiàn)出的是整體效果,一個表示對特定的元素應(yīng)用該景深,呈現(xiàn)出的是個體的效果。
如:

/* 給所有動畫共同的父級元素添加 prespective 屬性 */
.stage {
    perspective: 600px;
}
/* 給特定元素添加 prespective 屬性 */
#stage .box {
    transform: perspective(600px) rotateY(45deg);
}

如果每個元素都設(shè)置相同的 prespective 值,那么他們看起來是一樣的。如果給容器元素設(shè)置同樣值的 prespective,其內(nèi)部的元素呈現(xiàn)的效果并不是一樣的。關(guān)于這個問題同樣可以在這篇文章中找到相關(guān)的例子。

perspective-origin

如果說將 prespective 理解為我們距離顯示器的距離,那么 prespective-origin 就是我們的視線和顯示器上圖像所成的角度了。表示從哪個方向去看這個物體。其值默認為 center center,我們可以自行指定。

translateZ

我們知道 Z 軸是垂直于元素的坐標軸,那么 translateZ 函數(shù)就是將物體在垂直于元素的方向進行位移,translateZ 函數(shù)移動的距離越大,那么物體就距離透視點越近,當 translateZ 移動到和 prespective 值一樣時,顯示器就被該物體鋪滿啦。
下面這張圖可以幫助我們理解坐標軸以及對物體進行位移時正負值所呈現(xiàn)出的效果:

3D 坐標軸.jpg

下面,根據(jù)上面的知識,我們來實現(xiàn)一個 3D 的立方體效果。

3D 立方體

要想構(gòu)造一個立方體,首先需要構(gòu)造出一個展開圖,就像這樣:

立方體展開圖.png

其中 1 面和 6 面重合,2/3/4/5 面為側(cè)面,要構(gòu)造出一個立方體,我們只需將 2/3/4/5 這幾個面沿著繞著底邊轉(zhuǎn),再將 6 面沿著 Z 軸位移就可以了。
下面看下布局:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>起個什么名字好呢</title>
    <style>
        .box{
            width: 500px;
            height: 400px;
            margin: 100px auto;
            border-radius: 20px;
            box-shadow: 2px 2px 10px rgba(0,0,0,0.5);
            perspective: 1200px;
        }

        .box ul{
            list-style: none;
            margin: 0;
            padding: 0;
            width: inherit;
            height: inherit;
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
            transform-style: preserve-3d;
            transform: rotateX(26deg) rotateY(-20deg) rotateZ(15deg);
        }

        .box ul li{
            width: 100px;
            height: 100px;
            /* border: 1px solid #a05454; */
            text-align: center;
            font:40px/100px arial;
            position: absolute;
            color: #fff;
            /* background: rgba(167, 60, 128, 0.3); */
            opacity: 0.6;
        }

        .box li:nth-of-type(1){
            background: red;
        }

        .box li:nth-of-type(2){
            margin-left: -100px;
            transform: rotateY(-90deg);
            transform-origin: center right;
            background: blue;
        }

        .box li:nth-of-type(3){
            margin-left: 100px;
            transform: rotateY(90deg);
            transform-origin: center left;
            background: orange;
        }
        .box li:nth-of-type(4){
            margin-top: -100px;
            transform: rotateX(90deg);
            transform-origin: bottom center;
            background: #333;

        }
        .box li:nth-of-type(5){
            margin-top: 100px;
            transform: rotateX(-90deg);
            transform-origin: top center;
            background: green;

        }
        .box li:nth-of-type(6){
            transform:translateZ(-100px) rotateX(180deg);
            background: yellow;
        }

    </style>
</head>
<body>
    <div class="box">
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
        </ul>
    </div>
</body>
</html>

看下效果:

3d立方體.png

接下來做一個旋轉(zhuǎn)木馬的效果。

旋轉(zhuǎn)木馬效果

趁熱打鐵,再來一波旋轉(zhuǎn)木馬的效果。看下基本布局:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>起個什么名字好呢</title>
    <style>
        .box{
            width: 800px;
            height: 300px;
            margin: 100px auto;
            perspective: 1200px;
        }

        #stage{
            width: inherit;
            height: inherit;
            list-style: none;
            margin: 0;
            padding: 0;
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
            transform-style: preserve-3d;
        }

        li{
            position: absolute;
            width: 200px;
            height: 100px;
        }

        li:nth-of-type(1){
            transform: rotateY(0deg) translateZ(200px);
            background: red;
        }

        li:nth-of-type(2){
            transform: rotateY(72deg) translateZ(200px);
            background: blue;
        }

        li:nth-of-type(3){
            transform: rotateY(144deg) translateZ(200px);
            background: orange;
        }

        li:nth-of-type(4){
            transform: rotateY(216deg) translateZ(200px);
            background: green;
        }

        li:nth-of-type(5){
            transform: rotateY(288deg) translateZ(200px);
            background: pink;
        }
    </style>
</head>
<body>
    <div class="box">
        <ul id = "stage">
            <li>
                <img src="" alt="">
            </li>
            <li>
                <img src="" alt="">
            </li>
            <li>
                <img src="" alt="">
            </li>
            <li>
                <img src="" alt="">
            </li>
            <li>
                <img src="" alt="">
            </li>
        </ul>
    </div>
</body>
</html>

看下效果:

旋轉(zhuǎn)木馬布局.png

旋轉(zhuǎn)木馬的原理:將所有的圖片圍成一個圓周,因此需要通過 rotateY 將圖片進行旋轉(zhuǎn),每個圖片旋轉(zhuǎn)的角度為:360deg / 圖片數(shù)量。由于每個圖片都是沿著圖片中心進行旋轉(zhuǎn)的,因此旋轉(zhuǎn)后圖片會擠成一團,就像這樣:

旋轉(zhuǎn)效果01.png
旋轉(zhuǎn)效果02.png

因此我們需要在旋轉(zhuǎn)時,讓每個圖片沿著 Z 軸做一些位移,拉開它們之間的距離,就像上面代碼中的做法。
再來看一下俯視效果:


旋轉(zhuǎn)效果03.png

元素最佳的位移距離:
要讓所有元素剛好圍成一個圓,我們可以使用三角函數(shù)進行計算。

計算最佳位移.png

計算公式為:

r = 0.5 * w / Math.tan( 0.5*d / 180 * Math.PI)

如果每個圖片位移 r 距離,可以剛好圍成一個圓,為了讓圖片之間有一些空隙,我們可以再適當增加一點距離。

讓木馬動起來

基本的布局完成后,就可以寫 JavaScript 來控制木馬的旋轉(zhuǎn)了。
首先給舞臺元素加一個過渡:

...
#stage{
    width: inherit;
    height: inherit;
    list-style: none;
    margin: 0;
    padding: 0;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    transform-style: preserve-3d;
    transition: all 1s;
}
...

完成 JavaScript 代碼:

<script>
    const img_srcs = [
        "./imgs/1.jpg",
        "./imgs/2.jpg",
        "./imgs/3.jpg",
        "./imgs/4.jpg",
        "./imgs/5.jpg",
        "./imgs/6.jpg",
        "./imgs/7.jpg",
        "./imgs/8.jpg",
    ];
    const stage = document.getElementById("stage");

    function rotateStage(num){
        stage.style.transform = `rotateY(${-num * 360 / img_srcs.length}deg)`;
    } 

    (function(img_srcs){
        const deg = 360 / img_srcs.length;
        for(let i = 0; i < img_srcs.length;i ++){
            const li = document.createElement("li");
            const img = document.createElement("img");

            li.style.transform = `rotateY(${ deg * i}deg) translateZ(${80 / Math.tan( 0.5*deg / 180 * Math.PI) + 30}px)`;
            img.src = img_srcs[i];

            li.addEventListener("click",(e)=>{ rotateStage(i) });
            li.appendChild(img);
            stage.appendChild(li);
        }
    }(img_srcs));
</script>

看下實現(xiàn)效果:

旋轉(zhuǎn)木馬效果.gif

總結(jié)

本文主要學習了 CSS3 中的 3D 變形,通過 3D 變形的學習,我們可以做出一些很有意思的特效。下面是本文中的一些知識點總結(jié):

  • transform-style:用來定義子元素是否進行 3D 變換
  • perspective:景深,用來定義 3D 變換的透視點,建議將該屬性加給效果中所有用到變形(transform)的元素的父級(如旋轉(zhuǎn)木馬效果中的 .box)
  • perspective-origin:視點,表示從哪個方向去看這個物體
  • translateZ:將元素沿著 Z 軸進行位移,Z 軸就是垂直于元素的坐標軸

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • CSS的變形對應(yīng)的屬性是transform,它的作用是修改元素自身的坐標空間。這個修改實際對應(yīng)了一個坐標系統(tǒng)映射轉(zhuǎn)...
    荷小音閱讀 1,142評論 1 5
  • 看了很多視頻、文章,最后卻通通忘記了,別人的知識依舊是別人的,自己卻什么都沒獲得。此系列文章旨在加深自己的印象,因...
    DCbryant閱讀 1,896評論 0 4
  • CSS里transform變形這個屬性有點學習難度,尤其在CSS3里加上了3D效果之后,2維變3維學習成本更是成倍...
    張歆琳閱讀 28,011評論 5 81
  • 一、寫在前面的秋褲 早在去年的去年,我就大肆介紹了2D transform相關(guān)內(nèi)容。看過海賊王的都知道,帶D的家伙...
    MrZengB閱讀 1,369評論 2 9
  • 1、屬性選擇器:id選擇器 # 通過id 來選擇類名選擇器 . 通過類名來選擇屬性選擇器 ...
    Yuann閱讀 1,657評論 0 7