马黑黑 发表于 2025-7-9 14:18

菇凉、竹子和CSS三维动画

本帖最后由 马黑黑 于 2025-7-9 16:41 编辑 <br /><br /><style>
    .papa {
      margin: 20px auto;
        left: calc(50% - 81px);
        transform: translate(-50%);
      width: 1200px;
      height: 700px;
      border: 1px solid olive;
      background: url('https://638183.freep.cn/638183/t24/5/cshow.jpg') no-repeat center/cover;
      perspective: 800px;
      position: relative;
    }
    .lzwrap {
      position: absolute;
      width: 100%;
      height: 100%;
      background: none ;
      transform-style: preserve-3d;
      display: grid;
      place-items: center;
    }
    .lzwrap::before {
      position:absolute;
      content: '点击小球开始动画';
      left: 20px;
      bottom: 20px;
    }
    .lzwrap::after {
      position: absolute;
      content: url('https://638183.freep.cn/638183/small/vuzi.png');
      left: 20%;
      bottom: 0;
      background: url('https://638183.freep.cn/638183/small/vuzi.png') no-repeat center/cover;
      transform: rotateY(15deg);
    }
    li-zi {
      --size: 50px;
      position: absolute;
      width: var(--size);
      height: var(--size);
      border-radius: 50%;
      background: linear-gradient(35deg, lightgreen, skyblue);
      transform: rotateY(0) translate3d(0, 100px, 400px);
      cursor: pointer;
    }
    .ani {
      animation: rot 16s ease-in-out;
    }
    @keyframes rot {
      to {
            transform: rotateY(720deg) translate3d(0, 100px, 400px) rotateY(-720deg);
      }
    }
</style>

<div class="papa">
    <div class="lzwrap">
      <li-zi title="点击运行"></li-zi>
    </div>
</div>

<script>
    var lz = document.querySelector('li-zi');
    var isRunnig = false;

    lz.onclick = () => {
      if (isRunnig) return;
      lz.classList.add('ani');
    }

    lz.onanimationstart = () => isRunnig = true;

    lz.onanimationend = () => {
      if (!isRunnig) return;
      lz.classList.remove('ani');
      isRunnig = false;
    };
</script>

马黑黑 发表于 2025-7-9 14:22

本帖最后由 马黑黑 于 2025-7-9 17:45 编辑

本帖演示小球在CSS三维动画时其与竹子、菇凉的位置遮挡关系。菇凉是外层容器的背景图片的内容,不存在于3d场景中,因此小球总是从她面前经过;竹子则与小球一样同属3d景物,存在三维空间物体间的遮挡关系,因此小球可以再竹子前后绕行。

具体代码如下:


<style>
    .papa {
      margin: 20px auto;
      width: 1200px;
      height: 700px;
      border: 1px solid olive;
      background: url('https://638183.freep.cn/638183/t24/5/cshow.jpg') no-repeat center/cover;
      perspective: 800px;
      position: relative;
    }
    .lzwrap {
      position: absolute;
      width: 100%;
      height: 100%;
      background: none ;
      transform-style: preserve-3d;
      display: grid;
      place-items: center;
    }
    .lzwrap::before {
      position:absolute;
      content: '点击小球开始动画';
      left: 20px;
      bottom: 20px;
    }
    .lzwrap::after {
      position: absolute;
      content: url('https://638183.freep.cn/638183/small/vuzi.png');
      left: 20%;
      bottom: 0;
      background: url('https://638183.freep.cn/638183/small/vuzi.png') no-repeat center/cover;
      transform: rotateY(15deg);
    }
    li-zi {
      --size: 50px;
      position: absolute;
      width: var(--size);
      height: var(--size);
      border-radius: 50%;
      background: linear-gradient(35deg, lightgreen, skyblue);
      transform: rotateY(0) translate3d(0, 100px, 400px);
      cursor: pointer;
    }
    .ani {
      animation: rot 16s ease-in-out;
    }
    @keyframes rot {
      to {
            transform: rotateY(720deg) translate3d(0, 100px, 400px) rotateY(-720deg);
      }
    }
</style>

<div class="papa">
    <div class="lzwrap">
      <li-zi title="点击运行"></li-zi>
    </div>
</div>

<script>
    var lz = document.querySelector('li-zi');
    var isRunnig = false;

    lz.onclick = () => {
      if (isRunnig) return;
      lz.classList.add('ani');
    }

    lz.onanimationstart = () => isRunnig = true;

    lz.onanimationend = () => {
      if (!isRunnig) return;
      lz.classList.remove('ani');
      isRunnig = false;
    };
</script>


若有错误,敬请斧正,本人将感激不尽!

马黑黑 发表于 2025-7-9 14:24

菇凉、竹子和CSS三维动画相关解释:

先讲一下设置HTML为三层结构的目的:确保小球的transform 3d运动全程可见。设若只设计两层HTML标签结构,则当容器标签设置了背景,做3d运动的元素即第二层标签元素将在背向观者时不可见,而通常情况下我们有需要容器元素有背景,为此,容器元素和做3d运动子元素之间应该建立一个中间层,该层元素设置背景为无(推荐)或透明,即transparent或类似rgba(0,0,0,0)颜色表达体系,它之下才是做3d运动的元素。

解释核心代码:

一、容器元素(class="papa"的div)设置透视视图

    perspective: 800px;

透视视图也可以理解为相机的位置,因它的存在而在容器元素场景下产生景深效果,后续其内的子元素或孙辈元素因之而拥有渲染3d效果的舞台。perspective属性值会直接影响子孙元素在Z轴上的效果,因为景深的方向和Z轴同向,是观者和屏幕表面(xy面)的视线方向,遵循自然规律的远小近大的规则。

二、中间层元素(class="lzwrap")设置3d转换形式

    transform-style: preserve-3d;

真正的三维场景要求物体间拥有真实的遮挡关系,以上设置确保这个关系的正确建立。transform-style属性缺省时默认采用flat值,该值下的3d场景不能虚拟真实世界的三维物体间的遮挡关系,必须设置值为preserve-3d才可以。该属性也可以设置在容器层元素,但既然有中间层,强烈建议将其放在中间层。另外,非常重要的是,设置了transform-style: preserve-3d;的元素不能设置防溢出属性overflow: hidden;,否则preserve-3d的设置无效。

另外,在中间层标签里,通过grib网格布局及其相关属性规范了其下子元素的位置即上下左右居中。子元素的初始位置与其后续的动画有关联,后面会继续解释。

三、呈现三维效果的元素transform转换设置

这里我们使用了一个自定义标签 li-zi,这是依据CSS3的规范设计的标签,标签名称需要由连接符 - 将头尾两部分连接起来。使用自定义标签主要是为了让标签名称更具备语义化含义。此为外话。

li-zi标签初始化了一些CSS属性设置,其中下面这句——

    transform: rotateY(0) translate3d(0, 100px, 400px);

——它的作用是告诉浏览器li-zi元素在Y轴旋转0度、在X轴上平移0像素、在Y轴上平移100像素、在Z轴上平移400像素。就是说,通过transform属性初始化了li-zi的位置。

前面提到,中间层元素规范了子元素的绝对居中,此处代码,通过Y轴上的位置设置li-zi元素安放在了预设的位置:纵向偏下的地方(0 是Y轴中心)。

四、关键帧动画 keyframes 设置

关键帧动画延续使用li-zi元素的transform属性,外加一个倒回来的Y轴上的旋转以确保li-zi在自转时不变形:

    @keyframes rot {
      to {
            transform: rotateY(720deg) translate3d(0, 100px, 400px) rotateY(-720deg);
      }
    }

这里,rotateY() 在先,translate3d() 在后,这样能令运行关键帧动画的元素做弧线移动。注意查看 translate3d 小括号里面的参数,和初始化值保持一致,就是说,通过此transform转换设置,仅通过li-zi的自转就能让li-zi做弧线行进运动,其原理是,li-zi元素在Y轴上做自转的同时在Z轴上平移400px,换言之,li-zi的每一次自转都会在Z轴上和父层元素的中心点 0 保持 400px 的距离,为了维持这个距离,每一次的转动都会绕Z轴做弧线位移。这里还需要理解一下li-zi在Y轴上离开中心点 0 有 100px 的距离但自转时不会在Y轴上产生弧线位置移动的原理——因为li-zi的自转是在Y轴上进行的自转。然后是li-zi自转的倒转,前面rotateY多少度,最后跟着倒回多少度,因此运动过程中li-zi并没有旋转,都实时抵消了,但实现了li-zi的弧线运动。

补充一句:自转角度设置为720度意味着li-zi一个运动周期内做两圈弧线运动,圆的度数是360度。

五、li-zi元素运行关键帧动画 animation 设置

    animation: rot 16s ease-in-out;

这句可以直接放在 li-zi { ... } 属性体内,本例处于演示需要,把它放在一个类标签里,这样更方便实现JS交互功能。

樵歌 发表于 2025-7-9 17:48

这球太色了{:4_358:}

马黑黑 发表于 2025-7-9 18:45

樵歌 发表于 2025-7-9 17:48
这球太色了

额,这是彩球

红影 发表于 2025-7-9 21:28

“运动过程中li-zi并没有旋转,都实时抵消了,但实现了li-zi的弧线运动。”
这个挺难理解的。如果没这个旋转,就是400之间的直线运动了吧,有这个菜带出的弧线运动?{:4_187:}

红影 发表于 2025-7-9 21:30

这个帖子用竹子做参照,很清楚地标示了中间层的作用{:4_204:}

马黑黑 发表于 2025-7-9 21:54

红影 发表于 2025-7-9 21:28
“运动过程中li-zi并没有旋转,都实时抵消了,但实现了li-zi的弧线运动。”
这个挺难理解的。如果没这个旋 ...

道理是酱紫:

rotateY + translateZ 驱动了 lz 的弧线位移;

后面的反向自转 rotateY 则在弧线位移之后实时抵消了自转。

马黑黑 发表于 2025-7-9 21:55

红影 发表于 2025-7-9 21:30
这个帖子用竹子做参照,很清楚地标示了中间层的作用

总体弧线运动的知识体系和过去介绍的2d关键帧动画中圆弧运动是一致的,这里只是引入了 Z轴,看着复杂了不少。

红影 发表于 2025-7-9 23:05

马黑黑 发表于 2025-7-9 21:54
道理是酱紫:

rotateY + translateZ 驱动了 lz 的弧线位移;


嗯嗯,和之前的旋转加平移导致的弧线一样,只是这里抵消了自转{:4_187:}

红影 发表于 2025-7-9 23:06

马黑黑 发表于 2025-7-9 21:55
总体弧线运动的知识体系和过去介绍的2d关键帧动画中圆弧运动是一致的,这里只是引入了 Z轴,看着复杂了不 ...

是的,多了个z轴,感觉一下很难理解了。

马黑黑 发表于 2025-7-10 12:51

红影 发表于 2025-7-9 23:05
嗯嗯,和之前的旋转加平移导致的弧线一样,只是这里抵消了自转

对,原理是酱紫的

马黑黑 发表于 2025-7-10 12:52

红影 发表于 2025-7-9 23:06
是的,多了个z轴,感觉一下很难理解了。

这个其实不应该

红影 发表于 2025-7-10 21:32

马黑黑 发表于 2025-7-10 12:51
对,原理是酱紫的

平面的和三维的,原理都一样。

红影 发表于 2025-7-10 21:32

马黑黑 发表于 2025-7-10 12:52
这个其实不应该

嗯,一个是在屏幕上绕圈,一个是在纵深上绕圈。

马黑黑 发表于 2025-7-10 22:18

红影 发表于 2025-7-10 21:32
嗯,一个是在屏幕上绕圈,一个是在纵深上绕圈。

{:4_181:}

马黑黑 发表于 2025-7-10 22:18

红影 发表于 2025-7-10 21:32
平面的和三维的,原理都一样。

差不多的

红影 发表于 2025-7-10 23:07

马黑黑 发表于 2025-7-10 22:18


平面打成了屏幕,哈哈,总打错字{:4_173:}

红影 发表于 2025-7-10 23:08

马黑黑 发表于 2025-7-10 22:18
差不多的

理解了平面的,三维的也比较好理解了。

马黑黑 发表于 2025-7-11 22:05

红影 发表于 2025-7-10 23:08
理解了平面的,三维的也比较好理解了。

点线面体,一步一步扩展
页: [1] 2 3 4 5
查看完整版本: 菇凉、竹子和CSS三维动画