summaryrefslogtreecommitdiff
path: root/public/scrollbar.js
blob: 2218b55bdc38a4584618e2053244396e3432e5fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function initScrollbox(box) {
  const area = box.querySelector("[data-scroll-area]");
  const track = box.querySelector("[data-scroll-track]");
  const thumb = box.querySelector("[data-scroll-thumb]");

  const inset = () => parseFloat(getComputedStyle(thumb).left) || 0;

  const update = () => {
    const pad = inset();
    const room = track.clientHeight - pad * 2;
    const maxScroll = area.scrollHeight - area.clientHeight;
    const ratio = Math.min(1, area.clientHeight / area.scrollHeight);
    const height = Math.max(room * ratio, thumb.clientWidth);
    const progress = maxScroll > 0 ? area.scrollTop / maxScroll : 0;
    thumb.style.height = `${height}px`;
    thumb.style.top = `${pad + progress * (room - height)}px`;
  };

  thumb.addEventListener("pointerdown", (e) => {
    e.preventDefault();
    thumb.setPointerCapture(e.pointerId);
    const startY = e.clientY;
    const startTop = parseFloat(thumb.style.top) || 0;

    const move = (ev) => {
      const pad = inset();
      const room = track.clientHeight - pad * 2 - thumb.offsetHeight;
      const top = Math.min(
        Math.max(startTop - pad + ev.clientY - startY, 0),
        room,
      );
      const progress = room > 0 ? top / room : 0;
      area.scrollTop = progress * (area.scrollHeight - area.clientHeight);
    };
    const stop = () => {
      thumb.removeEventListener("pointermove", move);
      thumb.removeEventListener("pointerup", stop);
      thumb.removeEventListener("pointercancel", stop);
    };
    thumb.addEventListener("pointermove", move);
    thumb.addEventListener("pointerup", stop);
    thumb.addEventListener("pointercancel", stop);
  });

  area.addEventListener("scroll", update, { passive: true });
  const observer = new ResizeObserver(update);
  observer.observe(area);
  for (const child of area.children) observer.observe(child);
  update();
}

document.querySelectorAll("[data-scrollbox]").forEach(initScrollbox);

// Vim style j/k scrolling
document.addEventListener("keydown", (e) => {
  // Don't scroll in input elements
  const tag = document.activeElement.tagName;
  if (
    ["INPUT", "TEXTAREA", "SELECT"].includes(tag) ||
    document.activeElement.isContentEditable
  ) {
    return;
  }

  // Only work for "j" and "k", no ctrl/alt/shift
  if (e.ctrlKey || e.altKey || e.metaKey) return;

  const dir = key === "j" ? step : -step;
  const scrollTarget = document.scrollingElement || document.documentElement;
  const scrollAmount = 100; // in pixels
  if (scrollTarget && scrollTarget.scrollHeight > scrollTarget.clientHeight) {
    scrollTarget.scrollBy({ top: dir, behavior: "auto" });
  } else {
    window.scrollBy({ top: dir, behavior: "auto" });
  }
});