如何为函数调用设置延迟?解决Material UI风格按钮涟漪清理异常
解决Material UI按钮涟漪快速点击时的清理问题
问题核心是快速连续点击时,多个清理操作同时触发,各自的延迟逻辑互相干扰,没有按顺序等待前一个动画完成再处理下一个。解决思路是给清理操作加个任务队列,让它们排队执行。
具体实现步骤:
- 新增两个状态:一个存待清理的涟漪ID队列,一个标记是否正在处理队列
- 修改
handleRippleRemoval:只触发涟漪淡出动画,把要清理的ID加入队列 - 写一个队列处理函数,确保前一个清理完成后再处理下一个
代码示例(React):
import { useState, useCallback, useEffect } from 'react'; const RippleButton = () => { const [ripples, setRipples] = useState([]); const [cleanupQueue, setCleanupQueue] = useState([]); const [isProcessing, setIsProcessing] = useState(false); // 用递增计数器生成唯一涟漪ID,比Date.now()更可靠 const [rippleIdCounter, setRippleIdCounter] = useState(0); // 生成新涟漪的函数 const addRipple = (e) => { const button = e.currentTarget; const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; const newRipple = { id: rippleIdCounter, width: `${size}px`, height: `${size}px`, top: `${y}px`, left: `${x}px`, visible: true }; setRipples(prev => [...prev, newRipple]); setRippleIdCounter(prev => prev + 1); }; // 处理清理队列的核心函数 const processCleanupQueue = useCallback(async () => { // 如果正在处理或队列为空,直接返回 if (isProcessing || cleanupQueue.length === 0) return; setIsProcessing(true); const targetId = cleanupQueue[0]; // 等待淡出动画完成(550ms) await new Promise(resolve => setTimeout(resolve, 550)); // 从涟漪数组中移除目标涟漪 setRipples(prev => prev.filter(ripple => ripple.id !== targetId)); // 移除队列中的第一个任务 setCleanupQueue(prev => prev.slice(1)); // 标记处理完成,继续处理下一个任务 setIsProcessing(false); processCleanupQueue(); }, [cleanupQueue, isProcessing]); // 修改后的涟漪移除触发函数 const handleRippleRemoval = () => { if (ripples.length === 0) return; const lastRipple = ripples[ripples.length - 1]; // 触发淡出动画 setRipples(prev => prev.map(ripple => ripple.id === lastRipple.id ? {...ripple, visible: false} : ripple )); // 避免重复加入同一个ID到队列 if (!cleanupQueue.includes(lastRipple.id)) { setCleanupQueue(prev => [...prev, lastRipple.id]); } }; // 监听队列变化,自动启动处理 useEffect(() => { processCleanupQueue(); }, [cleanupQueue]); return ( <button onMouseDown={addRipple} onMouseUp={handleRippleRemoval} onMouseLeave={handleRippleRemoval} // 鼠标离开也要触发清理 style={{ position: 'relative', overflow: 'hidden', padding: '12px 24px', fontSize: '16px', border: 'none', borderRadius: '4px', backgroundColor: '#2196f3', color: 'white', cursor: 'pointer' }} > 带涟漪的按钮 {ripples.map(ripple => ( <div key={ripple.id} style={{ position: 'absolute', width: ripple.width, height: ripple.height, top: ripple.top, left: ripple.left, borderRadius: '50%', backgroundColor: 'rgba(255,255,255,0.3)', transform: 'scale(0)', opacity: ripple.visible ? 1 : 0, transition: 'transform 300ms ease-out, opacity 250ms ease-out', transformOrigin: 'center' }} className={ripple.visible ? 'ripple-expand' : 'ripple-fade'} /> ))} <style jsx>{` .ripple-expand { animation: expand 300ms ease-out forwards; } .ripple-fade { animation: fade 250ms ease-out forwards; } @keyframes expand { from { transform: scale(0); } to { transform: scale(1); } } @keyframes fade { from { opacity: 1; } to { opacity: 0; } } `}</style> </button> ); }; export default RippleButton;
关键说明:
- 用任务队列
cleanupQueue存储需要清理的涟漪ID,确保每个清理操作按顺序执行 isProcessing标记避免同时处理多个任务,防止队列混乱- 给每个涟漪分配唯一ID,避免清理时误删其他涟漪
- 加入了
onMouseLeave事件处理,确保鼠标离开按钮时也能触发涟漪清理
内容的提问来源于stack exchange,提问作者Gabriel Ribeiro
相关产品推荐
相关产品推荐

