一塊兒逐步實現「水波紋效果」- 基於 React

介紹

  1. 閱讀源碼是學習的最好方式,material-ui 是知名的 react ui 庫,目前在 github 上有 55.8k star,很是值得咱們去學習
  2. 本系列文章會借鑑 material-ui 源代碼,一塊兒實現屬於咱們本身的 ui 組件 (文章最後會給出代碼地址)
  3. 本篇是該系列的第一篇「水波紋效果 ripple」,後續會持續更新

我的能力有限,若有錯誤請你們多多點評react

實現思路

實現效果

  1. 鼠標按下時,開啓水波紋 start 動畫,若是此時鼠標不擡起,繼續保持 start 動畫結尾樣式
  2. 鼠標擡起後,開啓水波紋 end 動畫
  3. 鼠標屢次點擊,出現多個水波紋效果

思路

很是簡單,咱們經過兩個組件達到效果git

  1. TouchRipple 組件處理動畫邏輯「水波紋位置、處理卸載 Ripple
  2. Ripple 組件用於展現單個 水波紋
function TouchRipple() {
  return <div> <Ripple /> <Ripple /> ..... </div>
}
複製代碼
  1. 任何一個元素想要擁有水波紋效果,只須要引入 TouchRipple
// 僞代碼
const ref = useRef()
<div onMouseDown={ref.start} onMouseUp={ref.exit}>
  <TouchRipple ref={ref} /> </div>
複製代碼

代碼實現

material-uiTouchRipple 組件用來附加水波紋效果,material TouchRipple 源碼地址github

Ripple 組件

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> ); } 複製代碼

TouchRipple 組件

  1. 記錄當前有幾個 ripple
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>
  )
}
複製代碼
  1. 添加 ripple
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]
  );
複製代碼
  1. 移除 ripple
// 這裏處理很簡單,直接移除第一個 ripple
const stop = React.useCallback(() => {
  setRipples(oldRipples => {
    if (oldRipples.length > 0) {
      return oldRipples.slice(1);
    }
    return oldRipples;
  });
}, []);
複製代碼
  1. 向外暴露接口
// 將 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> 複製代碼

結束

最後獻上完整代碼,供你們在線編輯,方便測試學習

在線編輯代碼測試

相關文章
相關標籤/搜索