Intersection Observer中async/await失效问题排查求助
问题描述
我用Intersection Observer监听页面元素,当元素进入视口后,通过classList.add()和classList.remove()循环添加/移除animate.css的动画类。现在Observer能正常触发,但async/await完全没用——所有计时器在Observer触发时立刻启动,根本没法在元素移出视口或者表单输入框激活的时候终止动画序列。
目前临时办法是把setTimeout()的延迟设成递增的,让动画能按顺序播,但终止动画的问题还是没解决。查了一堆MDN文档,搞不清是async/await用错了,还是Intersection Observer本身不支持这俩关键字。
相关代码:
const contactSectionSubmitButtonWrapper = document.querySelector('.submit-button-wrapper'); async function timer(fn, milliseconds){ await setTimeout(fn, milliseconds); } async function animation01(){ if(document.activeElement.id == ("name-field" || "email-field" || "message-field")) { console.log('Submit button animation stopped because an input field is active.'); } else { console.log('Submit button animation 01'); contactSectionSubmitButtonWrapper.classList.add('animate__pulse'); } } // There are twelve more of these animationXX functions, // but it's the same basic concept repeated, so I omitted // the other twelve for the sake of brevity. const contactSectionSubmitButtonObserver = new IntersectionObserver( async(entries)=>{ console.log(entries); for (const entry of entries) { if(entry.isIntersecting) { await timer(animation01, 3000); await timer(animation02, 6000); await timer(animation03, 9000); await timer(animation04, 12000); await timer(animation05, 15000); await timer(animation06, 18000); await timer(animation07, 21000); await timer(animation08, 24000); await timer(animation09, 27000); await timer(animation10, 30000); await timer(animation11, 31000); await timer(animation12, 35000); await timer(animation13, 38000); } } }, { threshold: 0.1, root: null, rootMargin: '0px' } ); contactSectionSubmitButtonObserver.observe(contactSectionSubmitButtonWrapper);
问题根源
async/await用法错误:setTimeout返回的是计时器ID,不是Promise,直接await setTimeout()完全无效,会导致所有计时器同时启动,根本不会按顺序等待。- 无动画终止机制:没有保存计时器ID,也没监听元素移出视口、输入框激活事件,一旦动画序列启动就无法中断。
- 条件判断逻辑错误:
document.activeElement.id == ("name-field" || "email-field" || "message-field")的写法不对,逻辑或运算会直接返回第一个真值"name-field",相当于只判断是否是name-field,另外两个输入框不会触发停止逻辑。
修复方案
1. 把setTimeout包装成Promise
要让await生效,必须将setTimeout转为Promise,同时返回计时器ID方便后续清理:
function timer(fn, milliseconds) { return new Promise(resolve => { const timerId = setTimeout(() => { fn(); resolve(timerId); }, milliseconds); }); }
2. 新增动画终止逻辑
维护一个数组保存所有计时器ID,同时监听输入框激活、元素移出视口事件,触发时清空所有计时器并移除动画类:
let timerIds = []; const inputFields = document.querySelectorAll('#name-field, #email-field, #message-field'); // 终止所有动画的通用函数 function stopAllAnimations() { timerIds.forEach(id => clearTimeout(id)); timerIds = []; // 移除所有可能的动画类,避免残留 contactSectionSubmitButtonWrapper.classList.remove('animate__pulse', 'animate__bounce', /* 其他动画类 */); } // 监听输入框激活事件 inputFields.forEach(field => { field.addEventListener('focus', stopAllAnimations); });
3. 修正Intersection Observer回调
当元素移出视口时立刻终止动画;进入视口时启动序列,每一步都检查是否需要终止:
const contactSectionSubmitButtonObserver = new IntersectionObserver(entries => { for (const entry of entries) { if (entry.isIntersecting) { // 用自执行异步函数启动动画序列 (async () => { try { // 每一步先检查是否已终止 if (timerIds.length === 0) return; timerIds.push(await timer(animation01, 3000)); if (timerIds.length === 0) return; timerIds.push(await timer(animation02, 3000)); // 改用相对延迟,不用递增总时间 // 后续动画依次类推... } catch (e) { console.error('动画序列出错:', e); } })(); } else { // 元素移出视口,终止动画 stopAllAnimations(); } } }, { threshold: 0.1, root: null, rootMargin: '0px' });
4. 修正输入框判断逻辑
把错误的条件判断改成正确的多值检查:
async function animation01() { const activeId = document.activeElement.id; if (['name-field', 'email-field', 'message-field'].includes(activeId)) { console.log('Submit button animation stopped because an input field is active.'); stopAllAnimations(); } else { console.log('Submit button animation 01'); contactSectionSubmitButtonWrapper.classList.add('animate__pulse'); // 动画结束后自动移除类,避免重复添加 setTimeout(() => { contactSectionSubmitButtonWrapper.classList.remove('animate__pulse'); }, 1000); // 对应animate.css pulse动画的时长 } }
5. 优化动画序列写法(可选)
没必要写13个单独的animationXX函数,把动画配置做成数组循环执行更简洁:
// 动画配置数组:[延迟时间, 动画类] const animations = [ [3000, 'animate__pulse'], [3000, 'animate__bounce'], // 补充其他动画配置... ]; // 通用动画序列执行函数 async function runAnimationSequence() { for (const [delay, animClass] of animations) { if (timerIds.length === 0) break; // 已终止则退出循环 const activeId = document.activeElement.id; if (['name-field', 'email-field', 'message-field'].includes(activeId)) { stopAllAnimations(); break; } // 添加动画类 contactSectionSubmitButtonWrapper.classList.add(animClass); // 动画结束后移除类 setTimeout(() => { contactSectionSubmitButtonWrapper.classList.remove(animClass); }, 1000); // 根据实际动画时长调整 // 等待延迟,保存计时器ID timerIds.push(await timer(() => {}, delay)); } } // 在Observer回调里调用这个函数 if (entry.isIntersecting) { runAnimationSequence(); }
内容的提问来源于stack exchange,提问作者SwineBurglar
相关产品推荐
相关产品推荐

