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

切换CSS类时实现遮罩始终从上到下滑动的动画问题

解决遮罩每次点击都从上到下滑动的问题

我明白你的困扰——用CSS过渡的话,移除类时遮罩会反向滑动,动画又没法在移除类时触发想要的效果。其实核心问题是:过渡是基于属性的起始/结束值反向运行,而我们需要每次切换都强制执行从上到下的单向动画,不管是显示还是隐藏状态。

下面给你两种可行的方案,按需选择:

方案一:用CSS动画控制单向滑动

这种方式通过定义两个独立的动画,分别控制遮罩的显示(滑入)和隐藏(滑出),每次切换时触发对应的动画,确保方向都是从上到下。

HTML 结构

<button class="toggle-mask">切换遮罩</button>
<div class="mask"></div>

CSS 样式

.mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  /* 默认隐藏在屏幕顶部 */
  transform: translateY(-100%);
  pointer-events: none; /* 不干扰底层交互 */
}

/* 显示动画:从上到下滑入屏幕 */
@keyframes slideDownIn {
  0% { transform: translateY(-100%); }
  100% { transform: translateY(0); }
}

/* 隐藏动画:从上到下滑出屏幕底部 */
@keyframes slideDownOut {
  0% { transform: translateY(0); }
  100% { transform: translateY(100%); }
}

/* 显示状态类 */
.mask.show {
  pointer-events: auto;
  animation: slideDownIn 0.5s forwards;
}

/* 隐藏状态类 */
.mask.hide {
  animation: slideDownOut 0.5s forwards;
}

JavaScript 逻辑

const toggleBtn = document.querySelector('.toggle-mask');
const mask = document.querySelector('.mask');
let isMaskVisible = false;

toggleBtn.addEventListener('click', () => {
  // 先移除所有动画类,避免冲突
  mask.classList.remove('show', 'hide');

  if (!isMaskVisible) {
    // 显示遮罩:触发滑入动画
    mask.classList.add('show');
    isMaskVisible = true;
  } else {
    // 隐藏遮罩:触发滑出动画
    mask.classList.add('hide');
    isMaskVisible = false;

    // 动画结束后重置遮罩位置,方便下次触发
    mask.addEventListener('animationend', () => {
      mask.classList.remove('hide');
      mask.style.transform = 'translateY(-100%)';
    }, { once: true });
  }
});

方案二:用过渡+强制重绘实现单向滑动

如果你更倾向于用CSS过渡而非动画,可以通过强制浏览器重绘,让遮罩在隐藏时先回到顶部,再触发向下滑动的过渡。

CSS 样式

.mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  transform: translateY(-100%);
  transition: transform 0.5s ease;
  pointer-events: none;
}

.mask.active {
  transform: translateY(0);
  pointer-events: auto;
}

JavaScript 逻辑

const toggleBtn = document.querySelector('.toggle-mask');
const mask = document.querySelector('.mask');
let isActive = false;

toggleBtn.addEventListener('click', () => {
  if (!isActive) {
    // 显示遮罩:正常触发过渡
    mask.classList.add('active');
    isActive = true;
  } else {
    // 第一步:强制遮罩回到顶部
    mask.style.transform = 'translateY(-100%)';
    // 强制浏览器重绘,确保识别这个状态
    void mask.offsetWidth;
    // 第二步:设置遮罩滑到屏幕底部,触发过渡
    mask.style.transform = 'translateY(100%)';

    // 过渡结束后重置状态
    mask.addEventListener('transitionend', () => {
      mask.classList.remove('active');
      mask.style.transform = 'translateY(-100%)';
      isActive = false;
    }, { once: true });
  }
});

两种方案的核心都是打破过渡的反向逻辑,强制每次点击都执行从上到下的滑动动画。你可以根据自己的代码结构选择合适的方式~

内容的提问来源于stack exchange,提问作者Kuba

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 07:39:09