关灯
开启左侧

【转】CSS 3D应该注意的事项

[复制链接]
油腻大叔 发表于 2019-3-24 20:52:45 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
 
转:http://www.sohu.com/a/115808352_488157
我一直喜欢3D。我也开始使用CSS 3D Transform,而且浏览器对它的支持度越来越好。但给我的感觉,使用Transform就是用来创建2D图形,并且使用旋转和位移可以创建一些3D图形。但在实际使用的时候,还是越到了不少的麻烦,而且这些麻烦出乎我的意料。我想或许大家也同样遇到过这样的问题,为了大家在使用CSS 3D Transform能避免这些麻烦,我把我碰到的与在大家分享一下。
3D渲染上下文
我清楚的记得一个晚上遇到一个麻烦,而且这个麻烦引起我的好奇心,于是我想亲自写一个测试用例来试试看,看看浏览器如何处理平面的交叉。该测试用例只包含了两个平面元素:
它们尺寸大小相同,并且使用绝对定位,让元素在屏幕中居中(),并且给他们设置了个背景色,让它们在屏幕中可见:
把body元素当作一个场景,并且设置了perspective(),让视角覆盖整个视窗。视角的值越大,元素似乎看起来离自已越远,看起来越小,反之越大。
测试示例实际上就是测试两个平面相交,所以使用Transform中的rotateY()做了一个Y轴旋转,并且设置了一个不同的背景颜色:
结果是令人失望的。浏览器似乎并没有正确的处理好两个平面交叉:
事实上是我错了,造成这个现象是我的代码导致的。我应该做的是让两个平面在。3D上下文渲染和还是不同的。就像如果它们不在同一个层叠上下文中,我们不能使用z-index来改变元素在z轴的顺序。同样的,如果不在同一个3D渲染范围内,3D Transform同样不能改变元素的顺序,让元素交叉。
为了确保这两个平面是在相同的3D渲染环境,最简单的方法就是把它们放在同一个容器中:
同样让容器元素通过绝对定位,让它放置在容器中间,同时给它设置一个transform-style:preserve-3d:
这样就解决了这个问题:
如果你使用Firefox查看上面的示例,你看到的效果是两个平面并没有交叉,这或许是Firefox独有的特性吧,但你使用Webkit内核浏览器或者Edge浏览器看到的效果是我们想要的效果。
上图演示是前面示例的效果,两个平面没有交叉。
现在你可能会感到非常奇怪,为什么不给这两个平面添加一个容器,不在场景中设置transform-style:preserve-3d就不能正常工作(在我们的示例中是body元素)?那么,在这种特殊情况下,如果在最初的示例中增加这个规则(在boyd元素是直接添加transform-style:preserve-3d),它就能正常工作(除非你是在Firefox浏览器查看这效果,正如前面所说,Firefox对于3D的顺序和交叉还是有问题的)。
实际上,如果我们想要在网页上使用3D,我们的场景可能不会直接是body元素,可能会在场景中添加其他属性,这些可能会影响页面的性能。
打断3D(造成压扁)
比方说,我们的场景是页面中的一个div元素,而且有其他的内容围绕着这个场景div:
在第二个元素上多添加了几个transform的属性,使之更加明显,看上去有些部分在场景外。上面示例看到的效果是我们不想要的。我们希望添加一些属性让文本更好的阅读。
overflow
最初让我想到的解决方案是在场景中添加一个overflow:hidden。然而,这样做是能让文本更好的阅读,但3D交叉效果又不正常了。
即使在场景中设置了preserve-3d,只要设置了overflow的值,就算是visible也会使效果变得像transform-style设置了flat(压平)一样。因此,多添加一个容器可能会让我写更多一点代码,但能让我们绕开这些麻烦。
这就是为什么我们要把一切元素都放置在一个独立的元素中,把这个容器当作场景,即使该元素不使用3D Transform。例如下面这个示例:
所有旋转的六边形列都放在.helix元素内:
除了保证整个组件绝对定位在视窗中间的样式之外,.helix元素没有任何其他样式(除非是继承下来的样式),并且所有的列都在同一个3D渲染范围内:
在场景中设置了overflow:hidden(这个示例body元素就是场景),这样做是因为六边形不依赖于视窗的大小,我不知道它们是否会延伸到容器外而导致滚动条的出现,这种现象是我不想看到的。
我承认我不止一次碰到这样的现象,从中吸引了相关教训。我保守起见,为了不让溢出出现在这里,直接使用overflow:hidden,这样能让溢出看起来不明显。
transform-style:preserve-3d设置了3D Transform的子元素不应该在他们的父元素内拍平(元素上设置了transform-style:preserve-3d)。因此,就算是直觉,场景中设置了overflow:hidden,防止其子元素打破他们的父容器,也不会让3D元素在场景内拍平。
但有时一个3D Transform子元素仍然可以在其父容器是平面的。比如说下面这种情况,我们一张具有两面的卡片:
通过绝对定位让所有元素在场景中(这个示例场景指的是body元素)水平垂直居中,并且给这两个卡片具有相同的大小尺寸。为了让这们在相同的空间,给卡片设置transform-style:preserve-3d。为了让背面可见,设置backface-visibility:hidden,并且在第二张卡片上设置rotate,让其沿着垂直轴(Y轴)旋转半圈(.5turn):
示例的效果如下所示:
两张卡片在他的父容器内仍然是平的,只不过第二张卡片绕着它的垂直轴(Y轴)旋转了半圈。它的朝向是相反的方式,但仍然是在同一平面上。到目前为止,这一切看上去都是正常的。
好,现在我想这两个卡片不是矩形的。给它们一个border-radius: 50%。但看起来没有任何变化:
接下来在.card上设置overflow:hidden,效果就正常了:
哎呀,这打破了我们的3D卡片。既然我们无法做到这一点,我们就在.face上设置:
在这种情况之下,解决这个问题的方法比打破3D卡这个问题更简单。但是,如果我们想要的是另一种形状,比如说一个正八边形,正八边形还是很容易实现的,比如采用两个元素(或者元素和伪元素的配合):
给这两个元素设置相同的尺寸,并且.inner元素设置rotate的值为45deg,给他们设置一个背景色,然后在.octagon元素上设置overflow:hidden,就可以看到一个正八边形:
你看到的八边形效果如下所示:
如果你对如何制作正多边形感兴趣,建议你阅读《》和《》。
如果希望在正多边形中添加文本呢?
你将看到的效果是这样,不尽人意:
造成这个现象是因为裁角的时候把文本也裁剪掉了。为了让文本能正常显示,给它设置一个text-align:center,并且设置一个line-height的值等于.octagon的高度(或者.inner),让文本垂直居中:
现在看起来好多了,但文本仍然是旋转的,因为.inner元素设置了一个rotate(45deg):
为了解决这个问题,只需要在.octagon元素上增加一个rotate,其旋转的角度值和.inner的旋转值一样,只是旋转方向刚好与.inner元素相反,所以是一个负值:
这下,文本在正八边形中的文本显示正常了:
现在让我们看看,如果我们想一正八边形的卡片,又将如何应用它。我们不能直接在.card上直接运用overflow:hidden(让它在.octagon元素上作用,同时两个面.inner又会是什么样)。因为在卡片上设置了overflow:hidden样式,根据前面介绍的内容,这样就会打破3D空间,让两个页不在同一个3D渲染环境。
替代方案是,需要把这些规则用在.octagon元素,并且使用它们的伪元素来做卡片的两个面:
最后看到的效果就是我们想要的效果:
clip-path
能引起类似的问题还有另一个属性clip-path。回到上面卡片的示例,我们不能在.card元素上直接使用clip-path来绘制三角形,因为我们需要一个3D Ttransform的子元素,也就是第二个面。我们应该用在卡片的面上:
注意:clip-path属性在Webkit内核浏览器下还是需要添加-webkit-前缀。对于Firefox(47+)浏览器需要通过about:config将layout.css.clip-path-shapes.enabled设置为true,另外Edge是不支持这个属性的(但你可以在这里,让Edge早日能支持这个属性)。
如果您从未接触过clip-path这个属性,建议你先阅读《》一文进行了解。
上面的代码看到的效果应该是这样的:
虽然不存在3D的问题,但效果看起来真的很别扭。如果从正面观察卡片,三角形的顶角是朝右的,按理说,后面应该是朝左的。但效果并不是我们所期望的,反面也朝右了。要解决这个问题,那么需要为不同的面设置不同的路径。clip-path绘制正面的三角形超右,绘制反正的三角形超左。
下面看到的效果才是我们想要的:
注意:还需要修改text-align的值:正面的默认值为left,反正的就需要设置为right。
另外,我们还可以在反面是使用scaleX(-1)来实现。如查你想进一步的了解scale的工作机制,可以看看下面的示例:
将上面介绍的原理运用到我们前面介绍的DEMO中:
效果如下:
这样看起来三角的方向是对了,但文字又出问题了。这意味着我们实际上要把文本放在背景元素的伪元素上,并且在.face元素上做一个反转。scale的反转就是设置另一个scale是1/f。在我们这个示例中,f就是-1。也就是说在伪元素上设置scale的值为1/-1=-1,就可以让文本看起来正常:
最后的效果如下:
如果mask设置非none值时,也会致使transform-style变为flat,就像overflow和clip-path设置了visible和none以外的值一样。
opacity
这是一个意想不到的问题。相对而言,这也是,如果在3D渲染环境下设置的opacity值小于1,效果就像是在层叠上下文一样(失去3D渲染上下文,就像前面所说的3D拍平)。这效果并不是在所有浏览器中都会发生的,比如在Edge、Safari和Brave下正常,而Chrome、Firefox和Opera看到的效果就是拍平后的效果。
请看下面的示例,一组立方体在3D空间内同时旋转:
结构很简单,在.assembly容器内有很多个(这个示例是有20个).cube元素,而且每个.cube有6个面。
现在我们说,想要的立方体是半透明的。那么我们这样做,能不能做到呢?
这样一来,就算在.cube上设置了transform-style:preserve-3d,也会变成flat的效果,就像.cube在它的父容器里被拍平了。现在只是Chrome、Opera和Firefox,但是在将来,所有浏览器都会是这样:
在Brave、Edge和Safari中,设置opacity值小于1时,立方体没有拍平。
在Chrome、Firefox、Opera浏览器中,结果是.cube被拍平了。
我们不能把opacity:0.5设置在已经设置了transform-style:preserve-3d的.assembly上。其效果和前面所说的将一样:
我们把opacity:0.5设置到.cube的每个面上,会不会引起同样的问题:
也可以把opacity设置在场景元素上(下面的示例是设置了body元素上),但需要注意,它也会影响场景的background或者伪元素。它也不会使个别立方体或面单独具有半透明度,只能整体一样,而且也没办法让不同的立方体有不同的透明值。
对比一下,在各个面上设置opacity和在场景中设置opacity效果差异:
上图的效果是在每个面设置opacity:0.5的效果。
上图的效果是在场景中设置opacity:0.5的效果。
filter
这个也让我感到惊奇,虽然不像opacity,但它在所有浏览器都是一样的。接着再拿3D立方体举例。通过hue-rotate()函数,让立方体在旋转的时候每个面都有不同的hue值。在.cube或者.assembly上设置filter的值不是none时,3D立方体就将会被拍平。
filter在Webkit内核浏览器中仍需要添加-webkit-前缀。
给每个面随机的色相有正常工作,但每个3D立方体还是被拍平了:
这个问题的解决方案是把filter设置在立方体的每个面上:
这样一来,3D立方体的每个面的色相是随机的,而且也在3D渲染上下文中,没有被拍平:
我们也不能把filter设置在.assembly上面。比方说,希望所有的立方体都具有模糊效果,你就为了方便在.assembly上设置filter:
其结果就是整个都被拍平了,平面变得模糊。Edge是个例外,直接一切都消失了。
我们可以做的就是尽量在立方体的每个面上使用blur(),虽然结果不会完全一样。而且就算是这样做,在不同的浏览器内核是渲染也将不一致,。
我们可以尝试在场景中设置blur(),尽管在各浏览器看上去好像还是个Bug(在Chrome和Firefox有时会闪烁,各个面消失;Edge完全不显示任何东西):
我比较好奇的是下面这个简单示例,在场景中也有设置了filter的blur()效果,但是在Blink内核的浏览器和Edge中效果很好,只是在Firefox下有问题。
总体而言,在3D渲染上下文中使用filter似乎问题很多,所以在使用的时候需要谨慎。
说了这么久的filter,如果你从未接触过的话,建议你先了解filter相关的东西,然后在回过头去阅读前面的内容。
mix-blend-mode
比如说我们一个.container元素,这个元素有一个多彩的background。在这个元素内有一个设置了background-image的.mover元素,并且.mover元素有一个改变位置的动画效果和设置mix-blend-modeverlay的样式。看到效果将是,.mover(这里称之为草莓,因为背景图片是草莓)移动位置,其位置在.container元素不同元素块上时,呈现给用户的效果将会不一样:
混合模式目前在Edge中不支持,所以在Edge中将看不到任何效果,但你可以,让其早日也能支持混合模式。但是有一点需要注意,不能直接使用body或者html来替代.container元素,因为在Blink内核的浏览器中还。这个bug在body或html替代container时,.mover运用混合模式时会出问题。但在Firefox和Safari中不存在这个现象。
好吧,上面看到的是2D平面的,但我们要聊的是3D的,那我们需要.mover是一个带有图片的3D立方体,在3D空间中旋转。
到目前为止,在没有设置混合模式之一,这一切都很好。接下来在立方体上设置mix-blend-modeverlay。问题来了,3D渲染打破了,立方体又被拍平了:
由于我们需要在立方体上使用3D Transform来制作动画,而且他们的子元素都需要使用3D Transform,所以需要在立方体(.cube)上设置transform-style的值为preserve-3d。但我们还需要在.cube上设置mix-blend-modeverlay,这样问题就来了,在.cube上设置mix-blend-modeverlay致使transform-style的值变为flat,那么所有立方体就被拍平了。
尝试把mix-blend-modeverlay设置在立方体的各个面上,但这问题依旧还是存在:
要解决这个问题,需要在.container和.mover容器之间再添加一个.scene容器,并且在这个元素上设置perspective和mix-blend-mode。

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

  • 最佳新人

    注册账号后积极发帖的会员
  • 活跃会员

    经常参与各类话题的讨论,发帖内容较有主见

0关注

24粉丝

29帖子

排行榜
关闭

站长推荐上一条 /1 下一条

官方微信

全国服务热线:

400-0708-360

公司地址:国家西部信息安全产业基地(成都市高新区云华路333号)

邮编:610000    Email:2908503813@qq.com

Copyright   ©2015-2016  EOIT论坛Powered by©Discuz!    ( 蜀ICP备11000634号-7 )