GSAP + SplitType + i18n text fails to update correctly after language change
02:11 15 Jul 2025

I'm using GSAP with SplitType and a custom language switcher (en.json, es.json) to translate my website dynamically. Everything translates correctly except the .scroll-fade-text section — which uses SplitType for scroll-triggered word-by-word animation.

On language change, the translated text briefly appears, then reverts back to the original language's content (probably due to SplitType or GSAP misfiring).

What I’ve Tried:

Verified the translation keys load and update correctly via data-i18n

Used requestAnimationFrame() and setTimeout() to delay SplitType initialization

Used .revert() and .kill() to clean up previous animation

document.addEventListener('DOMContentLoaded', () => {
  const langSelect = document.getElementById('langSelect');
  const storedLang = localStorage.getItem('lang') || 'en';
  langSelect.value = storedLang;

  applyLanguage(storedLang);

  langSelect.addEventListener('change', () => {
    const lang = langSelect.value;
    localStorage.setItem('lang', lang);
    applyLanguage(lang);
  });
});

function applyLanguage(lang) {
  fetch(`/lang/${lang}.json`)
    .then(res => res.json())
    .then(data => {
      document.querySelectorAll('[data-i18n]').forEach(el => {
        const key = el.getAttribute('data-i18n');
        const allowHtml = el.getAttribute('data-i18n-html') === 'true';
        if (data[key]) {
          if (allowHtml) {
            el.innerHTML = data[key];
          } else {
            el.textContent = data[key];
          }
        }
      });

      // Attempt delayed animation init
      requestAnimationFrame(() => {
        setTimeout(() => {
          initScrollAnimation();
        }, 100);
      });
    });
}

let scrollSplit;

function initScrollAnimation() {
  if (scrollSplit) scrollSplit.revert();

  ScrollTrigger.getAll().forEach(trigger => trigger.kill());
  gsap.killTweensOf("*");

  scrollSplit = new SplitType(".scroll-fade-text", {
    types: "words",
    wordClass: "word"
  });

  gsap.to(scrollSplit.words, {
    color: "white",
    stagger: 0.3,
    scrollTrigger: {
      trigger: ".scroll-lock-section",
      start: "top top",
      end: "+=1000",
      scrub: true,
      pin: true,
      invalidateOnRefresh: true
    }
  });
}
.scroll-lock-section {
  height: 85vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 20px;
  background-color: #0c1624;
}

.scroll-text-wrap {
  font-size: 48px;
  font-family: 'Manrope', sans-serif;
  font-weight: bold;
  max-width: 1000px;
  text-align: center;
  line-height: 1.5;
  margin-bottom: -150px;
}

.scroll-text-wrap .word {
  color: gray;
  transition: color 0.4s ease;
}
MY LONG TEXT IN ENGLISH HERE.

When I switch the language from English to Spanish:

All other data-i18n elements update correctly.

scroll-fade-text briefly changes, then reverts back to English.

The GSAP animation continues working, but with the old content.

How can I properly re-split and animate translated content inside .scroll-fade-text using SplitType + GSAP without it resetting or reverting?

All translation is client-side using data-i18n and a JSON map.

Working example...

HTML

Contact us

JSON

en.json

  "navContact": "Contact us",
/* more below */

es.json

  "navContact": "Contáctanos",
/* more below */
javascript html jquery css gsap