如何用transform代替负边距实现折纸效果?

How to replace negative margins with transform to achieve paper folding effect?

我正在尝试仅使用 CSS 实现折纸效果。我在视觉上(几乎)达到了预期的效果,但有一些重大缺陷;即:

我需要什么帮助:

我注意到每一对都已经连接,并且 *linked* 如果我移除边距并观察它们在不移动的情况下折叠,这要归功于变换原点。也许有一种方法可以实现 link 元素上方和下方的元素。您可以删除负边距和 运行 以了解我的意思。

$('#menu-main-toggle').on('click', function() {
    if ($(this).hasClass('active')) {
      $(this).removeClass('active');      
    } else {
      $(this).addClass('active');
    }
});
/* Aesthetic Styling Begin */
ul{margin:0;padding:0;list-style-type:none}#menu-main,#menu-main-toggle{width:75%;margin:auto}#menu-main-toggle,.menu-item{background:#e74c3c;border:none}#menu-main .menu-link,#menu-main-toggle{outline:0;display:block;height:50px;padding:12px;color:#fff;border-top:1px dashed #bf2718;box-sizing:border-box;text-decoration:none;text-align:center}
/* Aesthetic Styling End */

#nav-primary .dropdown-toggle.active + .toggleable > .menu-item {
    margin: 0;
    transform: perspective(320px) rotateX(0deg);
    box-shadow: inset 0 0 20px 0 transparent;
}

#menu-main .menu-item {
    transition: transform .75s ease-in-out, margin .75s ease-in-out, box-shadow .75s ease-in-out;
}
#menu-main .menu-item.odd {
    margin-bottom: -100px;
    transform: perspective(320px) rotateX(-90deg);
    transform-origin: top;
    box-shadow: inset 0 -50px 25px 0 rgba(0, 0, 0, .5);
}
#menu-main .menu-item.even {
    margin-top: -100px;
    transform: perspective(320px) rotateX(90deg);
    transform-origin: bottom;
    box-shadow: inset 0 50px 25px 0 rgba(0, 0, 0, .5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css" rel="stylesheet"/>
<nav id="nav-primary" class="text-center">
  <button id="menu-main-toggle" class="dropdown-toggle">Menu</button>
  <ul id="menu-main" class="toggleable">
    <li class="menu-item odd">
      <a class="menu-link" href="#">Lorem</a>
    </li>
    <li class="menu-item even">
      <a class="menu-link" href="#">Ipsum</a>
    </li>
    <li class="menu-item odd">
      <a class="menu-link" href="#">Situs Dolor</a>
    </li>
    <li class="menu-item even">
      <a class="menu-link" href="#">Consectetur</a>
    </li>
    <li class="menu-item odd">
      <a class="menu-link" href="#">Duis</a>
    </li>
    <li class="menu-item even">
      <a class="menu-link" href="#">Vivamus</a>
    </li>
  </ul>
</nav>

这是一个可行的解决方案。

我只在 Chrome 中测试过它,因为它使用的变量需要现代浏览器。但是您可以轻松调整它以在没有变量的情况下工作。

使用的变换是。我稍后会尝试解释它们..

将鼠标悬停在列表上以将其折叠

.test {
 --height: 150px;
 --time: 5s;
 --angle: 0deg;
 --angle2: -0deg;
 --bkg1: 0%;
 --bkg2: 100%;
}

.test:hover {
 --angle: 90deg;
 --angle2: -90deg;
 --bkg1: 100%;
 --bkg2: 0%;
}

ul {
 margin-top: 10px;
 width: 70%;
 position: relative;
 list-style: none;
}

li {
 position: absolute;
 width: 100%;
 transition: transform var(--time);
 bottom:  0px;
 transform: rotateX(var(--angle)) translateY(100%) rotateX(var(--angle2));
}

li:nth-child(1) {
 height: 0px;
}
li:nth-child(2), li:nth-child(3) {
 height: calc(2 * var(--height));
}
li:nth-child(4), li:nth-child(5) {
 height: calc(4 * var(--height));
}
li:nth-child(6), li:nth-child(7) {
 height: calc(6 * var(--height));
}

a {
 position: absolute;
 height: var(--height);
 width: 100%;
 display: block;
 font-size: 40px;
 background-size: 100% 300%;
 background-repeat: no-repeat;
 transition: transform var(--time), background-position var(--time);
}

li:nth-child(odd) a {
 top: 100%;
 transform-origin: top;
 transform: rotateX(var(--angle)) translateY(100%) perspective(400px) translateY(-100%) rotateX(var(--angle2)) rotateX(var(--angle2));
 background-image: linear-gradient(to top, rgba(0,0,0,0.6) 30%, transparent 66%);
 background-position: center var(--bkg1);
}
li:nth-child(even) a {
 bottom: 0px;
 transform-origin: bottom;
 transform: rotateX(var(--angle2)) translateY(-100%) perspective(400px) translateY(100%) rotateX(var(--angle)) rotateX(var(--angle));
 background-image: linear-gradient(rgba(0,0,0,0.6) 30%, transparent 66%);
 background-position: center var(--bkg2);
}

li:nth-child(1) a {
 background-color: rgba(200,0,0,0.3);
}
li:nth-child(2) a {
 background-color: rgba(0,200,0,0.3);
}
li:nth-child(3) a {
 background-color: rgba(0,0,200,0.3);
}
li:nth-child(4) a {
 background-color: rgba(200,200,0,0.3);
}
li:nth-child(5) a {
 background-color: rgba(200,0,200,0.3);
}
li:nth-child(6) a {
 background-color: rgba(0,200,200,0.3);
}
<ul class="test">
   <li>
      <a>-1-</a>
   </li>
   <li>
      <a>-2-</a>
   </li>
   <li>
      <a>-3-</a>
   </li>
   <li>
      <a>-4-</a>
   </li>
   <li>
      <a>-5-</a>
   </li>
   <li>
      <a>-6-</a>
   </li>
</ul>

让我们试着解释一下变换。

为了让事情更简单一些,我使用 2 个元素来实现转换:基础 li 将处理 Y 运动,a 将处理自己的旋转。

对于给定的元素,Y 平移量由位于其顶部的元素的旋转给出。这可以通过单个元素来复制,这些元素的总和具有相同的高度,以相同的速度旋转。

一开始,li 被放置在 Y = 0 处。然后它被旋转,然后平移与其高度相同的量,然后反向旋转,因为我们只希望这个元素到达 Y 位置而不是旋转。

对于 a 元素,这更难:-( 。我们希望它旋转,并以透视方式进行。但是我无法根据这种情况调整以下元素,所以我需要它fake透视图。这个透视图会缩小元素的宽度,但不会缩小高度。有两点在旋转的情况下保持不变:旋转轴和透视点。所以我需要先将元素移动到它的旋转边界,在这里应用透视,回到原来的位置,然后应用最后的旋转。

最后 2 次旋转正在解决 CSS 变量的问题。我需要

rotateX(calc( 2 * var(--angle)))

但这对于 Chrome 来说似乎太多了。