CSS3 中的动画之 3D 变形

3D 变换是 CSS3 非常重要的一项特性,有了它我们可以制作出很多 3D 效果,配合运动可以实现很炫的特技。学了它,网站更加炫酷了,前端er 们工资更高了,和 UI 妹妹们交流起来更加愉快了。
3D 变换还是在 transition 的基础上进行变换,相比于平面的 X 和 Y 轴变换,多了一条 Z 轴变换。同时,有了景深(prespective)的概念。

rotate 属性

认识 3D 变换,首先从 X、Y、Z 三条坐标轴说起,而对应着三条坐标轴,有了 rotateX、rotateY、rotateZ 这三个函数,分别表示在这三条轴上进行的旋转操作。
三条坐标轴可以像这样进行简单的理解:

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

这篇文章有十分详细的介绍,本文也主要参考了这篇文章的思路。

transform-style

transform 变形默认是在 2D 平面上,如果我们想进行 3D 变换,就需要进行明确指定:

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

该属性作用在容器元素上,表示其内部的元素将以 3D 效果呈现。

prespective 景深

景深可以简单理解为透视点,通过改变透视点的位置,可以看到不同的透视效果。CSS3 中的 prespective 也类似于透视点,但和常规透视点的区别是:CSS3 中的透视点在显示器的前方,而不是后方。电脑显示器遮挡了我们的视线,如果将透视点放在显示器后方,是没有什么意义的。
prespective 可以理解为站在多远的距离上看 3D 效果,所谓近大远小,当 prespective 足够大时,我们看到的图形就足够小,当 prespective 足够小时,我们看到的图形就足够大。
想象你的眼睛是一个摄像头,3D 图形是我们要拍摄的东西,当距离足够远时,拍摄到的物体就足够小,甚至成为一个点。当距离足够近时,拍摄到的物体就足够大,当距离为零时,此时摄像头贴近了物体,照片就被物体铺满了。
这就是为什么将 prespective 设置为足够小时,图形铺满整个显示器的原因了。
prespective 属性可以用在所有动画元素的父级容器上,也可以用在某个特定的元素上,二者的效果是有差别的,一个表示对容器内所有的元素应用该景深,呈现出的是整体效果,一个表示对特定的元素应用该景深,呈现出的是个体的效果。
如:

/* 给所有动画共同的父级元素添加 prespective 属性 */
.stage {
    perspective: 600px;
}
/* 给特定元素添加 prespective 属性 */
#stage .box {
    transform: perspective(600px) rotateY(45deg);
}

如果每个元素都设置相同的 prespective 值,那么他们看起来是一样的。如果给容器元素设置同样值的 prespective,其内部的元素呈现的效果并不是一样的。关于这个问题同样可以在这篇文章中找到相关的例子。

perspective-origin

如果说将 prespective 理解为我们距离显示器的距离,那么 prespective-origin 就是我们的视线和显示器上图像所成的角度了。表示从哪个方向去看这个物体。其值默认为 center center,我们可以自行指定。

translateZ

我们知道 Z 轴是垂直于元素的坐标轴,那么 translateZ 函数就是将物体在垂直于元素的方向进行位移,translateZ 函数移动的距离越大,那么物体就距离透视点越近,当 translateZ 移动到和 prespective 值一样时,显示器就被该物体铺满啦。
下面这张图可以帮助我们理解坐标轴以及对物体进行位移时正负值所呈现出的效果:

3D 坐标轴.jpg

下面,根据上面的知识,我们来实现一个 3D 的立方体效果。

3D 立方体

要想构造一个立方体,首先需要构造出一个展开图,就像这样:

立方体展开图.png

其中 1 面和 6 面重合,2/3/4/5 面为侧面,要构造出一个立方体,我们只需将 2/3/4/5 这几个面沿着绕着底边转,再将 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

接下来做一个旋转木马的效果。

旋转木马效果

趁热打铁,再来一波旋转木马的效果。看下基本布局:

<!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>

看下效果:

旋转木马布局.png

旋转木马的原理:将所有的图片围成一个圆周,因此需要通过 rotateY 将图片进行旋转,每个图片旋转的角度为:360deg / 图片数量。由于每个图片都是沿着图片中心进行旋转的,因此旋转后图片会挤成一团,就像这样:

旋转效果01.png
旋转效果02.png

因此我们需要在旋转时,让每个图片沿着 Z 轴做一些位移,拉开它们之间的距离,就像上面代码中的做法。
再来看一下俯视效果:


旋转效果03.png

元素最佳的位移距离:
要让所有元素刚好围成一个圆,我们可以使用三角函数进行计算。

计算最佳位移.png

计算公式为:

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

如果每个图片位移 r 距离,可以刚好围成一个圆,为了让图片之间有一些空隙,我们可以再适当增加一点距离。

让木马动起来

基本的布局完成后,就可以写 JavaScript 来控制木马的旋转了。
首先给舞台元素加一个过渡:

...
#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>

看下实现效果:

旋转木马效果.gif

总结

本文主要学习了 CSS3 中的 3D 变形,通过 3D 变形的学习,我们可以做出一些很有意思的特效。下面是本文中的一些知识点总结:

  • transform-style:用来定义子元素是否进行 3D 变换
  • perspective:景深,用来定义 3D 变换的透视点,建议将该属性加给效果中所有用到变形(transform)的元素的父级(如旋转木马效果中的 .box)
  • perspective-origin:视点,表示从哪个方向去看这个物体
  • translateZ:将元素沿着 Z 轴进行位移,Z 轴就是垂直于元素的坐标轴

完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • CSS的变形对应的属性是transform,它的作用是修改元素自身的坐标空间。这个修改实际对应了一个坐标系统映射转...
    荷小音阅读 4,831评论 1 5
  • 看了很多视频、文章,最后却通通忘记了,别人的知识依旧是别人的,自己却什么都没获得。此系列文章旨在加深自己的印象,因...
    DCbryant阅读 5,854评论 0 4
  • CSS里transform变形这个属性有点学习难度,尤其在CSS3里加上了3D效果之后,2维变3维学习成本更是成倍...
    张歆琳阅读 28,230评论 5 81
  • 一、写在前面的秋裤 早在去年的去年,我就大肆介绍了2D transform相关内容。看过海贼王的都知道,带D的家伙...
    MrZengB阅读 5,141评论 2 9
  • 1、属性选择器:id选择器 # 通过id 来选择类名选择器 . 通过类名来选择属性选择器 ...
    Yuann阅读 5,581评论 0 7

友情链接更多精彩内容