带Transition的矩形交换后出现多余位移,如何消除该异常?
修复矩形交换动画后的多余移动问题
问题根源
你遇到的多余移动,是因为transform仅做视觉偏移,DOM实际位置在动画过程中并未改变。用setTimeout固定时间后交换DOM时,元素布局位置突然变化,此时移除transform类,浏览器会自动计算从动画偏移位置到新布局位置的过渡,导致出现“额外移动”。另外setTimeout的时间和transition时长不一定完全匹配,也会加剧这个问题。
修复方案
用transitionend事件替代setTimeout监听动画完成,并且在DOM交换后强制浏览器重绘,再移除动画类,避免触发不必要的过渡。
修改后的代码
JavaScript
const rectangles = document.getElementsByClassName("rectangle"); for (let i = 0; i < rectangles.length; i++) { const rectangle = rectangles[i]; const upButton = rectangle.querySelector(".up"); const downButton = rectangle.querySelector(".down"); upButton.addEventListener("click", () => moveFile(rectangle, "up")); downButton.addEventListener("click", () => moveFile(rectangle, "down")); } function moveFile(currentRectangle, direction) { const siblingRectangle = direction === "up" ? currentRectangle.previousElementSibling : currentRectangle.nextElementSibling; if (!siblingRectangle) return; // 添加动画类 const currentClass = direction === "up" ? "move-up" : "move-down"; const siblingClass = direction === "up" ? "move-down" : "move-up"; currentRectangle.classList.add(currentClass); siblingRectangle.classList.add(siblingClass); // 监听动画结束事件(只执行一次) const handleTransitionEnd = () => { // 交换DOM位置 if (direction === "up") { currentRectangle.parentNode.insertBefore(currentRectangle, siblingRectangle); } else { currentRectangle.parentNode.insertBefore(siblingRectangle, currentRectangle); } // 强制浏览器重绘,确保DOM交换后的布局生效 void currentRectangle.offsetHeight; void siblingRectangle.offsetHeight; // 移除动画类 currentRectangle.classList.remove(currentClass); siblingRectangle.classList.remove(siblingClass); // 清理事件监听,避免重复触发 currentRectangle.removeEventListener("transitionend", handleTransitionEnd); siblingRectangle.removeEventListener("transitionend", handleTransitionEnd); }; currentRectangle.addEventListener("transitionend", handleTransitionEnd); siblingRectangle.addEventListener("transitionend", handleTransitionEnd); }
CSS(保留原样式)
.container { display: flex; flex-direction: column; align-items: center; height: 400px; } .rectangle { width: 150px; height: 100px; background-color: #ccc; border: solid rgb(255, 126, 126); margin: 10px; text-align: center; font-size: 18px; position: relative; transition: transform 0.5s ease-in-out; } .move-up { transform: translateY(-120px); } .move-down { transform: translateY(120px); } .button { width: 100px; height: 30px; background-color: #ddd; border: none; margin: 5px; cursor: pointer; }
HTML(保留原结构)
<div class="container"> <div class="rectangle"> <div>1</div> <button class="button up">Up</button> <button class="button down">Down</button> </div> <div class="rectangle"> <div>2</div> <button class="button up">Up</button> <button class="button down">Down</button> </div> <div class="rectangle"> <div>3</div> <button class="button up">Up</button> <button class="button down">Down</button> </div> </div>
关键改动说明
- 用transitionend替代setTimeout:确保动画真正完成后再操作DOM,避免时间不匹配导致的提前或延迟操作。
- 强制重绘:通过
void element.offsetHeight触发浏览器重绘,让浏览器先更新DOM交换后的布局位置,再移除transform类,此时transform从偏移值回到0,不会触发额外过渡(因为布局位置已经和目标位置一致)。 - 清理事件监听:动画结束后移除
transitionend监听,避免后续操作重复触发。
内容的提问来源于stack exchange,提问作者First Last
相关产品推荐
相关产品推荐

