• LeBron James

  • Tim Duncan

  • Stephen Curry

  • Kareem Abdul-Jabbar

  • Kevin Durant

  • Giannis Antetokounmpo

  • Luka Dončić

  • Michael Jordan

🔁 Los Mejores Jugadores del Momento ¡en Loop! 🏀

Este es un interesante efecto de «looping words» utilizando GSAP (GreenSock Animation Platform) para un listado de jugadores de la NBA.
 
La animación simula un desplazamiento continuo de palabras (nombres de jugadores en este caso) hacia arriba, dando un efecto de presentación cíclica que podría usarse de forma atractiva en muchos proyectos

HTML+JS

				
					<section class="cloneable">
    <div class="looping-words">
      <div class="looping-words__containers">
        <ul data-looping-words-list="" class="looping-words__list">
<li class="nba-intro__item"><p class="nba-intro__name">LeBron James</p></li>
<li class="nba-intro__item"><p class="nba-intro__name">Stephen Curry</p></li>
<li class="nba-intro__item"><p class="nba-intro__name">Kevin Durant</p></li>
<li class="nba-intro__item"><p class="nba-intro__name">Giannis Antetokounmpo</p></li>
<li class="nba-intro__item"><p class="nba-intro__name">Luka Dončić</p></li>

        </ul>
      </div>
      <div class="looping-words__fade"></div>
      <div data-looping-words-selector="" class="looping-words__selector">
        <div class="looping-words__edge"></div>
        <div class="looping-words__edge is--2"></div>
        <div class="looping-words__edge is--3"></div>
        <div class="looping-words__edge is--4"></div>
      </div>
    </div>
</section>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script>
    
    // ------- Osmo [https://osmo.supply/] ------- //

document.addEventListener('DOMContentLoaded', function() {
  const wordList = document.querySelector('[data-looping-words-list]');
  const words = Array.from(wordList.children);
  const totalWords = words.length;
  const wordHeight = 100 / totalWords; // Offset as a percentage
  const edgeElement = document.querySelector('[data-looping-words-selector]');
  let currentIndex = 0;
  function updateEdgeWidth() {
    const centerIndex = (currentIndex + 1) % totalWords;
    const centerWord = words[centerIndex];
    const centerWordWidth = centerWord.getBoundingClientRect().width;
    const listWidth = wordList.getBoundingClientRect().width;
    const percentageWidth = (centerWordWidth / listWidth) * 100;
    gsap.to(edgeElement, {
      width: `${percentageWidth}%`,
      duration: 0.5,
      ease: 'Expo.easeOut',
    });
  }
  function moveWords() {
    currentIndex++;
    gsap.to(wordList, {
      yPercent: -wordHeight * currentIndex,
      duration: 1.2,
      ease: 'elastic.out(1, 0.85)',
      onStart: updateEdgeWidth,
      onComplete: function() {
        if (currentIndex >= totalWords - 3) {
          wordList.appendChild(wordList.children[0]);
          currentIndex--;
          gsap.set(wordList, { yPercent: -wordHeight * currentIndex });
          words.push(words.shift());
        }
      }
    });
  }
  updateEdgeWidth();
  gsap.timeline({ repeat: -1, delay: 1 })
    .call(moveWords)
    .to({}, { duration: 2 })
    .repeat(-1);
});
    
</script>
				
			

CSS

				
					/* ------- Osmo [https://osmo.supply/] ------- */
/* Osmo UI: https://slater.app/10324/23333.css */


.cloneable {
  padding:5rem;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  display: flex;
  position: relative;
}

.looping-words {
  height: 2.7em;
  padding-left: .1em;
  padding-right: .1em;
  font-size: 11vw;
  line-height: .9;
  position: relative;
}

.looping-words__list {
  text-align: center;
  text-transform: uppercase;
  white-space: nowrap;
  flex-flow: column;
  align-items: center;
  margin: 0;
  padding: 0;
  font-family: PP Neue Corp, sans-serif;
  font-weight: 700;
  list-style: none;
  display: flex;
  position: relative;
}

.looping-words__list.is--primary {
  color:gray;
}

.looping-words__list.is--gray {
  color:red;
}

.looping-words__fade {
  background-image: linear-gradient(180deg, var(--color-neutral-300) 5%, transparent 40%, transparent 60%, var(--color-neutral-300) 95%);
  pointer-events: none;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

.looping-words__fade.is--radial {
  background-image: radial-gradient(circle closest-side at 50% 50%, transparent 64%, var(--color-neutral-400) 93%);
  width: 140%;
  display: block;
  left: -20%;
}

.looping-words__selector {
  pointer-events: none;
  width: 100%;
  height: .9em;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.looping-words__edge {
  border-top: .035em solid;
  border-left: .035em solid;
  border-color:red;
  width: .125em;
  height: .125em;
  position: absolute;
  top: 0;
  left: 0;
}

.looping-words__edge.is--2 {
  left: auto;
  right: 0;
  transform: rotate(90deg);
}

.looping-words__edge.is--3 {
  inset: auto 0 0 auto;
  transform: rotate(180deg);
}

.looping-words__edge.is--4 {
  top: auto;
  bottom: 0;
  transform: rotate(270deg);
}

.looping-words__containers {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}

.looping-words__p {
  margin: 0;
}

@font-face {
  font-family: 'PP Neue Corp';
  src: url('https://cdn.prod.website-files.com/6717aac16c9ea22eeef1e79e/6717de2d56e40b921572d2d9_PPNeueCorp-TightUltrabold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
				
			

Canvas Web Component

Como desarrollador full stack, notarás que se aprovechan técnicas modernas (como shadow DOM, ResizeObserver y custom elements) para lograr un componente reutilizable y de alto rendimiento.