模仿 Google Material
點擊波紋效果。css
使用 react-spring
動畫庫。html
yarn add react-spring@next
複製代碼
按鈕結構:react
<button>
<span class="g-ripple" />
<span>text</span>
</button>
複製代碼
其中 .g-ripple
用來模擬波紋效果。git
.g-ripple {
position: absolute; /* 經過top和left修改位置 */
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
}
複製代碼
父組件 button
至少包含:github
{
position: relative; /* 子組件須要經過絕對佈局定位 */
overflow: hidden; /* 防止波紋顯示溢出來 */
}
複製代碼
動畫原理:spring
點擊後獲取到相對於
button
的座標,計算波紋的正確座標,經過react-spring
進行動畫,修改opacity: 1->0
,transform: scale(0)->scale(2)
完成動畫。shell
import React, { useState, useEffect, useRef } from 'react';
import './index.css';
import { useSpring, animated } from 'react-spring';
function calcEventRelativePos(event) {
const rect = event.target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
}
function Ripple(props) {
const [data, setData] = useState({ top: 0, left: 0, width: 0, height: 0 });
const isInit = useRef(true);
const rippleEl = useRef(null);
const { spawnData } = props;
const rippleAnim = useSpring({
from: {
...props.style,
...data,
transform: 'scale(0)',
opacity: 1
},
to: !isInit.current ? { opacity: 0, transform: 'scale(3)' } : {},
config: {
duration: props.duration || 800
},
reset: true
});
useEffect(() => {
if (isInit.current) {
isInit.current = false;
} else {
const parentEl = rippleEl.current.parentElement;
const size = Math.max(parentEl.offsetWidth, parentEl.offsetHeight);
setData({
width: size,
height: size,
top: spawnData.y - size / 2 || 0,
left: spawnData.x - size / 2 || 0
});
}
}, [spawnData]);
return (
<animated.span className="g-ripple" style={rippleAnim} ref={rippleEl} ></animated.span> ); } export { Ripple, calcEventRelativePos }; 複製代碼
import React, { useState } from 'react';
import { Ripple, calcEventRelativePos } from './ripple';
function RippleButton(props) {
const [spawnData, setSpawnData] = useState({});
function onClick(event) {
props.onClick && props.onClick();
setSpawnData({
...calcEventRelativePos(event),
time: Date.now()
});
}
return (
<button {...props} type="button" className={`g-btn ${props.className || ' '}`} onClick={onClick} style={props.style} > <Ripple spawnData={spawnData} /> <span>{props.children}</span> </button> ); } export { RippleButton }; 複製代碼
.g-btn {
display: inline-block;
border: 1px solid #D0D3D4;
border-radius: 4px;
padding: 0 16px;
background: #3498DB;
margin: 0;
height: 32px;
outline: none;
cursor: pointer;
/* important↓ */
position: relative;
overflow: hidden;
}
.g-btn:hover {
background: #5DADE2;
}
.g-btn>span {
display: inline-block;
color: white;
pointer-events: none;
}
.g-ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
opacity: 1;
}
複製代碼