你知道,猪小弟从那些红色圆环中出现,宣布乐一通卡通的结束。 我们会做到这一点,但首先我们需要了解一些 CSS 概念。
CSS 中的所有内容都是一个盒子,或者说是矩形。 矩形可以堆叠,并且可以显示在其他矩形的顶部或底部。 矩形可以包含其他矩形,并且您可以对其进行样式设置,使内部矩形在外部矩形之外可见(因此它们溢出)或被外部矩形裁剪(使用overflow: hidden
)。 到目前为止,一切都很好。
如果您希望矩形在其周围矩形之外可见,但仅在一侧可见,该怎么办? 那是不可能的,对吧?

也许,当您查看上面的图片时,思路开始运转:如果我复制内部矩形并裁剪其一半,然后精确地定位它呢?。 但归根结底,您无法选择让元素在顶部溢出但在底部裁剪。
或者可以吗?
3D 变换
使用 3D 变换,您可以在 3D 空间中旋转、变换和平移元素。 这里有一组我收集的实用示例,展示了一些可能性。
为了让 3D 变换发挥作用,您需要两个 CSS 属性
perspective
,使用像素值来确定 3D 效果的突出程度transform-style: preserve-3d
,告诉浏览器在 3D 空间中保持元素的位置。
即使 3D 变换具有良好的支持,但遗憾的是,您并没有在“野外”看到太多 3D 变换。 网站仍然是“2D”事物,一个可以滚动的平面页面。 但是当我开始使用 3D 变换并寻找示例时,我发现了一个就 3D 变换而言最有趣的示例

图像清楚地显示了三个平面,但此效果是使用单个<div>
实现的。 其他两个平面是::before
和::after
伪元素,它们分别使用translate()
向上和向下移动,以在 3D 空间中彼此堆叠。 这里值得注意的是,::after
元素通常会定位在元素的顶部,但现在位于该元素的后面。 创建者能够通过添加transform: translateZ(-1px);
来实现这一点。
即使这是我当时见过的众多 3D 变换之一,但它也是第一个让我意识到我实际上是在 3D 空间中定位元素的变换。 如果我能做到这一点,我也可以使元素相交

我无法想到这种方法有什么用,但后来我看到了猪小弟的卡通动画。 他从底部框架后面出现,但他的脸部重叠并在同一框架的顶部边缘堆叠——这与我们之前看到的完全相同的裁剪情况。 这时我的思路开始运转。 我可以仅使用 CSS 复制该效果吗? 为了获得额外的奖励,我可以用单个<div>
复制它吗?
我开始尝试,并很快得到了以下结果

这里我们有一个带有其::before
和::after
伪元素的单个<div>
。 div 本身是透明的,::before
有一个蓝色边框,::after
已沿 x 轴旋转。 因为 div 具有perspective
,所以所有内容都位于 3D 中,并且因此,::after
伪元素位于框架顶部边缘边框的上方,并且位于框架底部边缘边框的后面。
以下是代码
div {
transform: perspective(3000px);
transform-style: preserve-3d;
position: relative;
width: 200px;
height: 200px;
}
div::before {
content: "";
width: 100%;
height: 100%;
border:10px solid darkblue;
}
div::after {
content: "";
position: absolute;
background: orangered;
width: 80%;
height: 150%;
display: block;
left: 10%;
bottom: -25%;
transform: rotateX(-10deg);
}
使用perspective
,我们可以确定观察者距离“z=0”有多远,我们可以将其视为 CSS 3D 空间的“地平线”。 透视越大,3D 效果越不明显,反之亦然。 对于大多数 3D 场景,perspective
值介于 500 和 1,000 像素之间效果最佳,尽管您可以对其进行调整以获得所需的精确效果。 您可以将其与透视绘图进行比较:如果您绘制两个彼此靠近的地平线点,您将获得非常强的透视效果;但如果它们相距较远,则事物看起来会更平坦。
从矩形到卡通
矩形很有趣,但我真正想要构建的是这样的东西

我无法从该图像中找到或创建猪小弟的精美剪裁版本,但维基百科页面包含一个不错的替代方案,所以我们将使用它。
首先,我们需要将图像分成三个部分
<div>
:猪小弟背后的蓝色背景::after
:形成某种隧道的所有红色圆圈::before
:猪小弟本人,以其全部荣耀设置为了背景图片
我们将从<div>
开始。 它将作为背景,也是其他元素的基础。 它还将包含我之前提到的perspective
和transform-style
属性,以及一些尺寸和背景颜色
div {
transform: perspective(3000px);
transform-style:preserve-3d;
position: relative;
width: 200px;
height: 200px;
background: #4992AD;
}
好的,接下来,我们将转向红色圆圈。 元素本身必须是透明的,因为那是猪小弟出现的开口。 那么我们应该如何处理呢? 我们可以像本文前面示例中那样使用边框,但我们只有一个边框,它可以具有纯色。 我们需要一堆可以接受渐变的圆圈。 我们可以使用box-shadow
代替,在属性值中链接多个阴影。 这为我们提供了所有需要的圆圈,并且通过对具有较大扩展半径的模糊半径值使用0
,我们可以创建多个“边框”的外观。
box-shadow: <x-offset> <y-offset> <blur-radius> <spread-radius> <color>;
我们将使用一个与<div>
本身一样大的border-radius
,使::before
成为一个圆圈。 然后我们将添加阴影。 当我们添加几个具有较大扩展的红色圆圈并添加模糊的白色时,我们会得到一个非常类似于猪小弟隧道的效果。
box-shadow: 0 0 20px 0px #fff, 0 0 0 30px #CF331F,
0 0 20px 30px #fff, 0 0 0 60px #CF331F,
0 0 20px 60px #fff, 0 0 0 90px #CF331F,
0 0 20px 90px #fff, 0 0 0 120px #CF331F,
0 0 20px 120px #fff, 0 0 0 150px #CF331F;
在这里,我们添加了五个圆圈,每个圆圈宽30px
。 每个圆圈都有一个纯红色的背景。 并且,通过在顶部使用模糊半径为20px
的白色阴影,我们创建了渐变效果。

在背景和圆圈排序后,我们现在将添加猪小弟。 让我们首先将他添加到我们希望他最终到达的位置,现在位于圆圈的上方。
div::before {
position: absolute;
content: "";
width: 80%;
height: 150%;
display: block;
left: 10%;
bottom: -12%;
background: url("Porky_Pig.svg") no-repeat center/contain;
}
您可能已经注意到“center/contain
”中用于background
的斜杠。 这是在background
速记 CSS 属性中同时设置位置(center
)和大小(contain
)的语法。 斜杠语法也用于font
速记 CSS 属性中,它用于设置font-size
和line-height
,如下所示:<font-size>/<line-height>
。
斜杠语法将在 CSS 的未来版本中得到更多使用。 例如,更新的rgb()
和hsl()
颜色语法可以在后面加上一个斜杠,然后跟一个数字来指示不透明度,如下所示:rgb(0 0 0 / 0.5)
。 这样,就无需在rgb()
和rgba()
之间切换。 这已经在所有浏览器中都能正常工作,除了 Internet Explorer 11。

此处的大小和定位有点随意,因此请根据需要进行调整。 我们离想要的结果已经很接近了,但现在需要让猪小弟的底部部分位于红色圆圈后面,而他的上半身仍然可见。
技巧
我们需要在 3D 空间中转换圆圈和猪小弟。 如果我们想旋转猪小弟,我们需要满足一些要求
- 他不能穿过背景裁剪。
- 我们不应该旋转得太厉害,以至于图像变形。
- 他的下半身应该在红色圆圈下方,上半身应该在红色圆圈上方。
为了确保猪小弟不会穿过背景裁剪,我们首先沿 Z 方向移动圆圈,使它们看起来更靠近观察者。 因为应用了preserve-3d
,所以这意味着它们也会稍微放大,但如果我们只移动一点点,则放大效果不会很明显,并且我们会在背景和圆圈之间留下足够的空间
transform: translateZ(20px);
现在是猪小弟。 我们将围绕 X 轴旋转他,导致他的上半身向我们靠近,下半身远离我们。 我们可以用以下方法做到这一点
transform: rotateX(-10deg);
起初这看起来很糟糕。 猪小弟的部分身体隐藏在蓝色背景后面,而且他还在以奇怪的方式穿过圆圈裁剪。

我们可以通过使用translateZ()
将猪小弟“靠近”我们(就像我们对圆圈所做的那样)来解决此问题,但更好的解决方案是更改旋转点的位 置。 现在它发生在图像的中心,导致图像的下半部分旋转远离我们。
如果我们将旋转的起点移到图像的底部,甚至稍微低于底部,那么整个图像都会向我们旋转。 因为我们已经将圆圈移近我们,所以最终一切看起来都应该正确
transform: rotateX(-10deg);
transform-origin: center 120%;

要了解 3D 中所有内容的工作原理,请在以下 Pen 中点击“显示调试”
动画
如果我们保持现状——静态图像——那么我们就无需经历所有这些麻烦。但是,当我们为事物添加动画时,就可以展现分层效果并增强视觉效果。
这是我想要实现的动画:猪小弟一开始很小,位于圆圈后面底部,然后放大,从蓝色背景中浮现出来,覆盖在红色圆圈上。他会在那里停留一段时间,然后再次移出。
我们将使用transform
来实现动画,以获得最佳性能。并且由于我们正在这样做,因此也需要确保rotateX
保留在其中。
@keyframes zoom {
0% {
transform: rotateX(-10deg) scale(0.66);
}
40% {
transform: rotateX(-10deg) scale(1);
}
60% {
transform: rotateX(-10deg) scale(1);
}
100% {
transform: rotateX(-10deg) scale(0.66);
}
}
很快,我们将能够直接设置不同的变换,因为浏览器已开始将它们作为单独的 CSS 属性实现。这意味着重复rotateX(-10deg)
最终将变得不必要;但目前,我们存在一些重复。
我们使用scale()
函数进行放大和缩小,并且由于我们已经设置了transform-origin
,因此缩放会从图像的中心底部开始,这正是我们想要的效果!我们将动画的缩放比例放大到猪小弟实际尺寸的 60%,并在最大点处有一个小停顿,此时他完全从圆形框架中弹出来。
动画作用于::before
伪元素。为了使动画看起来更自然一些,我们使用了ease-in-out
时间函数,该函数会在动画开始和结束时减慢速度。
div::before {
animation-name: zoom;
animation-duration: 4s;
animation-iteration-count: infinite;
animation-fill-mode:forwards;
animation-timing-function: ease-in-out;
}
减少运动怎么办?
很高兴你问到!对于对动画敏感并希望减少或不使用运动的人,我们可以使用prefers-reduced-motion
媒体查询。我们不会完全移除动画,而是针对那些更喜欢减少运动的人,并使用更细微的淡入淡出效果,而不是完整的动画。
@media (prefers-reduced-motion: reduce) {
@keyframes zoom {
0% {
opacity:0;
}
100% {
opacity: 1;
}
}
div::before {
animation-iteration-count: 1;
}
}
通过在媒体查询中覆盖@keyframes
,浏览器会自动获取它。这样,我们仍然可以强调猪小弟从圆圈中浮现出来的效果。通过将animation-iteration-count
设置为1
,我们仍然让人们看到效果,但随后停止以防止持续运动。
收尾工作
我们还可以做两件事让这个动画更有趣
- 我们可以通过在猪小弟后面添加一个阴影来创建更深的图像,随着他出现并看起来更靠近视野,阴影会逐渐变大。
- 当猪小弟移动时,我们可以让他转动,以进一步美化弹出效果。
我们可以使用同一个动画中的rotateZ()
来实现第二部分。非常简单。
但是第一部分需要额外的技巧。因为我们使用图像来显示猪小弟,所以我们不能使用box-shadow
,因为这会在::before
伪元素的框周围而不是猪小弟的形状周围创建阴影。
这时filter: drop-shadow()
可以派上用场。它会查看元素的不透明部分,并为其添加阴影,而不是在框周围添加阴影。
@keyframes zoom {
0% {
transform: rotateX(-10deg) scale(0.66);
filter: drop-shadow(-5px 5px 5px rgba(0,0,0,0));
}
40% {
transform: rotateZ(-10deg) rotateX(-10deg) scale(1);
filter: drop-shadow(-10px 10px 10px rgba(0,0,0,0.5));
}
60% {
transform: rotateZ(-10deg) rotateX(-10deg) scale(1);
filter: drop-shadow(-10px 10px 10px rgba(0,0,0,0.5));
}
100% {
transform: rotateX(-10deg) scale(0.66);
filter: drop-shadow(-5px 5px 5px rgba(0,0,0,0));
}
}
这就是我如何重新创建乐一通动画中的猪小弟动画。我现在所能说的就是,“这就是全部啦!”
太棒了!非常喜欢解释中的细节。
太神奇了!真的激励我去尝试一些我自己动手操作。
感谢你的想法!对我的当前项目特别有用。