如何优化flexbox/grid布局中的渐隐过滤动画实现方案?
优化过滤动画的实现方案
不需要克隆元素,你的核心问题是依赖硬编码的setTimeout同步动画,不仅逻辑复杂,还容易和CSS过渡时长耦合出错。下面是两种更优的实现方式:
一、用transitionend事件替代硬延时(推荐)
直接监听元素的过渡结束事件,替代固定时长的setTimeout,逻辑更可靠,也不需要手动同步CSS和JS的时间:
修改后的代码
CSS(微调,明确过渡属性)
.grid { display: flex; width: 100%; margin-top: 40px; flex-wrap: wrap; } .grid-item { flex: 0 0 calc(33.333% - 24px); padding: 12px; /* 明确过渡属性,避免触发不必要的事件 */ transition: opacity 0.3s ease-in, transform 0.3s ease-in; } img { width: 100%; } .grid-item.hidden { opacity: 0; transform: translateY(20px); } .grid-item.fully-hidden { display: none; }
JavaScript(重写逻辑,用事件监听替代setTimeout)
const filterBtn = document.querySelector('.filter'); const gridItems = document.querySelectorAll('.grid-item'); filterBtn.addEventListener('click', function() { // 提前分类要保留和隐藏的元素,减少重复DOM查询 const keepItems = []; const hideItems = []; gridItems.forEach(item => { item.classList.contains('will-hide') ? hideItems.push(item) : keepItems.push(item); }); // 第一步:给所有元素添加隐藏类,触发淡出动画 gridItems.forEach(item => item.classList.add('hidden')); // 监听隐藏动画结束,给要移除的元素添加完全隐藏类 const handleHideEnd = function(e) { // 只监听opacity或transform的过渡结束,避免多次触发 if (!['opacity', 'transform'].includes(e.propertyName)) return; this.classList.add('fully-hidden'); this.removeEventListener('transitionend', handleHideEnd); }; hideItems.forEach(item => item.addEventListener('transitionend', handleHideEnd)); // 等待所有隐藏元素的动画完成后,逐个显示保留元素 Promise.all(hideItems.map(item => { return new Promise(resolve => { item.addEventListener('transitionend', resolve, { once: true }); }); })).then(() => { keepItems.forEach((item, index) => { // 逐个延迟显示,保持原有的 stagger 效果 setTimeout(() => { item.classList.remove('fully-hidden'); // 强制触发重绘,避免过渡不生效 void item.offsetWidth; item.classList.remove('hidden'); }, index * 100); }); }); });
二、简化版:用CSS变量+动画控制时序(更轻量)
如果不需要精确监听动画结束,也可以用CSS动画配合类名,大幅减少JS逻辑:
示例代码
CSS
.grid { display: flex; width: 100%; margin-top: 40px; flex-wrap: wrap; } .grid-item { flex: 0 0 calc(33.333% - 24px); padding: 12px; transition: opacity 0.3s ease-in, transform 0.3s ease-in; opacity: 1; transform: translateY(0); } img { width: 100%; } /* 隐藏状态 */ .grid-item.will-hide { opacity: 0; transform: translateY(20px); pointer-events: none; } /* 过滤激活时,给保留元素添加淡入动画 */ .grid.filter-active .grid-item:not(.will-hide) { opacity: 0; transform: translateY(20px); animation: fadeIn 0.3s ease-in forwards; } @keyframes fadeIn { to { opacity: 1; transform: translateY(0); } }
JavaScript
const filterBtn = document.querySelector('.filter'); const grid = document.querySelector('.grid'); filterBtn.addEventListener('click', function() { grid.classList.add('filter-active'); // 给每个保留元素设置动画延迟,实现逐个显示效果 document.querySelectorAll('.grid-item:not(.will-hide)').forEach((item, index) => { item.style.animationDelay = `${index * 100}ms`; }); // 动画结束后移除激活类,可根据需求调整时机 setTimeout(() => grid.classList.remove('filter-active'), 300 + (document.querySelectorAll('.grid-item:not(.will-hide)').length * 100)); });
为什么不需要克隆元素?
克隆元素会额外增加DOM节点,还需要同步原元素的事件绑定、数据属性等状态,反而增加维护成本。直接控制现有元素的类名和样式,逻辑更简洁,性能也更好,完全能满足你的需求。
内容的提问来源于stack exchange,提问作者Runnick
相关产品推荐
相关产品推荐

