为何自制vanilla JS版slideUp函数无法平滑收起?
问题原因
你代码里的核心问题是同时混用了CSS transition和requestAnimationFrame手动修改height,两种动画机制互相干扰:
- CSS transition会在height属性变化时自动生成平滑过渡效果
- 而requestAnimationFrame是逐帧强制修改height值,这会打断transition的过渡过程,导致动画混乱;最终在height变为0时直接设置
display:none,视觉上就呈现为突然消失。
另外,slideDown结束后元素的height被固定为具体px值而非auto,会导致后续内容变化时scrollHeight计算偏差,但这属于次要问题。
修复方案
我们可以选择单一动画方式实现,以下两种方案任选其一:
方案一:纯CSS Transition实现(推荐,简洁高效)
去掉requestAnimationFrame的逐帧修改逻辑,利用CSS transition自动处理动画,仅通过JS触发高度变化:
修改后的JS代码
function toggleSlide(element) { if (element.style.display === '' || element.style.display === 'none') { slideDown(element); } else { slideUp(element); } } function slideDown(element) { element.style.display = 'block'; // 触发重绘,确保scrollHeight能正确获取 const temp = element.offsetHeight; element.style.height = element.scrollHeight + 'px'; // 动画结束后重置height为auto,避免内容变化时高度受限 element.addEventListener('transitionend', function onTransitionEnd() { element.style.height = 'auto'; element.removeEventListener('transitionend', onTransitionEnd); }); } function slideUp(element) { // 先把height设为当前scrollHeight,确保transition能触发 element.style.height = element.scrollHeight + 'px'; // 触发重绘 const temp = element.offsetHeight; element.style.height = '0'; // 动画结束后隐藏元素 element.addEventListener('transitionend', function onTransitionEnd() { element.style.display = 'none'; element.removeEventListener('transitionend', onTransitionEnd); }); }
CSS代码(保留transition)
.my-element { background-color: #ddd; transition: height 0.5s ease; overflow: hidden; display: none; }
方案二:纯requestAnimationFrame实现(移除CSS Transition)
如果坚持用JS手动控制动画,需要去掉CSS中的transition属性,避免冲突:
修改后的CSS代码
.my-element { background-color: #ddd; overflow: hidden; display: none; }
修改后的JS代码(优化动画速度一致性)
function toggleSlide(element) { if (element.style.display === '' || element.style.display === 'none') { slideDown(element); } else { slideUp(element); } } function slideDown(element) { if (element.style.display !== 'block') { element.style.display = 'block'; element.style.height = '0'; } const contentHeight = element.scrollHeight; let currentHeight = 0; // 按0.5秒完成动画计算步长,确保不同高度元素动画时长一致 const step = contentHeight / (0.5 * 60); function animate() { currentHeight += step; if (currentHeight >= contentHeight) { currentHeight = contentHeight; element.style.height = 'auto'; // 动画结束后重置为auto } else { element.style.height = `${currentHeight}px`; requestAnimationFrame(animate); } } animate(); } function slideUp(element) { const contentHeight = element.scrollHeight; let currentHeight = contentHeight; const step = contentHeight / (0.5 * 60); // 和slideDown保持一致时长 function animate() { currentHeight -= step; if (currentHeight <= 0) { currentHeight = 0; element.style.height = '0'; element.style.display = 'none'; } else { element.style.height = `${currentHeight}px`; requestAnimationFrame(animate); } } animate(); }
补充说明
之前slideDown看起来正常,是因为元素从height:0开始,requestAnimationFrame的增量连续,CSS transition的冲突视觉上不明显;而slideUp时从大高度往0递减,冲突导致动画混乱,最后直接隐藏的问题就被放大了。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

