自定义实现Bootstrap v5 Collapse时display:none致CSS过渡失效
问题原因
display: none状态的元素脱离正常渲染流,浏览器不会对同一事件循环内从display:none切换到可见、同时修改过渡属性的操作触发动画——所有样式变更会被合并到同一个渲染帧,浏览器直接渲染最终样式,跳过中间过渡过程。你当前的代码把移除collapse类(去掉display:none)、添加collapsing类、设置高度三个操作放在同一个同步执行流里,刚好踩中这个浏览器渲染机制的问题。
Bootstrap 官方collapse组件的实现逻辑
Bootstrap没有直接切换类名触发动画,而是通过分帧操作+强制重排+动态计算高度的方式绕开display对过渡的影响,完整流程分展开、收起两个方向:
展开流程
- 第一步:移除元素上的
collapse类(去掉display: none属性),添加collapsing类,此时元素高度为0、溢出隐藏,处于过渡初始状态 - 第二步:读取元素的
offsetHeight属性强制浏览器触发重排,让浏览器确认当前height:0的初始状态,避免样式合并 - 第三步:将元素的内联
height设置为元素真实内容高度(即element.scrollHeight,不是硬编码固定值),浏览器检测到高度差后启动过渡动画 - 第四步:监听
transitionend事件,等动画执行完成后,移除collapsing类,添加collapse和show类,同时清空内联height样式,让元素高度自适应内容
收起流程
- 第一步:先把元素当前的实际高度通过内联样式固定,读取
offsetHeight触发重排 - 第二步:移除
show类,添加collapsing类,将内联height设置为0,触发高度从当前值到0的过渡动画 - 第三步:等动画执行完成后,移除
collapsing类,加回collapse类,清空内联height样式,元素回到display: none的隐藏状态
修正后的可运行代码
.collapse:not(.show) { display: none; } .collapse { display: block; height: auto; } .collapsing { height: 0; overflow: hidden; transition: height 0.3s ease; /* 官方默认过渡时长为0.3s,可自行调整 */ } #content { border: 1px solid black; }
jQuery(function($) { const $content = $("#content"); let isAnimating = false; // 动画锁,防止动画过程中重复点击导致状态错乱 $(".toggle").on("click", function() { if (isAnimating) return; isAnimating = true; if ($content.hasClass("show")) { // 执行收起逻辑 const currentHeight = $content.outerHeight(); $content.css('height', currentHeight + 'px'); // 强制浏览器重排,确认初始高度状态 $content[0].offsetHeight; $content.removeClass("collapse show") .addClass("collapsing") .css('height', '0'); // 动画结束后切换最终状态 $content.one('transitionend', function() { $content.removeClass("collapsing") .addClass("collapse") .css('height', ''); isAnimating = false; }) } else { // 执行展开逻辑 $content.removeClass("collapse") .addClass("collapsing"); // 强制浏览器重排,确认初始高度状态 $content[0].offsetHeight; // 计算内容真实高度,不硬编码固定值 const targetHeight = $content[0].scrollHeight; $content.css('height', targetHeight + 'px'); // 动画结束后切换最终状态 $content.one('transitionend', function() { $content.removeClass("collapsing") .addClass("collapse show") .css('height', ''); isAnimating = false; }) } }) })
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <button class="toggle">Toggle</button> <div id="content" class="collapse"> <p>Content Goes Here</p> <p>多行内容测试高度自适应,不管内容多少都能平滑过渡</p> </div>
关键注意点
- 强制重排是核心:必须在设置初始状态后、设置目标高度前读取布局属性(
offsetHeight/clientHeight/getBoundingClientRect()均可),强迫浏览器完成一帧渲染,否则样式还是会被合并 - 不要硬编码高度:用
scrollHeight取内容真实高度,否则内容变化时动画会错位 - 动画结束后一定要清空内联height样式,切换到
height: auto状态,不然内部内容动态变化时会出现高度裁剪问题
内容的提问来源于stack exchange,提问作者Alaksandar Jesus Gene
相关产品推荐
相关产品推荐

