CSS魔法|MagicDesign - CSS实现3D拐角轮播图

3,522 阅读6分钟

1 前言

大家好,我是心锁,一枚23届准毕业生。

近期我正在尝试完成所谓的「拐角轮播」,目前拐角的部分已经完成了百分之七八十,所以我们来解析一下如何实现这种CSS 3D效果

2 演示

这里是体验链接,给各位大佬呈上grinzero.github.io/magic-desig…

2022-08-26 13.45.53

当然效果有些残缺哈......

87923D69A780C4012B16BDA5F4718552

别急着走嘛,我们虽然没有原图在拐角处实现的那么丝滑,但是也算是实现了方案中的3D容器方案

image-20220826165019691

那我究竟是怎么把卡片3D化的呢?

(补充码上掘金的简易版本)

3 3D折叠容器实现

众所周知,起码截止目前为止,CSS并不支持把单一一个页面元素折叠成曲面或者说部分折叠。

在这个基础上,我们必定需要使用到多个元素叠加。

3.1 基本框架

我们得到这样一份基本框架

<div className="swiper-container">
	<div
		className="mg-w-full mg-h-full mg-relative corner-swiper"
		ref={listRef}
	>
		这里放SwiperItem
	</div>
</div>

我们暂时不需要知道swiper-container属性,我们现在仅需要关注SwiperItem的上层,我们在这里实现3D容器,而在swiper-container只实现Swiper的滑动功能。

我们在corner-swiper真正实现3D容器

3.2 容器的切割拼接

众所周知,圆形是可以由无数个正多边形拼合形成的

image-20220826171510765

而我实现3D容器的思路就是切割,我目前将3D容器切成了两份,通过absolute叠加在一起,这里的实现并不麻烦,是absolute的应用,就不贴代码了。

2022-08-26 17.20.40

再接着就是切割

我们要用到的CSS属性是clip-path,如图的三份元素切片的实现,我们借助clip-path是比较容易实现的

2022-08-26 17.37.52

.swiperElement1{
  clip-path:polygon(0% 0, 20% 0, 20% 100%, 0% 100%);
}
.swiperElement2{
  clip-path:polygon(20% 0, 26% 0, 26% 100%, 20% 100%);
}
.swiperElement3{
  clip-path:polygon(26% 0, 100% 0, 100% 100%, 26% 100%);
}

可以看到,我们通过polygon函数将各个元素按照百分比切割成了三份,这三个元素可以完整地拼成原本的容器

当然,可能是浏览器原因,拼接之间会存在一条白线

image-20220826174323924

我们可以通过translateX属性从右到左渐进的+1px来隐藏这条白线

.swiperElement1{
  clip-path:polygon(0% 0, 20% 0, 20% 100%, 0% 100%);
  transform:translateX(2px);
}
.swiperElement2{
  clip-path:polygon(20% 0, 26% 0, 26% 100%, 20% 100%);
  transform:translateX(1px);
}
.swiperElement3{
  clip-path:polygon(26% 0, 100% 0, 100% 100%, 26% 100%);
  transform:translateX(0px);
}

那么现在3D容器的切割拼接就完毕了,接下来我们要将容器3D化。

image-20220826174814687

3.3 容器3D化

说到3D化,我们将使用到transform3D系列的属性,我可以先列举一下我们用到的属性

  • perspective

  • perspective-origin

  • transform-style: preserve-3d

  • transform: rotateX、rotateY

  • transform-origin

3.3.1 perspective

perspective,透视,是CSS 3D的基础,transform-styleperspective任意一个属性不配置都不会产生3D效果。

根据MDN的解释,perspective指定了观察者(也就是我们)和屏幕z之间的距离

可以在这个链接感受一下developer.mozilla.org/zh-CN/docs/…

image-20220826191003794

3.3.2 perspective-origin

而perspective-origin操控透视的视角

image-20220826194511370

我们可以通过该属性控制拐角的弧度

image-20220826194655510

需要注意,两个透视相关需要放在父容器

3.3.3 transform-style

其中transform-style最为简单,指定transform变换的类型是3D模式

.swiperElement1{
  clip-path:polygon(0% 0, 20% 0, 20% 100%, 0% 100%);
  transform:translateX(2px);
  transform-style:preserve-3d;
}
.swiperElement2{
  clip-path:polygon(20% 0, 26% 0, 26% 100%, 20% 100%);
  transform:translateX(1px);
  transform-style:preserve-3d;
}
.swiperElement3{
  clip-path:polygon(26% 0, 100% 0, 100% 100%, 26% 100%);
  transform:translateX(0px);
  transform-style:preserve-3d;
}

3.3.4 transform: rotateX、rotateY

transform中的rotateX、rotateY都是3D旋转,这两种旋转在3d模式是什么表现呢?我这里给些例子

  • rotateX

image-20220826183238839

这里是45deg角度的X轴旋转,我们可以看到x轴的表现如图

  • rotateY

image-20220826183630335

而这是y轴旋转,我们会发现目前y轴在最中心,从展示角度来讲这是正确的,但是实际中我们需要把原点移动。

这也就引申出来了transform-origin

3.3.5 transform-origin

在上述例子中,我们改变的是swiperElement1,而swiperElement1是在20%的地方做了切割

所以我们把y轴对准20%,这样我们就可以将y轴搬运到20%这里

.swiperElement1{
  clip-path:polygon(0% 0, 20% 0, 20% 100%, 0% 100%);
  transform:translateX(2px);
  transform-style:preserve-3d;
  transform-origin:20%;
}

image-20220826190357109

3.4 再次拼接

我们会发现,如果我们给swiper1,swiper2配置了3D化之后,第三个元素和前边就接不上了。

image-20220826195957438

这是很显然的问题,因为前两个元素拼接之后,第二个元素的最右方z轴实际上是上移了

image-20220826200503772

所以我们需要计算出Z轴移动了多少,将前边所有的元素或者后边的元素移动回去

2022-08-26 20.06.31

对于我们正在做的demo来说,目前代码是这样的

.swiperElement1{
  clip-path:polygon(0% 0, 20% 0, 20% 100%, 0% 100%);
  transform:translateX(2px);
  transform-style:preserve-3d;
  transform:translateX(2px) rotateY(-45deg);
  transform-origin:20%;
}
.swiperElement2{
  clip-path:polygon(20% 0, 26% 0, 26% 100%, 20% 100%);
  transform:translateX(1px);
  transform-style:preserve-3d;
  transform:translateX(1px) rotateY(-22.5deg);
  transform-origin:20%;
}
.swiperElement3{
  clip-path:polygon(26% 0, 100% 0, 100% 100%, 26% 100%);
  transform:translateX(0px);
  transform-style:preserve-3d;
  transform:translateX(-3px) translateZ(17.25px);
}

可以看到swiperElement3,存在属性transform:translateX(-3px) translateZ(17.25px);

这其中的translateX(-3px) translateZ(17.25px)是怎么计算的呢?

我们俯视得到透视图

image-20220826203030787

那么现在我们只需要建模出X的计算公式和Z的计算公式,我们可以假设全长为“1”。

注意上图的所有蓝色字体均指x轴宽,而不是直接长度

...

看到这里你是不是以为我算出来了

....

不,我不太想算了

BB7DA1FB17EF781DE65FBC8CFE1C0AB6

咱把思路已经给得很明白了,亲爱的读者你动动手啊,直接告诉我答案,我要白嫖答案

85C924C681A148B9008FD03DBA4835D3

4 数据建模

虽然我没有对三个元素叠加进行建模,不过我做了两个元素的建模

image-20220826205213630

如图,将clip-path的部分属性和transform-origin通过CSS变量var(--position)来绑定。

同时在之前提到的swiper-container层通过translateX来控制Swiper的「切换current操作」

image-20220826205434586

之所以最终没有做3个面乃至于多个面的建模,一方面是麻烦,一方面是实现效果上。

三个元素的叠加其实效果并没有说特别好,而往上走到5、6或者更多元素叠加,其实对于网页的性能是会产生一定压力的。

那么,我们最终得到~

2022-08-26 21.02.12

5 总结

那么我们实践完这个堪称变态的拐角轮播,学习到了什么呢?

  • 一个复杂动画的实现需要一定的数学建模
  • CSS 3D的作用条件以及X、Y、Z轴的实际作用展现
  • clip-path与不同的orgin的实际作用展现

当然~如果亲只想试试这个效果,打开grinzero.github.io/magic-desig…

(文档刚刚写,组件也不是很完善,还很简陋,见谅见谅)