我的能力有限,若有错误请你们多多点评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> 复制代码
最后献上完整代码,供你们在线编辑,方便测试学习
在线编辑代码测试