Хвильовий ефект в React

  • 30 апреля, 17:33
  • 3809
  • 0

Багато з нас бачили  анімацію хвильового ефекту, яка була частиною Material Design. Хвиля являє собою коло, яке з'являється в точці клацання, а потім збільшується і зникає. Як інструмент для користувача інтерфейсу, це знайомий спосіб повідомити користувачеві, що сталося клацання.

В React найпростіший спосіб - використовувати  Material-UI, який є популярною UI бібліотекою. Загалом, це дуже хороша ідея, якщо тобі потрібна солідна UI бібліотека, яка генерує UI з коробки. Однак, для невеликого проекту не має сенсу вчитися працювати з великою бібліотекою тільки для досягнення одного ефекту.

Хвильовий ефект в React

Я переглянув безліч проектів, що реалізують щось подібне в Github, Codepen і Codesandbox, і черпав натхнення в деяких з кращих. Хвильовий можливий на будь-який веб-платформі, так як він досягається за допомогою CSS.

Ось як виглядає моя імплементація CSS.

<button class = "button" > 
  <div class = "ripple-container" > 
    <span class = "ripple" > </ span> 
  </ Div>
</ Button>

.button {
  overflow : hidden ;
  position : relative ;
}
.ripple-container {
  top : 0 ; 
  left : 0 ; 
  right : 0 ; 
  bottom : 0 ; 
  z-index : 0 ; 
  overflow : hidden ;
  position : absolute ;
}
.ripple-container span {
  position : absolute ;
  top : . . . ; 
  left : . . . ; 
  height : . . . ; 
  width : . . . ; 
  transform : scale ( 0 ) ;
  border-radius : 100% ; 
  opacity : 0. 25 ; 
  background-color : #fff ; 
  animation-name : ripple ;
  animation-duration : 850ms ; 
}
@keyframes ripple {
  to {
    opacity : 0 ; 
    transform : scale ( 2 ) ;
  }
}

Властивість  overflow: hidden запобігає виходу хвилі з контейнера. Брижі (хвилі) - це коло ( border-radius: 100% ), яке починається з невеликого розміру і стає більше по мірі зникнення. Зростаюча і зникає анімація досягається шляхом маніпулювання  transform: scale  і  opacity .

Однак, нам потрібно буде динамічно надати кілька стилів з використанням Javascript. Знайти позиційні координати: тобто  top і  left, які засновані на тому, де користувач клацнув, а також  height і  width, які залежать від розміру контейнера.

Почнемо

Для своїх стилів мені зручно використовувати стильові компоненти. Ти ж можеш використовувати те, що вважаєш за краще. Перше, що ми зробимо - додамо вищевказаний CSS в наші компоненти. Стилі будуть знаходитися в окремому файлі.

// file => styles.ts
import styled from "styled-components" ;
export const Button = styled . button ` 
  position : relative ;
  padding : 5px 30px ;
  overflow : hidden ;
  cursor : pointer ;
  background - color : $ { ( props ) = > ( props . color ? props . color : "tomato" ) } ;     
  color : #fff ;
  font - size : 20px ;
  border - radius : 20px ;
  border : 1px solid #fff ;
  text - align : center ;
  box - shadow : 0 0 5px rgba ( 0 , 0 , 0 , 0.4 ) ;     
  box - sizing : border - box ;
` ;
export const RippleContainer = styled . div ` 
  top : 0 ; 
  left : 0 ; 
  right : 0 ; 
  bottom : 0 ; 
  z - index : 0 ; 
  overflow : hidden ;
  position : absolute ;
  border - radius : inherit ;

  span {
    position : absolute ;
    transform : scale ( 0 ) ;
    border - radius : 100 % ; 
    opacity : 0.25 ; 
    background - color : $ { ( props ) = > props . color } ; 
    animation - name : ripple ;
    animation - duration : $ { ( props ) = > props . duration } ms ; 
  }

  @ Keyframes ripple {
    to {
      opacity : 0 ; 
      transform : scale ( 2 ) ;
    }
  }
` ;

Зверни увагу, що для кнопки  background-color залежить від  props color  самого стильового компонента, але якщо він не буде переданий, то за замовчуванням буде фон  tomato. Для контейнера теж саме. Плюс тривалість анімації залежить від  props . Це зроблено для того, щоб ми могли динамічно встановлювати ці значення пізніше. Давай визначимо їх зараз:

import React from 'react'
. . .

export function Ripple ( { duration = 850 , color = "#ffffff" } ) {     
  . . .
}

Далі для нашої брижі ми визначаємо масив і створюємо функцію для додавання мерехтіння в цей масив. Кожен елемент масиву буде об'єктом з властивостями  x,  y і  size, які є інформацією, необхідною для стилізації хвилі. Щоб обчислити ці значення, ми винесемо їх з події  mousedown.

export function Ripple ( { duration = 850 , color = '#ffffff' } ) {     
  const [ rippleArray , setRippleArray ] = React . useState ( [ ] ) ;  
  const addRipple = ( event ) = > {   

    // Координати контейнера
    const rippleContainer = event . currentTarget . getBoundingClientRect ( ) ;

    // Вибираємо найдовшу сторону
    const size =
      rippleContainer . width > rippleContainer . height 
        ? rippleContainer . width
        : RippleContainer . height ;

    // Координати клацання миші
    const x = event . pageX - rippleContainer . left - size / 2 ;   
    const y = event . pageY - rippleContainer . top - size / 2 ;   

    // Нова хвиля
    const newRipple = { 
      x ,
      y ,
      size
    } ;
    setRippleArray ( [ . . . rippleArray , newRipple ] ) ;
  } ;
  return ( 
    . . .
  ) ;
}

Наведений вище код використовує API браузера  getBoundClientRect(), який дозволяє нам отримати найдовший край контейнера і координати  x (left) і  y (top), щодо документа. Разом з  MouseEvent.pageX і  MouseEvent.pageY він дозволяє нам обчислювати координати  x і  y миші (клацання) щодо контейнера. 

Тепер можемо візуалізувати масив хвилі (брижі).

return ( 
  < RippleContainer duration = { duration } color = { color } onMouseDown = { addRipple } >
    { RippleArray . length & & 
      rippleArray . map ( ( ripple , index ) = > {  
        return ( 
          < span
            key = { "span" + index } 
            style = { {
              top : ripple . y ,
              left : ripple . x ,
              width : ripple . size ,
              height : ripple . size ,
            } }
          / >
        ) ;
      } ) }
  < / RippleContainer >
) ;

RippleContainer  - це стилізований компонент, який приймає  duration і  color як  props, разом з  addRipple в якості обробника події  onMouseDown. Усередині нього ми відображаємо всі наші хвилі і додаємо розраховані параметри  top, left, width  і  height .

З цим ми закінчили. Тим не менш, є ще одна маленька річ, яку потрібно зробити, а саме: очистити брижі після того, як вони зробили анімацію. Це зроблено для того, щоб застарілі елементи не захаращували DOM.

Нижче приведена реалізація і використання  useEffect хука. По суті, кожен раз, коли ми створюємо нову хвилю, таймер скидається. Зверни увагу, що тривалість тайм-ауту набагато більше, ніж наша брижа.

React . useEffect ( ( ) = > {  
  let bounce ;
  if ( rippleArray . length > 0 ) {    
    window . clearTimeout ( bounce ) ;
    bounce = window . setTimeout ( ( ) = > {  
      setRippleArray ( [ ] ) ;
      window . clearTimeout ( bounce ) ;
    } , Duration * 4 ) ; 
  }
  return ( ) = > window . clearTimeout ( bounce ) ;  
} , [ RippleArray . length , duration ] ) ; 

Тепер ми закінчили з нашим компонентом Ripple. Давай створимо кнопки.

import React from "react" ;
import "./App.css" ;
import { Ripple } from "./RippleButton" ;
import { Button } from "./RippleButton/styles" ;
function App ( ) { 
  return ( 
    <div className = "App" >
      <header className = "App-header" >
        <Button>
          Learn React
          <Ripple duration = { 850 } color = "#fff" />
        </ Button>
        <Button>
          Learn React
          <Ripple duration = { 1000 } color = "#fff" />
        </ Button>
        <Button>
          Learn React
          <Ripple duration = { 2000 } color = "#fff" />
        </ Button>
      </ Header>
    </ Div>
  ) ;
}
export default App;

Тепер у нас є брижі у всіх відтінках і швидкостях. Більш того, наш компонент  Ripple, якщо він має  overflow: hidden і  position: relative в своїх стилях, може бути повторно використаний практично в будь-якому контейнері.

Джерело перекладу


0 комментариев
Сортировка:
Добавить комментарий