「React」製做波紋按鈕 Ripple Button

簡介

模仿 Google Material 點擊波紋效果。css

預覽

lZ2RjP.gif

準備

使用 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->0transform: scale(0)->scale(2) 完成動畫。shell

代碼

Ripple.js

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

RippleButton.js

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

index.css

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

github-react-ripple-button佈局

相關文章
相關標籤/搜索