本文解决模态框(game over 提示)动画仅首次触发、后续点击重置后不再播放的问题,核心在于避免属性冲突(如同时存在 open 和 close),推荐使用 CSS 类切换 + animation-fill-mode: forwards 实现可复用的动画控制。
本文解决模态框(game over 提示)动画仅首次触发、后续点击重置后不再播放的问题,核心在于避免属性冲突(如同时存在 `open` 和 `close`),推荐使用 css 类切换 + `animation-fill-mode: forwards` 实现可复用的动画控制。
在您当前的代码中,动画失效的根本原因在于:modal.setAttribute("open", "") 和 modal.setAttribute("close", "") 并非互斥操作,而是向 DOM 元素持续添加属性。首次 gameOver() 后,<div class="modal"> 被赋予 open 属性;而 resetGame() 中调用 modal.setAttribute("close", "") 并不会移除 open,导致元素同时拥有 open 和 close 两个属性。此时 CSS 选择器 .modal[open] 和 .modal[close] 均可能匹配,浏览器渲染行为不确定,动画自然无法稳定触发。
✅ 正确做法是:统一通过 CSS 类(class)控制状态,而非自定义属性(attribute)。类名天然具备“互斥性”——你只需 element.classList.add('open') 并 element.classList.remove('close'),即可确保状态唯一、逻辑清晰、动画可重入。
1. 修改 HTML 结构(保持语义,无需新增属性)
<div class="modal" id="gameOverModal"> <p class="modal-content"></p></div>
2. 更新 CSS:用 .modal.open / .modal.close 替代 [open] / [close] 属性选择器
.modal { position: fixed; background: #fffdfc; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.35); top: 20%; left: 50%; transform: translateX(-50%); text-align: center; font-size: 2rem; padding: 0.5em; width: 30em; border-radius: 2px; opacity: 0; pointer-events: none; z-index: 1; /* 关键:隐藏时强制不占布局流 */ display: none;}.modal.open { display: block; /* 动画开始前必须可见,否则 animation 不触发 */ animation: modal-fade-in 500ms forwards;}.modal.close { animation: modal-fade-out 500ms forwards;}@keyframes modal-fade-in { from { opacity: 0; } to { opacity: 1; }}@keyframes modal-fade-out { from { opacity: 1; } to { opacity: 0; }}
? 注意:display: none 是关键。opacity: 0 不等于隐藏——它仍占据空间且可交互(除非加 pointer-events: none)。我们通过 display: none 彻底移出渲染流,并在 .open 类中设为 display: block 触发动画,既保证性能又避免闪烁。
3. 修正 JavaScript 状态管理逻辑
替换原 gameOver() 和 resetGame() 中关于 modal 的操作:
// ✅ 在 gameOver() 中:modal.classList.remove("close");modal.classList.add("open");modal.querySelector(".modal-content").textContent = finalResult;// ✅ 在 resetGame() 中:modal.classList.remove("open");modal.classList.add("close");// 可选:动画结束后彻底隐藏(增强健壮性)setTimeout(() => { if (modal.classList.contains("close")) { modal.style.display = "none"; }}, 500);
4. 补充防抖与动画完成监听(进阶优化)
为防止快速连点重置导致动画队列混乱,可在 resetGame() 开头加锁判断:
if (modal.classList.contains("open") || modal.classList.contains("close")) return;
或者监听 animationend 事件清理状态:
modal.addEventListener("animationend", () => { if (modal.classList.contains("close")) { modal.style.display = "none"; modal.classList.remove("close"); }});
通过以上重构,您的游戏结束动画将真正实现「每次 gameOver → 播放入场;每次 reset → 播放出场」的稳定循环,彻底告别“只动一次”的困扰。