GSAP, ELEMENTOR HOVER ANIMATION, TUTORIAL

Wie du eine Marquee Animation In Elementor mit GSAP erstellst

SHARE

Marquee Examples built in Elementor with GSAP

Dynamische Marquee-Animationen in Elementor Pro mit GSAP

Eine Schritt-für-Schritt-Anleitung zum Erstellen interaktiver, responsiver horizontaler Marquees

Einleitung

In der heutigen digitalen Welt können ansprechende Website-Animationen die Benutzererfahrung erheblich verbessern. Ein besonders auffälliger Effekt ist die unendlich laufende horizontale Marquee-Animation, die mit Elementor Pro, CSS und GSAP erstellt werden kann. In diesem Tutorial zeige ich dir, wie du eine flüssige, interaktive und vollständig responsive Marquee-Animation baust – ideal für Logos, Texte oder andere scrollende Inhalte.

Wichtige Funktionen der Marquee-Animation

Diese Animation bietet eine leistungsstarke und flexible Funktionalität auf allen Geräten:

  • Desktop: Sanftes horizontales Scrollen mit Hover-Interaktion (Animation pausiert und kehrt um)
  • Mobil/Tablet: Touch-optimiert mit Richtungswechsel bei Interaktion
  • Anpassbare Geschwindigkeiten für verschiedene Gerätetypen
  • Vollständig responsives Design, das eine gleichmäßige Animation über alle Bildschirmgrößen hinweg sicherstellt

Grundstruktur in Elementor einrichten

Die Basis der Marquee-Animation besteht aus mehreren wichtigen Elementen:

1. Hauptcontainer einrichten

  • Erstelle einen Container mit voller Breite
  • Setze Overflow auf „hidden“
  • Weise die CSS-Klasse marquee-container zu
  • Setze Margin und Padding auf 0

2. Inhaltscontainer konfigurieren

  • Breite auf 100% setzen, Richtung auf horizontal (Row)
  • Zentrierte Ausrichtung aktivieren
  • Kein Wrapping aktivieren
  • CSS-Klasse marquee-content hinzufügen

Die Marquee-Elemente aufbauen

Die Marquee nutzt das Icon-Box-Widget, kann aber auch mit Bild- und Überschriften-Widgets verwendet werden. Wichtige Schritte:

  • Unnötige Text-Elemente entfernen
  • Titel-Tags auf <div> setzen
  • Position und Abstand des Icons anpassen
  • Responsive Größen für verschiedene Geräte einstellen
  • CSS-Klasse marquee-item hinzufügen

Anpassungen und Responsivität

Die Animation kann individuell angepasst werden:

  • Geschwindigkeit für Desktop, Tablet und Mobilgeräte
  • Richtungssteuerung (von links nach rechts oder umgekehrt)
  • Breakpoint-Management, um sich an Elementor-Einstellungen anzupassen
  • Farbschemata und Abstände anpassen

GSAP-Integration

GSAP (GreenSock Animation Platform) sorgt für die flüssige Animation:

  • Individuell einstellbare Geschwindigkeit für jedes Gerät
  • Breakpoint-Management für responsive Animationen
  • Richtungssteuerung mit einfacher Parameter-Anpassung
  • Interaktive Steuerung für Pause und Umkehrung der Animation

Erweiterte Funktionen und wichtige Überlegungen

Beim Implementieren der Marquee-Animation solltest du Folgendes beachten:

  • Mehrere Marquees benötigen verschiedene Variablen-Sets
  • Breakpoints sollten mit den Elementor-Einstellungen übereinstimmen
  • Die Geschwindigkeit kann einfach über Zahlenwerte angepasst werden
  • Touch-Interaktionen erfordern eine spezielle Konfiguration für mobile Geräte

Best Practices & Tipps für optimale Performance

  • Dupliziere Elemente, um die gesamte Viewport-Breite + zusätzliche Elemente abzudecken
  • Sorge für gleichmäßige Abstände zwischen den Elementen
  • Passe die Zeilenhöhe an, um eine korrekte Ausrichtung der Icons zu gewährleisten
  • Teste gründlich auf verschiedenen Geräten und Bildschirmgrößen

Fazit

Diese Marquee-Animation ist eine professionelle, interaktive Ergänzung, die deine Website ansprechender macht und gleichzeitig voll responsiv bleibt. Die Kombination aus Elementor Pro’s Flexibilität und GSAP’s Animationstechnologie ermöglicht eine flüssige, individuell anpassbare Scroll-Animation, die für verschiedene Anwendungen geeignet ist.

Das Beste daran? Diese Lösung ist extrem vielseitig – egal, ob du Kundenlogos präsentierst, wichtige Nachrichten hervorhebst oder Produktinformationen zeigst, die Marquee-Animation kann perfekt an deine Bedürfnisse angepasst werden und sieht auf allen Geräten professionell aus.

Indem du diese Schritte befolgst und die Parameter an deine Website anpasst, kannst du eine interaktive, visuell ansprechende Marquee erstellen, die das Nutzererlebnis verbessert. 🚀

Wichtige Ergänzungen:

    • Verwende keine clamp()-Funktion für Größen, wenn du die Marquees erstellst. Nutze stattdessen Pixel oder REM – keine % oder vw, da das die Animation zerstören kann.

    • Der knifflige Teil ist immer die Berechnung der Breite aller Elemente, besonders wenn du verschiedene Widgets wie Bild- und Text-Widgets kombinierst.

    • Mit GSAP Match Media wird für jeden Breakpoint die Größe individuell berechnet.

Falls du NUR das Bild-Widget nutzt:

    • Dann ist das CSS etwas anders. Falls du daran interessiert bist, lass es mich wissen – dann kann ich ein separates Video dazu machen. Oder du probierst es selbst aus. 🙂

Zwei Marquees auf einer Seite (wie in meiner Demo):

Grundsätzlich ist dieser Code für ein Marquee auf der Seite optimiert. Ich habe aber dennoch zusätzlichen Code für eine zweite Marquee Animation unten eingefügt.
Falls es eine generelle Anforderung wäre, zwei Marquees auf einer Seite zu haben, müsste der Code dafür optimiert werden. Das war aber nicht der Fokus dieser Demo – ich fand nur, dass es cool aussieht. 😃

So erstellst du das zweite Marquee:

Ändere einfach die Klassen in den Widgets wie folgt:

    • Marquee Container: CSS-Klasse → marquee-container2

    • Marquee Content Container: CSS-Klasse → marquee-content2

    • Icon-Box-Widget: CSS-Klasse → marquee-item2

Dann füge den CSS- und GSAP-Code ein, den du weiter unten unter dem Titel „Second Marquee“ findest.

GSAP Code

Füge den Code in das HTML Widget

<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js"></script>

<script>

function horizontalLoop(items, config) {
  items = gsap.utils.toArray(items);
  config = config || {};
  let tl = gsap.timeline({
      repeat: config.repeat,
      paused: config.paused,
      defaults: { ease: "none" },
      onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100),
    }),
    length = items.length,
    startX = items[0].offsetLeft,
    times = [],
    widths = [],
    xPercents = [],
    curIndex = 0,
    pixelsPerSecond = (config.speed || 1) * 100,
    snap = config.snap === false ? (v) => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural
    totalWidth,
    curX,
    distanceToStart,
    distanceToLoop,
    item,
    i;
  gsap.set(items, {
    // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster.
    xPercent: (i, el) => {
      let w = (widths[i] = parseFloat(gsap.getProperty(el, "width", "px")));
      xPercents[i] = snap(
        (parseFloat(gsap.getProperty(el, "x", "px")) / w) * 100 +
          gsap.getProperty(el, "xPercent")
      );
      return xPercents[i];
    },
  });
  gsap.set(items, { x: 0 });
  totalWidth =
    items[length - 1].offsetLeft +
    (xPercents[length - 1] / 100) * widths[length - 1] -
    startX +
    items[length - 1].offsetWidth *
      gsap.getProperty(items[length - 1], "scaleX") +
    (parseFloat(config.paddingRight) || 0);
  for (i = 0; i < length; i++) {
    item = items[i];
    curX = (xPercents[i] / 100) * widths[i];
    distanceToStart = item.offsetLeft + curX - startX;
    distanceToLoop =
      distanceToStart + widths[i] * gsap.getProperty(item, "scaleX");
    tl.to(
      item,
      {
        xPercent: snap(((curX - distanceToLoop) / widths[i]) * 100),
        duration: distanceToLoop / pixelsPerSecond,
      },
      0
    )
      .fromTo(
        item,
        {
          xPercent: snap(
            ((curX - distanceToLoop + totalWidth) / widths[i]) * 100
          ),
        },
        {
          xPercent: xPercents[i],
          duration:
            (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond,
          immediateRender: false,
        },
        distanceToLoop / pixelsPerSecond
      )
      .add("label" + i, distanceToStart / pixelsPerSecond);
    times[i] = distanceToStart / pixelsPerSecond;
  }
  function toIndex(index, vars) {
    vars = vars || {};
    Math.abs(index - curIndex) > length / 2 &&
      (index += index > curIndex ? -length : length); // always go in the shortest direction
    let newIndex = gsap.utils.wrap(0, length, index),
      time = times[newIndex];
    if (time > tl.time() !== index > curIndex) {
      // if we're wrapping the timeline's playhead, make the proper adjustments
      vars.modifiers = { time: gsap.utils.wrap(0, tl.duration()) };
      time += tl.duration() * (index > curIndex ? 1 : -1);
    }
    curIndex = newIndex;
    vars.overwrite = true;
    return tl.tweenTo(time, vars);
  }
  tl.next = (vars) => toIndex(curIndex + 1, vars);
  tl.previous = (vars) => toIndex(curIndex - 1, vars);
  tl.current = () => curIndex;
  tl.toIndex = (index, vars) => toIndex(index, vars);
  tl.times = times;
  tl.progress(1, true).progress(0, true); // pre-render for performance
  if (config.reversed) {
    tl.vars.onReverseComplete();
    tl.reverse();
  }
  return tl;
}


 let mm = gsap.matchMedia();

mm.add("(min-width: 1025px) and (prefers-reduced-motion: no-preference)", () => restartMarquee(1, 0)); //change here the speed and padding right for desktop//
mm.add("(min-width: 768px) and (max-width: 1024px) and (prefers-reduced-motion: no-preference)", () => restartMarquee(1, 0));//change here the speed and padding right for tablet//
mm.add("(max-width: 767px) and (prefers-reduced-motion: no-preference)", () => restartMarquee(0.8, 0)); //change here the speed and padding right for mobile//

function restartMarquee(speed, paddingRight) {
  // 🎯 Prüfe, ob der Nutzer reduzierte Bewegung bevorzugt
  const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (prefersReducedMotion) {
    console.log("🛑 prefers-reduced-motion erkannt! Marquee wird NICHT gestartet.");
    if (window.loop) {
      window.loop.kill();
      window.loop = null;
    }

    // Alle Animationen der Marquee-Elemente stoppen und statisch setzen
    gsap.killTweensOf(".marquee-item");
    gsap.set(".marquee-item", { clearProps: "all" });

    document.querySelectorAll(".marquee-item").forEach(item => {
      item.style.transform = "translate(0, 0)";
      item.style.animation = "none";
    });

    return; // Beende die Funktion hier, damit keine neue Animation gestartet wird
  }

  if (window.loop) {
    window.loop.kill(); // Stoppe die alte Animation
    window.loop = null;
  }

  let direction = 1;
  const marqueeContainer = document.querySelector(".marquee-container");

  if (marqueeContainer) {
    marqueeContainer.addEventListener("mouseenter", () => {
      if (window.loop) window.loop.pause();
    });

    marqueeContainer.addEventListener("mouseleave", () => {
      if (window.loop) {
        direction *= -1;
        gsap.to(window.loop, { timeScale: direction, overwrite: true });
        window.loop.play();
      }
    });

    marqueeContainer.addEventListener("touchstart", (e) => {
      e.preventDefault();
      if (window.loop) window.loop.pause();
    });

    marqueeContainer.addEventListener("touchend", () => {
      if (window.loop) {
        direction *= -1;
        gsap.to(window.loop, { timeScale: direction, overwrite: true });
        window.loop.play();
      }
    });
  }

  gsap.set(".marquee-item", { clearProps: "all" });

  setTimeout(() => {
    window.loop = horizontalLoop(".marquee-item", {
      speed: speed,  
      repeat: -1,
      paused: false,
      paddingRight: paddingRight,
    });
  }, 100);
}


let resizeTimeout;
window.addEventListener("resize", () => {
  clearTimeout(resizeTimeout);
  resizeTimeout = setTimeout(() => {
    mm.revert(); // Reset MatchMedia animations
    restartMarqueeForCurrentMedia(); // Restart marquee with updated sizes
  }, 200);
});

function restartMarqueeForCurrentMedia() {
  let speed = 1;
  let paddingRight = 0;

  if (window.matchMedia("(min-width: 1024px) and (prefers-reduced-motion: no-preference)").matches) {
    speed = 1;
    paddingRight = 0;
  } else if (window.matchMedia("(min-width: 768px) and (max-width: 1023px) and (prefers-reduced-motion: no-preference)").matches) {
    speed = 1;
    paddingRight = 0;
  } else if (window.matchMedia("(max-width: 767px) and (prefers-reduced-motion: no-preference)").matches) {
    speed = 0.8;
    paddingRight = 0;
  }

  restartMarquee(speed, paddingRight);
}



</script>

CSS Code

Füge diesen Code in dein Custom CSS von WordPress oder Elementor oder in eines deiner Widgets unter Advanced Custom CSS

* {
  box-sizing: border-box;
}

.marquee-container {
  width: 100% !important;
  overflow: hidden !important;
  white-space: nowrap !important;
  position: relative !important;
}

.marquee-content {
  display: flex !important;
}

.marquee-item {
  flex: 0 0 auto !important;
  padding: 0 0 !important;
  max-width: none !important;
  margin: 0 !important;
  justify-content: center !important;
}

/* Desktop and General Settings, will be overwritten for Tablet and Mobile with Media Queries below */


/* Spacing between icon and title (horizontal) */
.marquee-item > .elementor-widget-container > .elementor-icon-box-wrapper > .elementor-icon-box-icon {
   margin-right: 40px !important;
  line-height: 0 !important;
}


/* Tablet Settings */

@media (max-width: 1024px) {
    
    /* 4️⃣ 🛠 Fix for mobile: Removes unnecessary space caused by the Content-Box and aligns icon and heading */
  .marquee-item .elementor-widget-container .elementor-icon-box-wrapper {
    text-align: center !important;
}
 
 /* Spacing between icon and title (horizontal) */
 .marquee-item > .elementor-widget-container > .elementor-icon-box-wrapper > .elementor-icon-box-icon {
   margin-right: 30px !important;
  line-height: 0 !important;
}

}


/* Mobile Settings */

@media (max-width: 767px) {
    
/* 4️⃣ 🛠 Fix for mobile: Removes unnecessary space caused by the Content-Box and aligns icon and heading */
 .marquee-item .elementor-widget-container .elementor-icon-box-wrapper .elementor-icon-box-content {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  
  /* Spacing between icon and title (horizontal) */
 .marquee-item > .elementor-widget-container > .elementor-icon-box-wrapper > .elementor-icon-box-icon {
   margin-right: 30px !important;
  line-height: 0 !important;
}

}

Optional: 2. Marquee auf der gleichen Seite

Hier findet ihr den Code für das zusätzliche Marquee. Dieses ist bereits mit reversed: true voreingestellt, so dass das Marquee zunächst in umgekehrter Richtung wie das erste Marquee startet.

Zweiter Marquee CSS Code für das Custom CSS

* {
  box-sizing: border-box;
}

.marquee-container2 {
  width: 100% !important;
  overflow: hidden !important;
  white-space: nowrap !important;
  position: relative !important;
}

.marquee-content2 {
  display: flex !important;
}

.marquee-item2 {
  flex: 0 0 auto !important;
  padding: 0 0 !important;
  max-width: none !important;
  margin: 0 !important;
  justify-content: center !important;
}

/* Desktop and General Settings, will be overwritten for Tablet and Mobile with Media Queries below */


/* Spacing between icon and title (horizontal) */
.marquee-item2 > .elementor-widget-container > .elementor-icon-box-wrapper > .elementor-icon-box-icon {
   margin-right: 40px !important;
  line-height: 0 !important;
}


/* Tablet Settings */

@media (max-width: 1024px) {
    
    /* 4️⃣ 🛠 Fix for mobile: Removes unnecessary space caused by the Content-Box and aligns icon and heading */
  .marquee-item2 .elementor-widget-container .elementor-icon-box-wrapper {
    text-align: center !important;
}
 
 /* Spacing between icon and title (horizontal) */
 .marquee-item2 > .elementor-widget-container > .elementor-icon-box-wrapper > .elementor-icon-box-icon {
   margin-right: 30px !important;
  line-height: 0 !important;
}

}


/* Mobile Settings */

@media (max-width: 767px) {
    
/* 4️⃣ 🛠 Fix for mobile: Removes unnecessary space caused by the Content-Box and aligns icon and heading */
 .marquee-item2 .elementor-widget-container .elementor-icon-box-wrapper .elementor-icon-box-content {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  
  /* Spacing between icon and title (horizontal) */
 .marquee-item2 > .elementor-widget-container > .elementor-icon-box-wrapper > .elementor-icon-box-icon {
   margin-right: 30px !important;
  line-height: 0 !important;
}

}

Zweiter Marquee: GSAP Code for HTML Widget

<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js"></script>

<script>

function horizontalLoop(items, config) {
  items = gsap.utils.toArray(items);
  config = config || {};
  let tl = gsap.timeline({
      repeat: config.repeat,
      paused: config.paused,
      defaults: { ease: "none" },
      onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100),
    }),
    length = items.length,
    startX = items[0].offsetLeft,
    times = [],
    widths = [],
    xPercents = [],
    curIndex = 0,
    pixelsPerSecond = (config.speed || 1) * 100,
    snap = config.snap === false ? (v) => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural
    totalWidth,
    curX,
    distanceToStart,
    distanceToLoop,
    item,
    i;
  gsap.set(items, {
    // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster.
    xPercent: (i, el) => {
      let w = (widths[i] = parseFloat(gsap.getProperty(el, "width", "px")));
      xPercents[i] = snap(
        (parseFloat(gsap.getProperty(el, "x", "px")) / w) * 100 +
          gsap.getProperty(el, "xPercent")
      );
      return xPercents[i];
    },
  });
  gsap.set(items, { x: 0 });
  totalWidth =
    items[length - 1].offsetLeft +
    (xPercents[length - 1] / 100) * widths[length - 1] -
    startX +
    items[length - 1].offsetWidth *
      gsap.getProperty(items[length - 1], "scaleX") +
    (parseFloat(config.paddingRight) || 0);
  for (i = 0; i < length; i++) {
    item = items[i];
    curX = (xPercents[i] / 100) * widths[i];
    distanceToStart = item.offsetLeft + curX - startX;
    distanceToLoop =
      distanceToStart + widths[i] * gsap.getProperty(item, "scaleX");
    tl.to(
      item,
      {
        xPercent: snap(((curX - distanceToLoop) / widths[i]) * 100),
        duration: distanceToLoop / pixelsPerSecond,
      },
      0
    )
      .fromTo(
        item,
        {
          xPercent: snap(
            ((curX - distanceToLoop + totalWidth) / widths[i]) * 100
          ),
        },
        {
          xPercent: xPercents[i],
          duration:
            (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond,
          immediateRender: false,
        },
        distanceToLoop / pixelsPerSecond
      )
      .add("label" + i, distanceToStart / pixelsPerSecond);
    times[i] = distanceToStart / pixelsPerSecond;
  }
  function toIndex(index, vars) {
    vars = vars || {};
    Math.abs(index - curIndex) > length / 2 &&
      (index += index > curIndex ? -length : length); // always go in the shortest direction
    let newIndex = gsap.utils.wrap(0, length, index),
      time = times[newIndex];
    if (time > tl.time() !== index > curIndex) {
      // if we're wrapping the timeline's playhead, make the proper adjustments
      vars.modifiers = { time: gsap.utils.wrap(0, tl.duration()) };
      time += tl.duration() * (index > curIndex ? 1 : -1);
    }
    curIndex = newIndex;
    vars.overwrite = true;
    return tl.tweenTo(time, vars);
  }
  tl.next = (vars) => toIndex(curIndex + 1, vars);
  tl.previous = (vars) => toIndex(curIndex - 1, vars);
  tl.current = () => curIndex;
  tl.toIndex = (index, vars) => toIndex(index, vars);
  tl.times = times;
  tl.progress(1, true).progress(0, true); // pre-render for performance
  if (config.reversed) {
    tl.vars.onReverseComplete();
    tl.reverse();
  }
  return tl;
}


 let mm2 = gsap.matchMedia();

mm2.add("(min-width: 1024px) and (prefers-reduced-motion: no-preference)", () => restartMarquee2(1, 0));
mm2.add("(min-width: 768px) and (max-width: 1023px) and (prefers-reduced-motion: no-preference)", () => restartMarquee2(1, 0));
mm2.add("(max-width: 767px) and (prefers-reduced-motion: no-preference)", () => restartMarquee2(0.8, 0));

function restartMarquee2(speed2, paddingRight2) {
  // 🎯 Prüfe, ob der Nutzer reduzierte Bewegung bevorzugt
  const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (prefersReducedMotion) {
    console.log("🛑 prefers-reduced-motion erkannt! Marquee wird NICHT gestartet.");
    if (window.loop2) {
      window.loop2.kill();
      window.loop2 = null;
    }

    // Alle Animationen der Marquee-Elemente stoppen und statisch setzen
    gsap.killTweensOf(".marquee-item2");
    gsap.set(".marquee-item2", { clearProps: "all" });

    document.querySelectorAll(".marquee-item2").forEach(item => {
      item.style.transform = "translate(0, 0)";
      item.style.animation = "none";
    });

    return; // Beende die Funktion hier, damit keine neue Animation gestartet wird
  }

  if (window.loop2) {
    window.loop2.kill(); // Stoppe die alte Animation
    window.loop2 = null;
  }

  let direction = 1;
  const marqueeContainer2 = document.querySelector(".marquee-container2");

  if (marqueeContainer2) {
    marqueeContainer2.addEventListener("mouseenter", () => {
      if (window.loop2) window.loop2.pause();
    });

    marqueeContainer2.addEventListener("mouseleave", () => {
      if (window.loop2) {
        direction *= -1;
        gsap.to(window.loop2, { timeScale: direction, overwrite: true });
        window.loop2.play();
      }
    });

    marqueeContainer2.addEventListener("touchstart", (e) => {
      e.preventDefault();
      if (window.loop2) window.loop2.pause();
    });

    marqueeContainer2.addEventListener("touchend", () => {
      if (window.loop2) {
        direction *= -1;
        gsap.to(window.loop2, { timeScale: direction, overwrite: true });
        window.loop2.play();
      }
    });
  }



  gsap.set(".marquee-item2", { clearProps: "all" });

  setTimeout(() => {
    window.loop2 = horizontalLoop(".marquee-item2", {
      speed: speed2,
      repeat: -1,
      paused: false,
      reversed: true,
      paddingRight: paddingRight2,
    });
  }, 100);
}


let resizeTimeout2;
window.addEventListener("resize", () => {
  clearTimeout(resizeTimeout2);
  resizeTimeout2 = setTimeout(() => {
    mm2.revert(); // Reset MatchMedia animations
    restartMarqueeForCurrentMedia2(); // Restart marquee with updated sizes
  }, 200);
});

function restartMarqueeForCurrentMedia2() {
  let speed2 = 1;
  let paddingRight2 = 0;

  if (window.matchMedia("(min-width: 1024px) and (prefers-reduced-motion: no-preference)").matches) {
    speed = 1;
    paddingRight = 0;
  } else if (window.matchMedia("(min-width: 768px) and (max-width: 1023px) and (prefers-reduced-motion: no-preference)").matches) {
    speed = 1;
    paddingRight = 0;
  } else if (window.matchMedia("(max-width: 767px) and (prefers-reduced-motion: no-preference)").matches) {
    speed = 0.8;
    paddingRight = 0;
  }

  restartMarquee2(speed2, paddingRight2);
}



</script>

DSGVO Cookie Consent mit Real Cookie Banner