如何让CSS动画在重新生成网格容器时仅执行一次?
解决笔记容器动画重复执行的问题
我来帮你搞定这个每次提交表单就所有容器重新播放动画的问题~核心原因是你的draw_flex()函数每次都会重新生成所有HTML元素并替换整个容器内容,相当于所有笔记容器都被重新创建了一遍,CSS动画自然会再次触发。下面给你几种实用的解决方案,按推荐程度排序:
方法1:仅渲染新添加的笔记(最优方案)
与其每次清空重绘所有元素,不如只追加新的笔记,这样只有新容器会触发动画,旧容器不受影响。我们需要维护一个变量记录最后一次渲染到哪个索引,每次只处理新增的笔记:
修改JavaScript代码:
// 初始化最后渲染的索引,从0开始 let lastRenderedIndex = 0; function draw_flex() { // 只处理从lastRenderedIndex到notes末尾的新笔记 for(let i = lastRenderedIndex; i < notes.length; i++) { // 创建外层列容器 const noteCol = document.createElement('div'); noteCol.className = 'col-12 col-sm-12 col-md-4 col-lg-4 col-xl-2'; // 创建笔记容器 const container = document.createElement('div'); container.className = 'container'; // 创建任务文本元素 const taskEl = document.createElement('div'); taskEl.className = 'text'; taskEl.textContent = notes[i].task; // 创建日期元素 const dateEl = document.createElement('div'); dateEl.className = 'date'; dateEl.textContent = notes[i].date; // 创建时间元素 const timeEl = document.createElement('div'); timeEl.className = 'time'; timeEl.textContent = notes[i].time; // 创建删除按钮 const deleteBtn = document.createElement('div'); deleteBtn.className = 'button'; deleteBtn.addEventListener('click', () => delete_note_by_key(i)); const icon = document.createElement('i'); icon.className = 'fa fa-times'; deleteBtn.appendChild(icon); // 组装元素 container.appendChild(taskEl); container.appendChild(dateEl); container.appendChild(timeEl); container.appendChild(deleteBtn); noteCol.appendChild(container); // 追加到页面容器中 document.getElementById('my-flex').appendChild(noteCol); } // 更新最后渲染的索引,下次从这里开始 lastRenderedIndex = notes.length; }
调整CSS(可选,但更稳妥):
给动画加上animation-fill-mode,让元素保持动画结束后的状态,同时明确动画只执行一次:
div.container { background: url('notebg.png') no-repeat; height: 250px; animation-duration: 4s; animation-name: fadeIn; animation-fill-mode: forwards; /* 保持动画结束后的样式 */ animation-iteration-count: 1; /* 明确只执行一次(默认就是1,写出来更清晰) */ }
这种方法不仅解决了动画重复的问题,还提升了页面性能——不用每次销毁重建所有DOM元素。
方法2:给已动画过的容器标记状态
如果因为某些原因必须重新渲染所有元素,可以给每个笔记对象添加一个状态标记,只给未播放过动画的容器添加动画类:
修改CSS:
把动画抽成单独的类,默认容器不包含动画:
div.container { background: url('notebg.png') no-repeat; height: 250px; } .container.animate { animation-duration: 4s; animation-name: fadeIn; animation-fill-mode: forwards; }
修改JavaScript代码:
function draw_flex() { let html_string = ''; for(let i = 0; i < notes.length; i++) { // 检查当前笔记是否已经播放过动画,没有的话加animate类 const animateClass = notes[i].hasAnimated ? '' : 'animate'; html_string += ` <div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-2"> <div class="container ${animateClass}"> <div class="text">${notes[i].task}</div> <div class="date">${notes[i].date}</div> <div class="time">${notes[i].time}</div> <div class="button" onClick="delete_note_by_key(${i})"> <i class="fa fa-times"></i> </div> </div> </div> `; // 标记该笔记已播放过动画 notes[i].hasAnimated = true; } // 注意:原代码最后多了一个</div>,这里要去掉,因为外层容器已经存在 document.getElementById('my-flex').innerHTML = html_string; }
这种方法需要维护每个笔记的状态,适合必须全量渲染的场景,但性能不如方法1。
方法3:动画结束后移除动画类
通过监听animationend事件,在动画播放完毕后移除动画相关的类,这样即使元素被重新创建(不推荐),也不会再次触发动画:
修改CSS:
div.container { background: url('notebg.png') no-repeat; height: 250px; } .fade-in { animation-duration: 4s; animation-name: fadeIn; }
修改JavaScript代码:
function draw_flex() { // 清空容器 const flexContainer = document.getElementById('my-flex'); flexContainer.innerHTML = ''; for(let i = 0; i < notes.length; i++) { const noteCol = document.createElement('div'); noteCol.className = 'col-12 col-sm-12 col-md-4 col-lg-4 col-xl-2'; const container = document.createElement('div'); container.className = 'container fade-in'; // 监听动画结束事件,移除动画类 container.addEventListener('animationend', () => { container.classList.remove('fade-in'); }); // 剩下的元素创建逻辑和方法1一致,这里简化一下 container.innerHTML = ` <div class="text">${notes[i].task}</div> <div class="date">${notes[i].date}</div> <div class="time">${notes[i].time}</div> <div class="button" onClick="delete_note_by_key(${i})"> <i class="fa fa-times"></i> </div> `; noteCol.appendChild(container); flexContainer.appendChild(noteCol); } }
这种方法虽然能解决问题,但全量渲染DOM的性能开销还是存在,所以优先推荐方法1。
内容的提问来源于stack exchange,提问作者Anton Susman
相关产品推荐
相关产品推荐

