PageSourceSearch

https://www.appliedmldays.org/assets/controllers/marquee_controller-669254c2.js

js appliedmldays.org collected 2026-09-24 18:34:30 UTC 3,103 bytes, 101 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2
3// Continuously scrolls its track horizontally for a seamless logo marquee.
4// Items are duplicated once so the loop has no visible seam, and the track
5// bleeds to the viewport edges. Pauses on hover/focus. When all items already
6// fit within the viewport, the track is simply centered and does not move.
7// Honors prefers-reduced-motion by falling back to a manually scrollable strip.
8export default class extends Controller {
9  static targets = ["track"];
10  static values = { speed: { type: Number, default: 40 } }; // px per second
11
12  connect() {
13    this.#setup();
14    this.onResize = this.#debounce(() => this.#reset(), 150);
15    window.addEventListener("resize", this.onResize);
16  }
17
18  disconnect() {
19    window.removeEventListener("resize", this.onResize);
20    this.#stop();
21  }
22
23  pause() {
24    this.paused = true;
25  }
26
27  resume() {
28    this.paused = false;
29  }
30
31  #setup() {
32    // Everything fits: center the row and leave it still.
33    if (this.trackTarget.scrollWidth <= this.element.clientWidth) {
34      this.element.classList.remove("overflow-hidden", "overflow-x-auto");
35      this.trackTarget.classList.remove("w-max");
36      this.trackTarget.classList.add("w-full", "justify-center");
37      return;
38    }
39
40    // Reduced motion: let the user scroll the overflow manually.
41    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
42      this.element.classList.remove("overflow-hidden");
43      this.element.classList.add("overflow-x-auto");
44      return;
45    }
46
47    this.#duplicateItems();
48    this.offset = 0;
49    this.paused = false;
50    this.lastTime = null;
51    this.frame = requestAnimationFrame(this.#step);
52  }
53
54  #reset() {
55    this.#stop();
56    this.element.classList.add("overflow-hidden");
57    this.element.classList.remove("overflow-x-auto");
58    this.trackTarget.classList.add("w-max");
59    this.trackTarget.classList.remove("w-full", "justify-center");
60    this.trackTarget.style.transform = "";
61    this.trackTarget.querySelectorAll("[data-marquee-clone]").forEach((clone) => clone.remove());
62    this.#setup();
63  }
64
65  #stop() {
66    if (this.frame) cancelAnimationFrame(this.frame);
67    this.frame = null;
68  }
69
70  #duplicateItems() {
71    Array.from(this.trackTarget.children).forEach((item) => {
72      const clone = item.cloneNode(true);
73      clone.setAttribute("aria-hidden", "true");
74      clone.setAttribute("data-marquee-clone", "");
75      this.trackTarget.appendChild(clone);
76    });
77  }
78
79  #step = (time) => {
80    if (this.lastTime === null) this.lastTime = time;
81    const delta = (time - this.lastTime) / 1000;
82    this.lastTime = time;
83
84    if (!this.paused) {
85      this.offset += this.speedValue * delta;
86      const half = this.trackTarget.scrollWidth / 2;
87      if (half > 0 && this.offset >= half) this.offset -= half;
88      this.trackTarget.style.transform = `translateX(${-this.offset}px)`;
89    }
90
91    this.frame = requestAnimationFrame(this.#step);
92  };
93
94  #debounce(fn, wait) {
95    let timer;
96    return () => {
97      clearTimeout(timer);
98      timer = setTimeout(fn, wait);
99    };
100  }
101}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.