Skip to content

Instantly share code, notes, and snippets.

@LeoSeyers
Last active March 17, 2023 17:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LeoSeyers/4e5b94d7a22ce87cc28065992ae312f6 to your computer and use it in GitHub Desktop.
Save LeoSeyers/4e5b94d7a22ce87cc28065992ae312f6 to your computer and use it in GitHub Desktop.
Marquee Component
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(var(--offset));
}
}
.c-marquee {
--scrolling-speed: 2s;
--offset: -50%;
display: flex;
width: 100%;
overflow: hidden;
@include max-width(tab-lg) {
align-items: center;
height: var(--c-header);
}
span {
position: relative;
width: fit-content;
padding-right: var(--padding);
white-space: nowrap;
}
&:not(.is-enabled) span {
animation: marquee var(--scrolling-speed) linear infinite;
}
&__inner {
position: relative;
display: flex;
align-items: center;
width: fit-content;
transition: opacity 1s;
will-change: transform;
span {
width: fit-content;
white-space: nowrap;
&::after {
margin-left: var(--padding);
content: "· ";
}
}
}
}
@LeoSeyers
Copy link
Author

LeoSeyers commented Mar 1, 2023

import gsap from 'gsap'

class Marquee {
  constructor() {
    this.marquees = document.querySelectorAll('.c-marquee')

    this.marquees.forEach((marquee) => {
      marquee.classList.add('is-enabled')
      const timelines = []

      const inner = marquee.querySelector('.c-marquee__inner')
      if (inner.offsetWidth < marquee.offsetWidth * 2) {
        const multiplier = Math.ceil(marquee.offsetWidth / inner.offsetWidth) * 2
        const children = [...inner.children]

        for (let i = 0; i < multiplier; i += 1) {
          const clones = []
          children.forEach((child) => {
            const clone = child.cloneNode(true)
            clones.push(clone)
          })
          inner.append(...clones)
        }
      }

      const spans = marquee.querySelectorAll('.c-marquee__inner span')
      const duration = inner.offsetWidth / 120

      spans.forEach((span, i) => {
        const { offsetLeft, offsetWidth } = span
        timelines[i] = gsap.timeline()
          .to(span, {
            duration,
            ease: 'none',
            x: () => `-=${inner.offsetWidth}px`,
            modifiers: {
              x(x) {
                const wrap = gsap.utils.unitize(
                  gsap.utils.wrap(
                    0 - offsetLeft - offsetWidth,
                    inner.offsetWidth - offsetLeft - offsetWidth,
                  ),
                  'px',
                )
                return wrap(x)
              },
            },
            repeat: -1,
          })
      })

      marquee.addEventListener('mouseenter', () => {
        timelines.forEach((timeline) => {
          timeline.timeScale(0.25)
        })
      })

      marquee.addEventListener('mouseleave', () => {
        timelines.forEach((timeline) => {
          timeline.timeScale(1)
        })
      })
    })
  }
}

export default Marquee

@LeoSeyers
Copy link
Author

LeoSeyers commented Mar 1, 2023

<div class="c-marquee">
        <div class="c-marquee__inner">
            <span></span>
            <span></span>
            <span></span>
          </div>
    </div>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment