我的能力有限,若有錯誤請你們多多點評react
start 動畫
,若是此時鼠標不擡起,繼續保持 start 動畫
結尾樣式end 動畫
很是簡單,咱們經過兩個組件達到效果git
TouchRipple
組件處理動畫邏輯「水波紋位置、處理卸載 Ripple
」Ripple
組件用於展現單個 水波紋
function TouchRipple() {
return <div> <Ripple /> <Ripple /> ..... </div>
}
複製代碼
TouchRipple
// 僞代碼
const ref = useRef()
<div onMouseDown={ref.start} onMouseUp={ref.exit}>
<TouchRipple ref={ref} /> </div>
複製代碼
material-ui
中 TouchRipple
組件用來附加水波紋效果,material TouchRipple 源碼地址github
export function Ripple(props) {
const {
rippleX,
rippleY,
rippleSize,
in: inProp,
onExited = () => {},
timeout
} = props;
const [leaving, setLeaving] = React.useState(false);
const rippleClassName = clsx("ripple", "rippleVisible");
const rippleStyles = {
width: rippleSize,
height: rippleSize,
top: -(rippleSize / 2) + rippleY,
left: -(rippleSize / 2) + rippleX
};
const childClassName = clsx("child", {
// 根據 leaving,執行結束動畫
childLeaving: leaving
});
React.useEffect(() => {
if (!inProp) {
// 執行 exit 動畫
setLeaving(true);
// Unmount
const timeoutId = setTimeout(() => {
onExited();
}, timeout);
return () => {
clearTimeout(timeoutId);
};
}
return undefined;
}, [onExited, inProp, timeout]);
return (
<span className={rippleClassName} style={rippleStyles}> <span className={childClassName} /> </span> ); } 複製代碼
import { TransitionGroup } from "react-transition-group";
function TouchRipple() {
// 記錄 ripples
const [ripples, setRipples] = React.useState([]);
// 獲取 container ref
const container = React.useRef(null);
return (
<span ref={container} className={"touchRippleRoot"}> <TransitionGroup component={null} exit> {ripples} </TransitionGroup> </span>
)
}
複製代碼
const startCommit = React.useCallback(
params => {
const { rippleX, rippleY, rippleSize } = params;
// 追加 ripple,開啓動畫
setRipples(oldRipples => [
...oldRipples,
<Ripple
key={nextKey.current}
classes={classes}
timeout={DURATION}
rippleX={rippleX}
rippleY={rippleY}
rippleSize={rippleSize}
/>
]);
nextKey.current += 1;
},
[classes]
);
const start = React.useCallback(
(event = {}, options = {}) => {
// 默認是 centerProp
const { center = centerProp } = options;
const element = container.current;
const rect = element.getBoundingClientRect()
let rippleX;
let rippleY;
let rippleSize;
// 設置 rippleX rippleY
if (
center
) {
rippleX = Math.round(rect.width / 2);
rippleY = Math.round(rect.height / 2);
} else {
const clientX = event.clientX
const clientY = event.clientY
rippleX = Math.round(clientX - rect.left);
rippleY = Math.round(clientY - rect.top);
}
// 設置 rippleSize
if (center) {
rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
} else {
const sizeX =
Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
const sizeY =
Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
}
startCommit({ rippleX, rippleY, rippleSize });
},
[startCommit, centerProp]
);
複製代碼
// 這裏處理很簡單,直接移除第一個 ripple
const stop = React.useCallback(() => {
setRipples(oldRipples => {
if (oldRipples.length > 0) {
return oldRipples.slice(1);
}
return oldRipples;
});
}, []);
複製代碼
// 將 start stop 暴露出去
React.useImperativeHandle(
ref,
() => ({
start,
stop
}),
[start, stop]
);
複製代碼
const rippleRef = React.useRef();
<div className="test" onMouseDown={e => { rippleRef.current.start(e); }} onMouseUp={() => { rippleRef.current.stop(); }} > 註冊 <TouchRipple ref={rippleRef} /> </div> 複製代碼
最後獻上完整代碼,供你們在線編輯,方便測試學習
在線編輯代碼測試