这是一个仅限CSS的解决方案,具有以下财产:
开始时没有延迟,过渡也不会提前停止。在两个方向上(展开和折叠),如果在CSS中指定300毫秒的转换持续时间,则转换需要300毫秒。它正在转换实际高度(与transform:scaleY(0)不同),因此如果可折叠元素后面有内容,它会做正确的事情。虽然(和其他解决方案一样)有神奇的数字(比如“选择一个比你的盒子永远都要高的长度”),但如果你的假设最终是错误的,这并不是致命的。在这种情况下,转换看起来可能并不令人惊讶,但在转换之前和之后,这不是问题:在展开(高度:自动)状态下,整个内容始终具有正确的高度(不同于例如,如果选择的最大高度太低)。在塌陷状态下,高度应该为零。
Demo
这里有一个演示,其中有三个可折叠的元素,它们都有不同的高度,都使用相同的CSS。您可能希望在单击“运行代码段”后单击“完整页面”。请注意,JavaScript仅切换折叠的CSS类,不涉及度量。(通过使用复选框或:target,您可以在完全没有JavaScript的情况下进行这个精确的演示)。还要注意,负责转换的CSS部分很短,HTML只需要一个额外的包装元素。
$(函数(){$(“.tggler”).click(函数(){$(this).next().tggleClass(“塌陷”);$(this).tggleClass(“已切换”);//这只会旋转展开器箭头});});.可折叠包装纸{显示:柔性;溢出:隐藏;}.可折叠包装:在之后{内容:“”;高度:50px;过渡:高度0.3s线性,最大高度0s 0.3s线性;最大高度:0px;}.可折叠{过渡:边缘底部0.3s立方bezier(0,0,0、1);边距底部:0;最大高度:1000000px;}.scollapsible-wrappercollapsed>.scollapsble{边距底部:-2000px;过渡:边缘底部0.3s立方bezier(1,0,1,1),能见度0s 0.3s,最大高度0s 0.3s;可见性:隐藏;最大高度:0;}.可折叠包装器.折叠:之后{高度:0;过渡:高度0.3s线性;最大高度:50px;}/*可折叠实现结束;下面的东西只是这个演示的样式*/#集装箱{显示:柔性;对齐项目:弹性开始;最大宽度:1000px;边距:0自动;} .菜单{边框:1px实心#ccc;方框阴影:0 1px 3px rgba(0,0,0,0.5);边距:20px;}.菜单项{显示:块;背景:线性梯度(到底部,#fff 0%,#eee 100%);边距:0;填充:1em;线高:1.3;}.可折叠.菜单项{左侧边框:2px实心#888;右边框:2px实心#888;背景:线性梯度(至底部,#eee 0%,#ddd 100%);}菜单项日志{背景:线性梯度(至底部,#aaa 0%,#888 100%);颜色:白色;光标:指针;}.menu item.tggler:之前{内容:“”;显示:块;左侧边框:8px纯白色;边框顶部:8px实心透明;边框底部:8px实心透明;宽度:0;高度:0;浮动:右侧;转变:转变0.3s缓和;}.menu item.tggler.toggled:之前{变换:旋转(90度);}正文{字体系列:无衬线;字体大小:14px;}*,*:之后{框大小调整:边框框;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><div id=“container”><div class=“menu”><div class=“menu item”>涉及全息甲板的东西</div><div class=“menu item”>派遣一支客场球队</div><div class=“menu item toggler”>高级解决方案</div><div class=“可折叠包装器已折叠”><div class=“可折叠”><div class=“menu item”>单独的茶托</div><div class=“menu item”>派遣一支包括队长在内的客场球队(尽管Riker表示抗议)</div><div class=“menu item”>Ask Worf</div><div class=“menu item”>涉及19世纪卫斯理和全息甲板的东西</div><div class=“menu item”>向Q寻求帮助</div></div></div><div class=“menu item”>甜言蜜语外星人侵略者</div><div class=“menu item”>从辅助系统重新布线</div></div><div class=“menu”><div class=“menu item”>涉及全息甲板的东西</div><div class=“menu item”>派遣一支客场球队</div><div class=“menu item toggler”>高级解决方案</div><div class=“可折叠包装器已折叠”><div class=“可折叠”><div class=“menu item”>单独的茶托</div><div class=“menu item”>派出一支包括队长在内的客场球队(尽管Riker表示抗议)</div></div></div><div class=“menu item”>甜言蜜语外星人侵略者</div><div class=“menu item”>从辅助系统重新布线</div></div><div class=“menu”><div class=“menu item”>涉及全息甲板的东西</div><div class=“menu item”>派遣一支客场球队</div><div class=“menu item toggler”>高级解决方案</div><div class=“可折叠包装器已折叠”><div class=“可折叠”><div class=“menu item”>单独的茶托</div><div class=“menu item”>派出一支包括队长在内的客场球队(尽管Riker表示抗议)</div><div class=“menu item”>Ask Worf</div><div class=“menu item”>涉及19世纪卫斯理和全息甲板的东西</div><div class=“menu item”>向Q寻求帮助</div><div class=“menu item”>单独的茶托</div><div class=“menu item”>派遣一支包括队长在内的客场球队(尽管Riker表示抗议)</div><div class=“menu item”>Ask Worf</div><div class=“menu item”>涉及19世纪卫斯理和全息甲板的东西</div><div class=“menu item”>向Q寻求帮助</div></div></div><div class=“menu item”>甜言蜜语外星人侵略者</div><div class=“menu item”>从辅助系统重新布线</div></div></div>
它是如何工作的?
事实上,实现这一点涉及两个过渡。其中一个将边距底部从0px(展开状态)转换为-2000px(折叠状态)(类似于此答案)。这里的2000是第一个神奇的数字,它基于这样的假设,即你的盒子不会高于这个数字(2000像素似乎是一个合理的选择)。
单独使用保证金底部转换有两个问题:
如果你有一个高于2000像素的框,那么底部的边距:-2000px不会隐藏所有内容,即使在折叠的情况下也会有可见的内容。这是我们稍后要做的一个小修复。如果实际的框是1000像素高,并且你的过渡是300毫秒长,那么在大约150毫秒之后,可见的过渡已经结束(或者,在相反的方向上,延迟150毫秒开始)。
解决第二个问题是第二个转换出现的地方,这个转换在概念上以包装器的最小高度为目标(“概念上”,因为我们实际上并没有为此使用min-height属性;稍后将详细介绍)。
这是一个动画,展示了如何将底部边距过渡与最小高度过渡相结合,这两个过渡都具有相同的持续时间,从而为我们提供了从全高到零高的组合过渡。
左栏显示负底部边距如何向上推动底部,从而降低可见高度。中间的条形图显示了最小高度如何确保在塌陷情况下过渡不会提前结束,而在扩展情况下,过渡不会延迟开始。右栏显示了两者的组合如何在正确的时间内使长方体从全高过渡到零高。
对于我的演示,我已经确定了50像素作为上限最小高度值。这是第二个神奇的数字,它应该低于盒子的高度。50px似乎也很合理;您似乎不太可能经常想要让一个元素在一开始甚至不到50像素高的情况下进行折叠。
正如您在动画中看到的,生成的过渡是连续的,但它是不可微分的——当最小高度等于由底部边距调整的全高度时,速度会突然变化。这在动画中非常明显,因为它对两个过渡都使用了线性计时函数,而且整个过渡非常缓慢。在实际情况下(我在顶部的演示),转换只需要300毫秒,而底部边缘转换不是线性的。我在这两种转换中都使用了很多不同的计时功能,而我最终使用的这些功能似乎最适合最广泛的情况。
还有两个问题需要解决:
从上面看,高度超过2000像素的盒子在折叠状态下没有完全隐藏,相反的问题是,在非隐藏的情况下,即使在转换未运行时,高度小于50像素的框也太高,因为最小高度使它们保持在50像素。
我们通过给容器元素一个最大高度来解决第一个问题:在折叠的情况下为0,0到0.3s的过渡。这意味着这不是真正的过渡,但最大高度是有延迟的;它只在过渡结束后才适用。为了正确工作,我们还需要为相反的非折叠状态选择一个数值最大高度。但与2000px的情况不同,选取过大的数字会影响转换的质量,在这种情况下,这真的无关紧要。因此,我们可以选择一个高度如此之高的数字,我们知道任何高度都不会接近这个数字。我选了一百万像素。如果你觉得你可能需要支持超过100万像素的内容,那么1)对不起,2)只需添加几个零。
第二个问题是为什么我们实际上没有使用最小高度作为最小高度过渡。相反,容器中有一个::after伪元素,其高度从50px转换为零。这与最小高度具有相同的效果:它不会让容器收缩到伪元素当前具有的任何高度以下。但是因为我们使用的是高度,而不是最小高度,所以我们现在可以使用最大高度(再次使用延迟)在过渡结束后将伪元素的实际高度设置为零,确保至少在过渡之外,即使是小元素也具有正确的高度。因为min-height比max-height强,所以如果我们使用容器的min-heith而不是伪元素的高度,这将不起作用。就像上一段中的最大高度一样,该最大高度也需要过渡另一端的值。但在这种情况下,我们可以选择50像素。
在Chrome(Win、Mac、Android、iOS)、Firefox(Win,Mac、Android)、Edge、IE11(除了我的演示中的flexbox布局问题,我没有调试)和Safari(Mac、iOS)中进行测试。说到flexbox,应该可以在不使用任何flexbox的情况下实现这一功能;事实上,我认为你可以在IE7中让几乎所有的东西都正常工作,除了你不会有CSS转换,这是一个毫无意义的练习。