如何用transform代替负边距实现折纸效果?
How to replace negative margins with transform to achieve paper folding effect?
我正在尝试仅使用 CSS 实现折纸效果。我在视觉上(几乎)达到了预期的效果,但有一些重大缺陷;即:
- 使用框阴影和负边距会大大降低性能。
- 负边距与变换过渡不同步,最终会在过渡开始和结束之间的某处重叠列表项。
我需要什么帮助:
- 重要:用转换样式替换负边距,有助于更真实地实现所需的折叠效果,提高性能并删除由负边距创建的重叠元素。问题是所有元素都展开并折回到 Menu 按钮后面,因为变换相对于它们自己的原始空间移动元素,不会通过它们的移动影响其他元素,留下它们的空间现在。
- 可选:用另一种方式替换 box-shadow 来模拟光线照射到边缘然后在折叠内褪色,光线穿过屏幕,用户就是,为了提高性能,或许可以通过使用opacity和pseudo 类.
我注意到每一对都已经连接,并且 *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 来说似乎太多了。
我正在尝试仅使用 CSS 实现折纸效果。我在视觉上(几乎)达到了预期的效果,但有一些重大缺陷;即:
- 使用框阴影和负边距会大大降低性能。
- 负边距与变换过渡不同步,最终会在过渡开始和结束之间的某处重叠列表项。
我需要什么帮助:
- 重要:用转换样式替换负边距,有助于更真实地实现所需的折叠效果,提高性能并删除由负边距创建的重叠元素。问题是所有元素都展开并折回到 Menu 按钮后面,因为变换相对于它们自己的原始空间移动元素,不会通过它们的移动影响其他元素,留下它们的空间现在。
- 可选:用另一种方式替换 box-shadow 来模拟光线照射到边缘然后在折叠内褪色,光线穿过屏幕,用户就是,为了提高性能,或许可以通过使用opacity和pseudo 类.
我注意到每一对都已经连接,并且 *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 来说似乎太多了。