jsx import { jsx } from '@emotion/core' ; import React , { useEffect , useImperativeHandle , useState } from 'react' ; export interface ShakeBoxProps extends React . HTMLProps < HTMLDivElement > { amplitude ? : number ; damping ? : number ; speed ? : number ; threshold ? : number ; } export interface ShakeBoxRef { shake : ( ) => void ; } export const ShakeBox = React . forwardRef < ShakeBoxRef , ShakeBoxProps > ( ( { children , amplitude = 8 , damping = 0.75 , speed = 4 , threshold = 1 } , ref ) => { const [ offset , setOffset ] = useState ( 0 ) ; let requestAnimationId = 0 ; const shakeLoop = ( targetOffset , currentOffset = 0 ) => { if ( targetOffset > 0 && currentOffset < targetOffset ) { currentOffset += speed ; } else if ( targetOffset < 0 && currentOffset > targetOffset ) { currentOffset -= speed ; } else { currentOffset = targetOffset - ( currentOffset - targetOffset ) ; targetOffset *= - damping ; } if ( Math . abs ( targetOffset ) >= threshold ) { requestAnimationId = requestAnimationFrame ( ( ) => shakeLoop ( targetOffset , currentOffset ) ) ; } else { currentOffset = 0 ; } setOffset ( currentOffset ) ; } ; useImperativeHandle ( ref , ( ) => ( { shake : ( ) => { cancelAnimationFrame ( requestAnimationId ) ; shakeLoop ( amplitude ) ; } , } ) ) ; useEffect ( ( ) => ( ) => cancelAnimationFrame ( requestAnimationId ) , [ ] ) ; return < div css = { { transform : `translateX(${ offset }px)` } } > { children } < /div>; } ) ;