首页 >web前端 >css教程 >如何制作纯CSS 3D软件包切换

如何制作纯CSS 3D软件包切换

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原创
2025-03-15 09:35:09378浏览

How to Make a Pure CSS 3D Package Toggle

您知道那些完全扁平的瓦楞纸箱吗?您可以将它们折叠起来并用胶带粘好,制成一个实用的盒子。当需要回收时,再将它们剪开压平。最近,有人向我咨询了这个概念的3D动画版本,我认为用纯CSS实现它会是一个有趣的教程,所以我们开始了!

这种动画会是什么样子?我们如何创建包装时间线?尺寸可以灵活调整吗?让我们创建一个纯CSS包裹切换效果。

最终效果如下:点击即可打包和解包纸箱。

从哪里开始?

从哪里开始呢?最好提前做好规划。我们知道我们需要一个包裹模板,并且需要将其在三维空间中折叠。如果您不熟悉CSS 3D,我建议您阅读这篇文章入门。

如果您熟悉3D CSS,您可能会倾向于构建一个长方体然后开始。但是,这会带来一些问题。我们需要考虑包裹如何从2D转换为3D。

让我们从创建模板开始。我们需要提前规划好标记,并思考我们希望包装动画如何运作。让我们从一些HTML开始。

<div>
  <div>
    <div>
      <div>
        <div></div>
        <div></div>
        <div>
          <div></div>
          <div></div>
        </div>
        <div>
          <div></div>
          <div></div>
          <div>
            <div></div>
            <div></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Mixin是个好主意

这里有很多内容。有很多div。我经常喜欢使用Pug来生成标记,这样可以将内容分成可重用的块。例如,每一面都有两个盖板。我们可以为侧面创建一个Pug mixin,并使用属性应用修饰符类名,使所有这些标记更容易编写。

mixin flaps()
  .package__flap.package__flap--top
  .package__flap.package__flap--bottom

mixin side(class)
  .package__side(class=`package__side--${class || 'side'}`)
     flaps()
    block

.scene
  .package__wrapper
    .package
       side('main')
         side('extra')
         side('flipped')
           side('tabbed')

我们使用了两个mixin。一个为盒子的每一面创建盖板。另一个创建盒子的侧面。请注意,在side mixin中,我们使用了block。mixin用法的子元素就在此处呈现,这特别有用,因为我们稍后需要嵌套一些侧面以简化我们的工作。

生成的标记:

<div class="scene">
  <div class="package__wrapper">
    <div class="package">
      <div class="package__side package__side--main">
        <div class="package__flap package__flap--top"></div>
        <div class="package__flap package__flap--bottom"></div>
        <div class="package__side package__side--extra">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
        </div>
      </div>
      <div class="package__side package__side--flipped">
        <div class="package__flap package__flap--top"></div>
        <div class="package__flap package__flap--bottom"></div>
        <div class="package__side package__side--tabbed">
          <div class="package__flap package__flap--top"></div>
          <div class="package__flap package__flap--bottom"></div>
        </div>
      </div>
    </div>
  </div>
</div>

嵌套侧面

嵌套侧面使折叠包裹更容易。就像每一面都有两个盖板一样。侧面的子元素可以继承侧面的变换,然后应用它们自己的变换。如果我们从长方体开始,就很难利用这一点。

查看这个演示,它在嵌套和非嵌套元素之间切换,以查看实际效果。

每个盒子都有一个transform-origin设置为右下角,值为100% 100%。选中“Transform”切换会将每个盒子旋转90度。但是,请注意,如果我们嵌套元素,该变换的行为会发生变化。

我们切换了两个版本的标记,但没有更改其他任何内容。

嵌套:

<div>
  <div>
    <div>
      <div>
        <div></div>
      </div>
    </div>
  </div>
</div>

非嵌套:

<div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

变换所有内容

应用一些样式到我们的HTML之后,我们就有了我们的包裹模板。

样式指定了不同的颜色,并将侧面定位到包裹上。每个侧面的位置相对于“主”侧面。(您很快就会明白为什么嵌套如此有用。)

需要注意一些事情。就像使用长方体一样,我们使用--height--width--depth变量来确定尺寸。这将使我们以后更容易更改包裹尺寸。

.package {
  height: calc(var(--height, 20) * 1vmin);
  width: calc(var(--width, 20) * 1vmin);
}

为什么这样定义尺寸?我们使用了一个无单位的默认尺寸20,这个想法我从Lea Verou的2016年CSS ConfAsia演讲中获得的(从52:44开始)。将自定义属性用作“数据”而不是“值”,我们可以使用calc()随意使用它们。此外,JavaScript不必关心值单位,我们可以更改为像素、百分比等,而无需在其他地方进行更改。您可以将其重构为--root中的系数,但这很快也会变得过于复杂。

每一侧的盖板的尺寸也需要比它们所属的侧面略小。这样,当我们折叠它们时,就不会出现z-index冲突。

.package__flap {
  width: 99.5%;
  height: 49.5%;
  background: var(--flap-bg, var(--face-4));
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
}
.package__flap--top {
  transform-origin: 50% 100%;
  bottom: 100%;
}
.package__flap--bottom {
  top: 100%;
  transform-origin: 50% 0%;
}
.package__side--extra > .package__flap--bottom,
.package__side--tabbed > .package__flap--bottom {
  top: 99%;
}
.package__side--extra > .package__flap--top,
.package__side--tabbed > .package__flap--top {
  bottom: 99%;
}

我们还开始考虑各个部件的transform-origin。顶部盖板将从其底部边缘旋转,底部盖板将从其顶部边缘旋转。

我们可以使用伪元素来创建右侧的标签。我们使用clip-path来获得所需的形状。

.package__side--tabbed:after {
  content: '';
  position: absolute;
  left: 99.5%;
  height: 100%;
  width: 10%;
  background: var(--face-3);
  -webkit-clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%);
  clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%);
  transform-origin: 0% 50%;
}

让我们开始在3D平面上使用我们的模板。我们可以首先在X轴和Y轴上旋转.scene

.scene {
  transform: rotateX(-24deg) rotateY(-32deg) rotateX(90deg);
}

折叠

我们准备开始折叠我们的模板了!我们的模板将根据自定义属性--packaged进行折叠。如果值为1,则可以折叠模板。例如,让我们折叠一些侧面和伪元素标签。

.package__side--tabbed,
.package__side--tabbed:after {
  transform: rotateY(calc(var(--packaged, 0) * -90deg));
}
.package__side--extra {
  transform: rotateY(calc(var(--packaged, 0) * 90deg));
}

或者,我们可以为所有不是“主”侧面的侧面编写一个规则。

.package__side:not(.package__side--main),
.package__side:not(.package__side--main):after {
  transform: rotateY(calc((var(--packaged, 0) * var(--rotation, 90)) * 1deg));
}
.package__side--tabbed { --rotation: -90; }

这将涵盖所有侧面。

还记得我说过嵌套侧面允许我们继承父元素的变换吗?如果我们更新我们的演示,以便我们可以更改--packaged的值,我们可以看到该值如何影响变换。尝试将--packaged的值在1和0之间滑动,您就会明白我的意思。

现在我们有了切换模板折叠状态的方法,我们可以开始处理一些运动了。我们之前的演示在两种状态之间切换。我们可以为此使用transition。最快的方法?向.scene中的每个子元素的transform添加一个transition

.scene *,
.scene *::after {
  transition: transform calc(var(--speed, 0.2) * 1s);
}

多步骤过渡!

但是我们不会一次性折叠整个模板——在现实生活中,折叠过程是有顺序的,我们会先折叠一侧及其盖板,然后继续下一个,依此类推。作用域自定义属性非常适合此目的。

.scene *,
.scene *::after {
  transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step, 1) * var(--delay, 0.2)) * 1s);
}

在这里,我们说,对于每个转换,使用transition-delay--step乘以--delay--delay值不会改变,但每个元素可以定义它在序列中的“步骤”。然后我们可以明确地说明事情发生的顺序。

.package__side--extra {
  --step: 1;
}
.package__side--tabbed {
  --step: 2;
}
.package__side--flipped,
.package__side--tabbed::after {
  --step: 3;
}

查看以下演示,以更好地了解其工作原理。更改滑块值以更新事情发生的顺序。您可以更改哪辆车获胜吗?

同样的技术是我们接下来要做的关键。我们甚至可以引入一个--initial-delay,为所有内容添加一个轻微的暂停,以获得更逼真的效果。

.race__light--animated,
.race__light--animated:after,
.car {
  animation-delay: calc((var(--step, 0) * var(--delay-step, 0)) * 1s);
}

如果我们回顾我们的包裹,我们可以更进一步,并将“步骤”应用于所有将要转换的元素。这相当冗长,但它确实有效。或者,您可以在标记中内联这些值。

.package__side--extra > .package__flap--bottom {
  --step: 4;
}
.package__side--tabbed > .package__flap--bottom {
  --step: 5;
}
.package__side--main > .package__flap--bottom {
  --step: 6;
}
.package__side--flipped > .package__flap--bottom {
  --step: 7;
}
.package__side--extra > .package__flap--top {
  --step: 8;
}
.package__side--tabbed > .package__flap--top {
  --step: 9;
}
.package__side--main > .package__flap--top {
  --step: 10;
}
.package__side--flipped > .package__flap--top {
  --step: 11;
}

但是,它感觉不太现实。

也许我们应该翻转盒子

如果我在现实生活中折叠盒子,我可能会先将盒子翻转过来,然后再折叠顶部盖板。我们该如何做到这一点呢?嗯,那些目光敏锐的人可能已经注意到了.package__wrapper元素。我们将使用它来滑动包裹。然后我们将包裹在x轴上旋转。这将给人一种将包裹翻到侧面的印象。

.package {
  transform-origin: 50% 100%;
  transform: rotateX(calc(var(--packaged, 0) * -90deg));
}
.package__wrapper {
  transform: translate(0, calc(var(--packaged, 0) * -100%));
}

相应地调整--step声明,我们可以得到类似这样的效果。

展开盒子

如果您在折叠和未折叠状态之间切换,您会注意到展开看起来不对。展开顺序应该是折叠顺序的完全反向。我们可以根据--packaged和步骤数来翻转--step。我们最新的步骤是15。我们可以将我们的转换更新为此:

.scene *,
.scene *:after {
  --no-of-steps: 15;
  --step-delay: calc(var(--step, 1) - ((1 - var(--packaged, 0)) * (var(--step) - ((var(--no-of-steps)   1) - var(--step)))));
  transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step-delay) * var(--delay, 0.2)) * 1s);
}

这确实是一大堆calc来反转transition-delay。但是,它确实有效!我们必须提醒自己要保持--no-of-steps值是最新的!

我们还有另一个选择。当我们继续走“纯CSS”路线时,我们最终将使用复选框技巧来切换折叠状态。我们可以有两组定义的“步骤”,当我们的复选框被选中时,其中一组处于活动状态。这当然是一个更冗长的解决方案。但是,它确实让我们能够更精细地控制。

/* 折叠 */
:checked ~ .scene .package__side--extra {
  --step: 1;
}
/* 展开 */
.package__side--extra {
  --step: 15;
}

尺寸和居中

在我们放弃在演示中使用dat.gui之前,让我们来调整一下包裹的尺寸。我们要检查一下我们的包裹在折叠和翻转时是否保持居中。在这个演示中,包裹有一个更大的--height,而.scene有一个虚线边框。

我们不妨顺便调整一下我们的变换,以便更好地居中包裹:

/* 通过在z轴上平移来考虑包裹高度 */
.scene {
  transform: rotateX(calc(var(--rotate-x, -24) * 1deg)) rotateY(calc(var(--rotate-y, -32) * 1deg)) rotateX(90deg) translate3d(0, 0, calc(var(--height, 20) * -0.5vmin));
}
/* 通过在翻转之前滑动深度来考虑包裹深度 */
.package__wrapper {
  transform: translate(0, calc((var(--packaged, 0) * var(--depth, 20)) * -1vmin));
}

这使我们在场景中获得了可靠的居中效果。但这完全取决于个人喜好!

添加复选框技巧

现在让我们摆脱dat.gui,使之成为“纯”CSS。为此,我们需要在HTML中引入一堆控件。我们将使用一个复选框来折叠和展开我们的包裹。然后我们将使用一个单选按钮来选择包裹尺寸。

<label for="one">S</label>

<label for="two">M</label>

<label for="three">L</label>

<label for="four">XL</label>

<input type="checkbox" id="package">
<label for="package" class="open">打开包裹</label>
<label for="package" class="close">关闭包裹</label>

在最终演示中,我们将隐藏输入并使用标签元素。但是,现在,让我们先让它们都可见。诀窍是在某些控件被选中时使用同级组合器(~)。然后,我们可以设置.scene上的自定义属性值。

#package:checked ~ .scene {
  --packaged: 1;
}
#one:checked ~ .scene {
  --height: 10;
  --width: 20;
  --depth: 20;
}
#two:checked ~ .scene {
  --height: 20;
  --width: 20;
  --depth: 20;
}
#three:checked ~ .scene {
  --height: 20;
  --width: 30;
  --depth: 20;
}
#four:checked ~ .scene {
  --height: 30;
  --width: 20;
  --depth: 30;
}

这是一个包含该功能的演示!

最终润色

现在我们可以让事情看起来“漂亮”,并添加一些额外的细节。让我们首先隐藏所有输入。

input {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

我们可以将尺寸选项设置为圆形按钮:

.size-label {
  position: fixed;
  top: var(--top);
  right: 1rem;
  z-index: 3;
  font-family: sans-serif;
  font-weight: bold;
  color: #262626;
  height: 44px;
  width: 44px;
  display: grid;
  place-items: center;
  background: #fcfcfc;
  border-radius: 50%;
  cursor: pointer;
  border: 4px solid #8bb1b1;
  transform: translate(0, calc(var(--y, 0) * 1%)) scale(var(--scale, 1));
  transition: transform 0.1s;
}
.size-label:hover {
  --y: -5;
}
.size-label:active {
  --y: 2;
  --scale: 0.9;
}

我们希望能够点击任何地方来切换包裹的折叠和展开。因此,我们的.open.close标签将占据整个屏幕。想知道为什么我们有两个标签吗?这是一个小技巧。如果我们使用transition-delay并放大相应的标签,我们可以在包裹转换时隐藏两个标签。这就是我们对抗垃圾点击的方式(尽管它不会阻止用户在键盘上按空格键)。

.close,
.open {
  position: fixed;
  height: 100vh;
  width: 100vw;
  z-index: 2;
  transform: scale(var(--scale, 1)) translate3d(0, 0, 50vmin);
  transition: transform 0s var(--reveal-delay, calc(((var(--no-of-steps, 15)   1) * var(--delay, 0.2)) * 1s));
}

#package:checked ~ .close,
.open {
  --scale: 0;
  --reveal-delay: 0s;
}
#package:checked ~ .open {
  --scale: 1;
  --reveal-delay: calc(((var(--no-of-steps, 15)   1) * var(--delay, 0.2)) * 1s);
}

查看此演示,了解我们在.open.close中添加了background-color的位置。在转换期间,两个标签都不可见。

我们已经拥有了完整的功能!但是,我们的包裹目前有点平淡无奇。让我们添加额外的细节,使其看起来更像“盒子”,例如包裹胶带和包装标签。

像这样的细节仅受我们想象力的限制!我们可以使用我们的--packaged自定义属性来影响任何内容。例如,.package__tape正在转换scaleY变换:

.package__tape {
  transform: translate3d(-50%, var(--offset-y), -2px) scaleX(var(--packaged, 0));
}

需要注意的是,每当我们添加影响序列的新功能时,都需要更新我们的步骤。不仅是--step值,还有--no-of-steps值。

就这样!

这就是创建纯CSS 3D包裹切换的方法。你会把它放到你的网站上吗?不太可能!但是,看到你可以用CSS实现这些事情很有趣。自定义属性非常强大。

为什么不超级庆祝一下,送出CSS的礼物呢!

保持出色!ʕ •ᴥ•ʔ

以上是如何制作纯CSS 3D软件包切换的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn