You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何自制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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 16:00:52