请选择 进入手机版 | 继续访问电脑版

技术控

    今日:519| 主题:53890
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] CSS 3D应该注意的事项

[复制链接]
い明 媚 发表于 2016-10-3 15:00:58
209 1

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
我一直喜欢3D。我也开始使用CSS 3D Transform,而且浏览器对它的支持度越来越好。但给我的感觉,使用Transform就是用来创建2D图形,并且使用旋转和位移可以创建一些3D图形。但在实际使用的时候,还是越到了不少的麻烦,而且这些麻烦出乎我的意料。我想或许大家也同样遇到过这样的问题,为了大家在使用CSS 3D Transform能避免这些麻烦,我把我碰到的与在大家分享一下。
  3D渲染上下文

  我清楚的记得一个晚上遇到一个麻烦,而且这个麻烦引起我的好奇心,于是我想亲自写一个测试用例来试试看,看看浏览器如何处理平面的交叉。该测试用例只包含了两个平面元素:
  1. <div class='plane'></div>
  2. <div class='plane'></div>
复制代码
  它们尺寸大小相同,并且使用绝对定位,让元素在屏幕中居中(水平垂直居中),并且给他们设置了个背景色,让它们在屏幕中可见:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }
复制代码
  把 body 元素当作一个场景,并且设置了 perspective (视角),让视角覆盖整个视窗。视角的值越大,元素似乎看起来离自已越远,看起来越小,反之越大。
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }
复制代码
  测试示例实际上就是测试两个平面相交,所以使用Transform中的 rotateY() 做了一个 Y 轴旋转,并且设置了一个不同的背景颜色:
  1. .plane:last-child {
  2.   transform: rotateY(60deg);
  3.   background: #d14730;
  4. }
复制代码
结果是令人失望的。浏览器似乎并没有正确的处理好两个平面交叉:
   事实上是我错了,造成这个现象是我的代码导致的。我应该做的是让两个平面在 3D上下文渲染 。3D上下文渲染和 层叠上下文渲染 还是不同的。就像如果它们不在同一个层叠上下文中,我们不能使用 z-index 来改变元素在 z 轴的顺序。同样的,如果不在同一个3D渲染范围内,3D Transform同样不能改变元素的顺序,让元素交叉。
  为了确保这两个平面是在相同的3D渲染环境,最简单的方法就是把它们放在同一个容器中:
  1. <div class='assembly'>
  2.     <div class='plane'></div>
  3.     <div class='plane'></div>   
  4. </div>
复制代码
  同样让容器元素通过绝对定位,让它放置在容器中间,同时给它设置一个 transform-style:preserve-3d :
  1. div {
  2.     position: absolute;
  3. }
  4. .assembly {
  5.     top: 50%;
  6.     left: 50%;
  7.     transform-style: preserve-3d;
  8. }
复制代码
这样就解决了这个问题:
  如果你使用Firefox查看上面的示例,你看到的效果是两个平面并没有交叉,这或许是Firefox独有的特性吧,但你使用Webkit内核浏览器或者Edge浏览器看到的效果是我们想要的效果。
   

CSS 3D应该注意的事项

CSS 3D应该注意的事项

  上图演示是前面示例的效果,两个平面没有交叉。
   现在你可能会感到非常奇怪,为什么不给这两个平面添加一个容器,不在场景中设置 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 元素内:
  1. <div class='helix'>
  2.     <div class='col'>
  3.         <!-- all the hexagons inside a column -->
  4.     </div>
  5.     <!-- the other columns -->
  6. </div>
复制代码
  除了保证整个组件绝对定位在视窗中间的样式之外, .helix 元素没有任何其他样式(除非是继承下来的样式),并且所有的列都在同一个3D渲染范围内:
  1. div {
  2.     position: absolute;
  3.     transform-style: preserve-3d;
  4. }
  5. .helix {
  6.     top: 50%;
  7.     left: 50%;
  8. }
复制代码
  在场景中设置了 overflow:hidden (这个示例 body 元素就是场景),这样做是因为六边形不依赖于视窗的大小,我不知道它们是否会延伸到容器外而导致滚动条的出现,这种现象是我不想看到的。
   我承认我不止一次碰到这样的现象,从中吸引了相关教训。我保守起见,为了不让溢出出现在这里,直接使用 overflow:hidden ,这样能让溢出看起来不明显。
   transform-style:preserve-3d 告诉浏览器 设置了3D Transform的子元素不应该在他们的父元素内拍平(元素上设置了 transform-style:preserve-3d )。因此,就算是直觉,场景中设置了 overflow:hidden ,防止其子元素打破他们的父容器,也不会让3D元素在场景内拍平。
  但有时一个3D Transform子元素仍然可以在其父容器是平面的。比如说下面这种情况,我们一张具有两面的卡片:
  1. <div class='card'>
  2.     <div class='face'>front</div>
  3.     <div class='face'>back</div>
  4. </div>
复制代码
  通过绝对定位让所有元素在场景中(这个示例场景指的是 body 元素)水平垂直居中,并且给这两个卡片具有相同的大小尺寸。为了让这们在相同的空间,给卡片设置 transform-style:preserve-3d 。为了让背面可见,设置 backface-visibility:hidden ,并且在第二张卡片上设置 rotate ,让其沿着垂直轴( Y 轴)旋转半圈( .5turn ):
  1. $dim: 40vmin;
  2. div {
  3.     position: absolute;
  4.     width: $dim;
  5.     height: $dim;
  6. }
  7. .card {
  8.     top: 50%;
  9.     left: 50%;
  10.     margin: -.5*$dim;
  11.     transform-style: preserve-3d;
  12. }
  13. .face {
  14.     backface-visibility: hidden;
  15.     background: #ee8c25;
  16.     &:last-child {
  17.         transform: rotateY(.5turn);
  18.         background: #d14730;
  19.     }
  20. }
复制代码
示例的效果如下所示:
   两张卡片在他的父容器内仍然是平的,只不过第二张卡片绕着它的垂直轴( Y 轴)旋转了半圈。它的朝向是相反的方式,但仍然是在同一平面上。到目前为止,这一切看上去都是正常的。
   好,现在我想这两个卡片不是矩形的。给它们一个 border-radius: 50% 。但看起来没有任何变化:
   接下来在 .card 上设置 overflow:hidden ,效果就正常了:
   哎呀,这打破了我们的3D卡片。既然我们无法做到这一点,我们就在 .face 上设置:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }0
复制代码
在这种情况之下,解决这个问题的方法比打破3D卡这个问题更简单。但是,如果我们想要的是另一种形状,比如说一个正八边形,正八边形还是很容易实现的,比如采用两个元素(或者元素和伪元素的配合):
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }1
复制代码
  给这两个元素设置相同的尺寸,并且 .inner 元素设置 rotate 的值为 45deg ,给他们设置一个背景色,然后在 .octagon 元素上设置 overflow:hidden ,就可以看到一个正八边形:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }2
复制代码
你看到的八边形效果如下所示:
   如果你对如何制作正多边形感兴趣,建议你阅读《Sass绘制多边形》和《 單一 div 的正多邊形變換 ( 純 CSS ) 》。
  如果希望在正多边形中添加文本呢?
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }3
复制代码
你将看到的效果是这样,不尽人意:
   造成这个现象是因为裁角的时候把文本也裁剪掉了。为了让文本能正常显示,给它设置一个 text-align:center ,并且设置一个 line-height 的值等于 .octagon 的高度(或者 .inner ),让文本垂直居中:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }4
复制代码
  现在看起来好多了,但文本仍然是旋转的,因为 .inner 元素设置了一个 rotate(45deg) :
   为了解决这个问题,只需要在 .octagon 元素上增加一个 rotate ,其旋转的角度值和 .inner 的旋转值一样,只是旋转方向刚好与 .inner 元素相反,所以是一个负值:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }5
复制代码
这下,文本在正八边形中的文本显示正常了:
   现在让我们看看,如果我们想一正八边形的卡片,又将如何应用它。我们不能直接在 .card 上直接运用 overflow:hidden (让它在 .octagon 元素上作用,同时两个面 .inner 又会是什么样)。因为在卡片上设置了 overflow:hidden 样式,根据前面介绍的内容,这样就会打破3D空间,让两个页不在同一个3D渲染环境。
   替代方案是,需要把这些规则用在 .octagon 元素,并且使用它们的伪元素来做卡片的两个面:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }6
复制代码
最后看到的效果就是我们想要的效果:
  clip-path

   能引起类似的问题还有另一个属性 clip-path 。回到上面卡片的示例,我们不能在 .card 元素上直接使用 clip-path 来绘制三角形,因为我们需要一个3D Ttransform的子元素,也就是第二个面。我们应该用在卡片的面上:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }7
复制代码
  注意: clip-path 属性在Webkit内核浏览器下还是需要添加 -webkit- 前缀。对于Firefox(47+)浏览器需要通过 about:config 将 layout.css.clip-path-shapes.enabled 设置为 true ,另外Edge是不支持这个属性的(但你可以在这里 进行投票 ,让Edge早日能支持这个属性)。
   如果您从未接触过 clip-path 这个属性,建议你先阅读《  CSS的 clip-path  》一文进行了解。
  上面的代码看到的效果应该是这样的:
   虽然不存在3D的问题,但效果看起来真的很别扭。如果从正面观察卡片,三角形的顶角是朝右的,按理说,后面应该是朝左的。但效果并不是我们所期望的,反面也朝右了。要解决这个问题,那么需要为不同的面设置不同的路径。 clip-path 绘制正面的三角形超右,绘制反正的三角形超左。
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }8
复制代码
下面看到的效果才是我们想要的:
   注意:还需要修改 text-align 的值:正面的默认值为 left ,反正的就需要设置为 right 。
   另外,我们还可以在反面是使用 scaleX(-1) 来实现。如查你想进一步的了解 scale 的工作机制,可以看看下面的示例:
  将上面介绍的原理运用到我们前面介绍的DEMO中:
  1. $dim: 40vmin;
  2. .plane {
  3.   position: absolute;
  4.   top: 50%;
  5.   left: 50%;
  6.   margin: -.5*$dim;
  7.   width: $dim;
  8.   height: $dim;
  9.   background: #ee8c25;
  10. }9
复制代码
效果如下:
   这样看起来三角的方向是对了,但文字又出问题了。这意味着我们实际上要把文本放在背景元素的伪元素上,并且在 .face 元素上做一个反转。 scale 的反转就是设置另一个 scale 是 1/f 。在我们这个示例中, f 就是 -1 。也就是说在伪元素上设置 scale 的值为 1/-1=-1 ,就可以让文本看起来正常:
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }0
复制代码
最后的效果如下:
   如果 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 个面。
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }1
复制代码
现在我们说,想要的立方体是半透明的。那么我们这样做,能不能做到呢?
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }2
复制代码
  这样一来,就算在 .cube 上设置了 transform-style:preserve-3d ,也会变成 flat 的效果,就像 .cube 在它的父容器里被拍平了。现在只是Chrome、Opera和Firefox,但是在将来,所有浏览器都会是这样:
   

CSS 3D应该注意的事项

CSS 3D应该注意的事项

   在Brave、Edge和Safari中,设置 opacity 值小于 1 时,立方体没有拍平。
   

CSS 3D应该注意的事项

CSS 3D应该注意的事项

   在Chrome、Firefox、Opera浏览器中,结果是 .cube 被拍平了。
   我们不能把 opacity:0.5 设置在已经设置了 transform-style:preserve-3d 的 .assembly 上。其效果和前面所说的将一样:
   我们把 opacity:0.5 设置到 .cube 的每个面上,会不会引起同样的问题:
   也可以把 opacity 设置在场景元素上(下面的示例是设置了 body 元素上),但需要注意,它也会影响场景的 background 或者伪元素。它也不会使个别立方体或面单独具有半透明度,只能整体一样,而且也没办法让不同的立方体有不同的透明值。
   对比一下,在各个面上设置 opacity 和在场景中设置 opacity 效果差异:
   

CSS 3D应该注意的事项

CSS 3D应该注意的事项

   上图的效果是在每个面设置 opacity:0.5 的效果。
   

CSS 3D应该注意的事项

CSS 3D应该注意的事项

   上图的效果是在场景中设置 opacity:0.5 的效果。
  filter

   这个也让我感到惊奇,虽然不像 opacity ,但它在所有浏览器都是一样的。接着再拿3D立方体举例。通过 hue-rotate() 函数,让立方体在旋转的时候每个面都有不同的 hue 值。在 .cube 或者 .assembly 上设置 filter 的值不是 none 时,3D立方体就将会被拍平。
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }3
复制代码
  filter 在Webkit内核浏览器中仍需要添加 -webkit- 前缀。
  给每个面随机的色相有正常工作,但每个3D立方体还是被拍平了:
   这个问题的解决方案是把 filter 设置在立方体的每个面上:
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }4
复制代码
这样一来,3D立方体的每个面的色相是随机的,而且也在3D渲染上下文中,没有被拍平:
   我们也不能把 filter 设置在 .assembly 上面。比方说,希望所有的立方体都具有模糊效果,你就为了方便在 .assembly 上设置 filter :
  1. body {
  2.   margin: 0;
  3.   height: 100vh;
  4.   perspective: 40em;
  5. }5
复制代码
  

CSS 3D应该注意的事项

CSS 3D应该注意的事项

  其结果就是整个都被拍平了,平面变得模糊。Edge是个例外,直接一切都消失了。
   我们可以做的就是尽量在立方体的每个面上使用 blur() ,虽然结果不会完全一样。而且就算是这样做,在不同的浏览器内核是渲染也将不一致, 看上去就像是有Bug一样 。
   我们可以尝试在场景中设置 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-mode:overlay 的样式。看到效果将是, .mover (这里称之为草莓,因为背景图片是草莓)移动位置,其位置在 .container 元素不同元素块上时,呈现给用户的效果将会不一样:
   混合模式目前在Edge中不支持,所以在Edge中将看不到任何效果,但你可以 在这里投票 ,让其早日也能支持混合模式。但是有一点需要注意,不能直接使用 body 或者 html 来替代 .container 元素,因为在Blink内核的浏览器中还 存在bug 。这个bug在 body 或 html 替代 container 时, .mover 运用混合模式时会出问题。但在Firefox和Safari中不存在这个现象。
   好吧,上面看到的是2D平面的,但我们要聊的是3D的,那我们需要 .mover 是一个带有图片的3D立方体,在3D空间中旋转。
   到目前为止,在没有设置混合模式之一,这一切都很好。接下来在立方体上设置 mix-blend-mode:overlay 。问题来了,3D渲染打破了,立方体又被拍平了:
   由于我们需要在立方体上使用3D Transform来制作动画,而且他们的子元素都需要使用3D Transform,所以需要在立方体( .cube )上设置 transform-style 的值为 preserve-3d 。但我们还需要在 .cube 上设置 mix-blend-mode:overlay ,这样问题就来了,在 .cube 上设置 mix-blend-mode:overlay 致使 transform-style 的值变为 flat ,那么所有立方体就被拍平了。
   尝试把 mix-blend-mode:overlay 设置在立方体的各个面上,但这问题依旧还是存在:
   要解决这个问题,需要在 .container 和 .mover 容器之间再添加一个 .scene 容器,并且在这个元素上设置 perspective 和 mix-blend-mode 。
  这似乎是解决了一切问题!
   本文根据 @ANA TUDOR 的《 Things to Watch Out for When Working with CSS 3D 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: https://css-tricks.com/things-watch-working-css-3d/ 。
     

CSS 3D应该注意的事项

CSS 3D应该注意的事项
       大漠

     常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。



上一篇:MariaDB 10.2 Beta
下一篇:Optimizing parallel HTTP request batches
izxyc 发表于 2016-10-7 02:14:51
い明 媚高端大气上档次,鉴定完毕
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )

© 2001-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表