Stagger Button

Step 1: Create | Choose your button and adapt attributes.

Choose and adapt the buttons as you like.
Stagger Button
anm-direction: up
anm-reverse: true
anm-stagger: 0.0075
anm-ease: expo.inOut
anm-custom: opacity: 0.5
anm-duration: 1
Stagger Button
anm-direction: up
anm-reverse: false
Stagger Button
anm-direction: down
anm-reverse: true
Stagger Button
anm-direction: down
anm-reverse: true

Step 2: Copy and paste Javascript code

Copy this javascript code and insert before the </body> tag of your project.
 
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/split-type@0.3.4/umd/index.min.js"></script> 


<script>
function buttonStaggerAnimation() {
  const buttons = document.querySelectorAll('[anm-stagger-btn=wrap]')

  buttons.forEach(button => {
    const text = button.querySelector('[anm-stagger-btn=text]')
    const direction = button.getAttribute('anm-direction')
    const isReverse = button.getAttribute('anm-reverse')
    const stagger = button.getAttribute('anm-stagger') || 0.0075
    const delay = button.getAttribute('anm-delay') || 0
    const duration = button.getAttribute('anm-duration') || 0.5
    const ease = button.getAttribute('anm-ease') || 'power3.inOut'
    const custom = button.getAttribute('anm-custom') || ''

    const parseCustomAttribute = attr => {
      const props = {}
      if (attr) {
        attr.split(',').forEach(pair => {
          const [key, value] = pair.split(':').map(item => item.trim())
          if (key && value) {
            props[key] = value
          }
        })
      }
      return props
    }

    const transformValuesForToState = (element, props) => {
      const transformedValues = {}
      const computedStyles = window.getComputedStyle(element)
      for (const key in props) {
        let value = props[key]
        if (key === 'opacity') {
          transformedValues[key] = '1'
        } else {
          transformedValues[key] = value.replace(/(\d+(\.\d+)?)/g, match => {
            const unitMatch = match.match(/(\d+(\.\d+)?)(px|rem|em|%|vh|vw|dvh|dvw|deg|rad|grad|turn|cvw|cvh)?/)
            return unitMatch ? `0${unitMatch[3] || ''}` : '0'
          })

          if (!/\d/.test(value)) {
            transformedValues[key] = computedStyles[key] || value
          }
        }
      }
      return transformedValues
    }

    const animationProps = parseCustomAttribute(custom)
    const toStateProps = transformValuesForToState(text, animationProps)

    const textClone = text.cloneNode(true)
    textClone.style.position = 'absolute'
    text.after(textClone)

    const textSplit = new SplitType(text, { types: 'chars' })
    const clonedSplit = new SplitType(textClone, { types: 'chars' })

    const timeline = gsap.timeline({
      defaults: { ease: ease, delay: delay, duration: duration, stagger: stagger },
      paused: true,
    })

    if (direction === 'up') {
      textClone.style.top = '100%'
      timeline
        .fromTo(textSplit.chars, { yPercent: 0, ...animationProps }, { yPercent: -100, ...toStateProps })
        .fromTo(clonedSplit.chars, { yPercent: 0, ...animationProps }, { yPercent: -100, ...toStateProps }, '<')
    } else if (direction === 'down') {
      textClone.style.top = '-100%'
      timeline
        .fromTo(textSplit.chars, { yPercent: 0, ...animationProps }, { yPercent: 100, ...toStateProps })
        .fromTo(clonedSplit.chars, { yPercent: 0, ...animationProps }, { yPercent: 100, ...toStateProps }, '<')
    }

    button.addEventListener('mouseenter', () => {
      timeline.restart()
    })

    button.addEventListener('mouseleave', () => {
      if (isReverse === 'true') {
        timeline.reverse()
      } else {
        return
      }
    })
  })
}

buttonStaggerAnimation()

  
</script> 

Step 3: Include or adapt attributes

Add, delete or edit attributes as needed. You can find an overview of all attributes related to this animation here:
Selector attributes
All selector attributes are mandatory and need to be present for the animation to work.
Name (This element wraps the entire animation.)
Name tooltip goes here
anm-stagger-btn
Value
wrap
Name (Add this to the text element of the animation.)
Name tooltip goes here
anm-stagger-btn
Value
text
Configurable attributes
Add this to the anm-stagger-btn=wrap element.
Name (Defines the direction of the animation.)
Name tooltip goes here
anm-direction
Value (Options: up, down
Value tooltip goes here
up
Add this to the anm-stagger-btn=wrap element.
Name (Defines if the animation should reverse on hover)
Name tooltip goes here
anm-reverse
Value (Options: true, false.)
Value tooltip goes here
false
Add this to the anm-stagger-btn=wrap element.
Name (Defines the duration of the animation.)
Name tooltip goes here
anm-duration
Value (A value in seconds (e.g., 1) that controls the duration of the animation.)
Value tooltip goes here
1
Add this to the anm-stagger-btn=wrap element.
Name (Defines the ease of the animation.)
Name tooltip goes here
anm-ease
Value (Use a valid GSAP ease string (e.g., power2.inOut, power1.inOut, linear. Learn more here)
Value tooltip goes here
1
Add this to the anm-stagger-btn=wrap element.
Name (Defines the delay of the animation.)
Name tooltip goes here
anm-delay
Value (A value in seconds (e.g., 0) that controls the delay of the animation.)
Value tooltip goes here
0
Add this to the anm-stagger-btn=wrap element.
Name (Defines the delay of the animation.)
Name tooltip goes here
anm-custom
Value (A custom animation gsap string (e.g., 'scale: 1.2' or 'rotate: 360', etc.))
Value tooltip goes here
opacity: 0.5
There are no configurable attributes