<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=">
  <title>Button effect</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
html,
body {
  margin: 0;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.btn {
  -webkit-appearance: none;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  padding: 16px 32px;
  border: none;
  border-radius: 3px;
  color: #222;
  font-weight: bold;
  background-color: #eee;
  transform: scale(1);
  transition: background-color .3s ease, transform .2s ease;
  
  &:hover {
    transform: scale(1.075);
    background-color: #ddd;
  }
  
  &:active {
    transform: scale(1);
  }
  
  &:focus {
    outline: none;
  }
  
  &__overlay-line {
    content: "";
    display: block;
    position: absolute;
    z-index: -1;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: linear-gradient(0deg, transparent, #aaa, transparent);
    transform: translateY(-100%);
    animation: slide-in-out 500ms ease;
  }
}

@keyframes slide-in-out {
  0% {
    transform: translateY(-100%);
}
  100% {
    transform: translateY(100%);
}
}
View Compiled
const { CSSTransition, TransitionGroup } = ReactTransitionGroup;

const useOverlayLines = () => {
  const [lines, setLines] = React.useState([]);
  const linesLastKey = React.useRef(1);
  const addLine = React.useCallback((key) => {
    setLines((oldLines) => ([...oldLines, linesLastKey.current++]));
  }, [setLines, linesLastKey]);

  const removeLine = React.useCallback((key) => {
    setLines(oldLines => {
      let index = oldLines.indexOf(key);

      if (index > -1) {
        const newLines = [...oldLines];
        newLines.splice(index, 1);
        return newLines;
      }
      return oldLines;
    });
  }, [setLines]);
  
  return {
    lines,
    addLine,
    removeLine
  }
}

const OverlayLinesAnimation = ({ lines, onRemoveLine = () => {}, children }) =>{
  return (
    <TransitionGroup>
      {lines && lines.map((key) => (
        <CSSTransition
          key={key}
          timeout={250} 
          onEntered={() => onRemoveLine(key)}
          unmountOnExit>
           <div className="btn__overlay-line"></div>
        </CSSTransition>
      ))}
     </TransitionGroup>
   );
}

const Button = ({ children, ...otherProps }) => {
  const {lines, addLine, removeLine} = useOverlayLines();
  return (
    <button type="button" onClick={addLine} className="btn" {...otherProps}>
      <OverlayLinesAnimation lines={lines} onRemoveLine={removeLine} />
      {children}
    </button>  
  )
}

const App = () => {
  return (
    <div className="container">
      <Button>Кнопка</Button>
     </div>
  )
}

ReactDOM.render(<App />, document.getElementById("app"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js
  3. https://unpkg.com/react-transition-group/dist/react-transition-group.js