如何将使用easeInExpo缓动的jQuery菜单代码重写为原生JS+CSS实现
实现方案
方案1:CSS Transition + 极简JS(生产环境移动端首选)
该方案依赖CSS硬件加速,动画流畅度远高于纯JS实现,完全匹配原代码的交互逻辑,是移动端场景的最优选择:
- 缓动效果:原代码的
easeInExpo可以用CSScubic-bezier(0.95, 0.05, 0.795, 0.035)1:1还原 - 滑动效果通过
max-height实现,解决height: auto无法触发过渡的问题 - 延迟、时长参数完全对齐原代码:展开延迟35ms、时长300ms,收起时长550ms
CSS代码
ul#nav { max-height: 0; overflow: hidden; /* 收起动画:时长550ms,默认缓动和原jQuery slideUp效果一致 */ transition: max-height 550ms ease; /* 移动端性能优化 */ will-change: max-height; } ul#nav.active { /* 取值要大于导航菜单最大可能高度,比如导航最高200px可设为300px */ max-height: 300px; /* 展开动画:延迟35ms,时长300ms,easeInExpo缓动 */ transition: max-height 300ms cubic-bezier(0.95, 0.05, 0.795, 0.035) 35ms; } button#hmenu.active { /* 保留原有按钮激活样式即可 */ }
JS代码
const hmenu = document.querySelector('button#hmenu'); const nav = document.querySelector('ul#nav'); hmenu.addEventListener('click', () => { const isActive = hmenu.classList.contains('active'); if (!isActive) { nav.classList.add('active'); hmenu.classList.add('active'); } else { nav.classList.remove('active'); hmenu.classList.remove('active'); } });
方案2:纯JS原生动画实现(精细控制场景备选)
如果需要更灵活的动画控制(比如中途打断动画、动态高度适配),可以用requestAnimationFrame实现,直接复用原有的缓动函数:
const hmenu = document.querySelector('button#hmenu'); const nav = document.querySelector('ul#nav'); let isNavOpen = false; let animationId = null; // 复用原easeInExpo缓动函数 function easeInExpo(t, b, c, d) { return t == 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } // 滑动展开函数 function slideDown(el, duration = 300, delay = 35) { setTimeout(() => { const startHeight = 0; el.style.overflow = 'hidden'; // 获取元素真实高度 el.style.maxHeight = 'none'; const targetHeight = el.offsetHeight; el.style.height = startHeight + 'px'; const startTime = performance.now(); function animate(now) { const elapsed = now - startTime; const progress = Math.min(elapsed / duration, 1); const currentHeight = easeInExpo(progress, startHeight, targetHeight - startHeight, 1); el.style.height = currentHeight + 'px'; if (progress < 1) { animationId = requestAnimationFrame(animate); } else { el.style.height = 'auto'; } } if (animationId) cancelAnimationFrame(animationId); animationId = requestAnimationFrame(animate); }, delay); } // 滑动收起函数 function slideUp(el, duration = 550) { const startHeight = el.offsetHeight; el.style.overflow = 'hidden'; const startTime = performance.now(); function animate(now) { const elapsed = now - startTime; const progress = Math.min(elapsed / duration, 1); const currentHeight = startHeight - (progress * startHeight); el.style.height = currentHeight + 'px'; if (progress < 1) { animationId = requestAnimationFrame(animate); } else { el.style.height = '0'; } } if (animationId) cancelAnimationFrame(animationId); animationId = requestAnimationFrame(animate); } // 绑定点击事件 hmenu.addEventListener('click', () => { if (!isNavOpen) { slideDown(nav); hmenu.classList.add('active'); isNavOpen = true; } else { slideUp(nav); hmenu.classList.remove('active'); isNavOpen = false; } });
生产环境注意事项
- 方案1中
max-height不要设置过大,否则会出现动画收尾过快的问题,建议比实际最大导航高度高30%即可 - 移动端可添加
touch-action: manipulation到按钮上,避免点击300ms延迟 - 方案1兼容性覆盖iOS9+、安卓4.4+,完全满足绝大多数移动端生产场景需求
内容的提问来源于stack exchange,提问作者drooh
相关产品推荐
相关产品推荐

