美化一下咱們的Antd菜單吧,讓每次點擊都有波紋效果

這是我參與更文挑戰的第7天,活動詳情查看: 更文挑戰css

點擊沒效果,總感受少點啥

用antd自帶菜單導航,點擊菜單項的時候並無什麼特別cool的效果,那咱們就本身動手加上吧。react

借鑑了網上一大佬的實現,並在其基礎上補全完善

大佬的實現git

在其基礎上我補全了功能,使其可以解決我所遇到的問題:github

首先菜單項是多項而且單項點擊切換的,大佬的實現並不能很好完成這一點,當存在多個具有該特效的節點時,一旦刷新,全部的節點都會觸發刷新,那就是滿屏的波紋,這一點必須解決。spring


開始重構以達到指望

首先作成一個高階組件,這樣就可使用ta來對其餘組件進行包裝,使其具備點擊特效,方便使用。數組

組件命名爲withMaterialHoc,先貼出組件的實現:markdown

withMaterialHoc/index.jsantd

import React from 'react';
import { Ripple, calcEventRelativePos } from './ripple';

let withMaterialHoc = (WrappedComponent) => {
  return class WithMaterial extends React.Component {
    constructor(props) {
      super(props)
      this.isSwitchIntl = false;
      this.state = {
        spawnData: "",
        clickCount: 0,
        isSwitchIntl: false
      }
    }

    handleClick = (event) => {
      this.setState({
        spawnData: calcEventRelativePos(event),
        time: Date.now(),
        clickCount: this.state.clickCount + 1
      });
    }

    shouldComponentUpdate(nextProps, nextState) {
      if (nextProps.intlData != this.props.intlData) {
        return true;
      }
      if (nextState.clickCount && (nextState.clickCount - this.state.clickCount === 1)) {
        return true
      } else {
        return false
      }
    }

    handleRippleEnd = () => {
      let value = this.state.clickCount - 1
      if (value < 0) {
        value = 0
      }
      this.setState({
        clickCount: value
      })
    }

    render() {
      const { spawnData } = this.state;
      const { className, style } = this.props;
      return (
        <div className={`g-btn ${className || ' '}`} onClick={this.handleClick} style={style} > <WrappedComponent {...this.props} /> <Ripple handleRippleEnd={this.handleRippleEnd} spawnData={spawnData} /> </div>
      );
    }
  };
}

export default withMaterialHoc;
複製代碼

ripple.jsapp

import React, { useState, useEffect, useRef, Fragment,memo } 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, handleRippleEnd} = props;
  const rippleAnim = useSpring({
    from: {
      ...props.style,
      ...data,
      transform: 'scale(0)',
      opacity: 1,
    },
    to: !isInit.current ? { opacity: 0, transform: 'scale(2)' } : {},
    config: {
      duration: props.duration || 300,
    },
    onRest: () => {
      handleRippleEnd()
    },
    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+50 || 50
      });
    }
  }, [spawnData]);
  return (
    <animated.span className="g-ripple" style={rippleAnim} ref={rippleEl} ></animated.span>
  );
}

Ripple = memo(Ripple)

export { Ripple, calcEventRelativePos };

複製代碼

分析:

大佬的實現爲何不行?

由於ripple是一個函數組件,每次父組件刷新,子組件就會刷新,從而出發了動畫(實現用的是react-spring庫,有興趣能夠深刻了解)。函數

那麼解決這個問題的關鍵就是,我點擊哪一個節點,哪一個節點觸發刷新,其餘的節點不刷新

使用memo來控制函數組件的刷新

React.memo

React.memo 爲高階組件。它與 React.PureComponent 很是類似,但它適用於函數組件,但不適用於 class 組件。

首先若是父組件傳入子組件的props不改變或者是引用的地址不變(淺比較),那麼被memo包裝過的函數組件就不會觸發刷新,so,問題就真麼愉快的解決了。

看下效果吧

結語

效果就是寧缺毋濫的,有時恰到好處的點綴,會起到意想不到的效果,內外兼修,纔是最佳的實現,趕忙加上試試。

集成了該功能的🌰

相關文章
相關標籤/搜索