PageSourceSearch

https://funabashi-keisei.com/js/drawer.js

js funabashi-keisei.com collected 2026-10-08 22:07:01 UTC 4,892 bytes, 180 lines download raw bytes

1(function () {
2  // ボタンと本体
3  const openButton = document.querySelector(".js-openDrawer");
4  const drawer = document.querySelector(".js-drawer");
5  const closeButton = drawer.querySelector(".js-closeDrawer");
6  const backdrop = drawer.querySelector(".js-backdrop");
7
8  const rootElement = document.documentElement;
9  const scrollLockModifier = "drawerOpen";
10
11  const scrollbarFixTargets = document.querySelectorAll(".js-scrollbarFix");
12  let scrollbarFix = false;
13
14  const scrollableTarget = drawer.querySelector(".js-scrollable");
15
16  let touchY = null;
17
18  // 現在の状態(開いていたらtrue)
19  let drawerOpen = false;
20
21  const tabbableElements = drawer.querySelectorAll("a[href], button");
22  const firstTabbable = tabbableElements[0];
23  const lastTabbable = tabbableElements[tabbableElements.length - 1];
24
25  // stateは真偽値
26  function changeAriaExpanded(state) {
27    const value = state ? "true" : "false";
28    drawer.setAttribute("aria-expanded", value);
29    openButton.setAttribute("aria-expanded", value);
30    closeButton.setAttribute("aria-expanded", value);
31  }
32
33  // stateは真偽値
34  function changeState(state) {
35    if (state === drawerOpen) {
36      console.log("2回以上連続で同じ状態に変更しようとしました");
37      return;
38    }
39    changeAriaExpanded(state);
40    drawerOpen = state;
41  }
42
43  function openDrawer() {
44    changeState(true);
45  }
46
47  function closeDrawer() {
48    changeState(false);
49  }
50
51  function onClickOpenButton() {
52    activateScrollLock();
53    openDrawer();
54    firstTabbable.focus();
55  }
56
57  function onClickCloseButton() {
58    closeDrawer();
59  }
60
61  function activateScrollLock() {
62    addScrollbarWidth();
63    rootElement.classList.add(scrollLockModifier);
64  }
65
66  function deactivateScrollLock() {
67    removeScrollbarWidth();
68    rootElement.classList.remove(scrollLockModifier);
69  }
70
71  function onTransitionendDrawer(event) {
72    if (event.target !== drawer || event.propertyName !== "visibility") {
73      return;
74    }
75    if (!drawerOpen) {
76      deactivateScrollLock();
77      openButton.focus();
78    }
79  }
80
81  // valueは文字列
82  function addScrollbarMargin(value) {
83    const targetsLength = scrollbarFixTargets.length;
84    for (let i = 0; i < targetsLength; i++) {
85      scrollbarFixTargets[i].style.marginRight = value;
86    }
87  }
88
89  function addScrollbarWidth() {
90    const scrollbarWidth = window.innerWidth - rootElement.clientWidth;
91    if (!scrollbarWidth) {
92      scrollbarFix = false;
93      return;
94    }
95    const value = scrollbarWidth + "px";
96    addScrollbarMargin(value);
97    scrollbarFix = true;
98  }
99
100  function removeScrollbarWidth() {
101    if (!scrollbarFix) {
102      return;
103    }
104    addScrollbarMargin("");
105  }
106
107  function onTouchStart(event) {
108    if (event.targetTouches.length > 1) {
109      return;
110    }
111    touchY = event.targetTouches[0].clientY;
112  }
113
114  function onTouchMove(event) {
115    if (event.targetTouches.length > 1) {
116      return;
117    }
118    // touchstart時と現在の差分から、スクロール方向を得る
119    // 正:上方向へスクロール
120    // 負:下方向へスクロール
121    const touchMoveDiff = event.targetTouches[0].clientY - touchY;
122
123    if (scrollableTarget.scrollTop === 0 && touchMoveDiff > 0) {
124      event.preventDefault();
125      return;
126    }
127
128    if (targetTotallyScrolled(scrollableTarget) && touchMoveDiff < 0) {
129      event.preventDefault();
130    }
131  }
132
133  function targetTotallyScrolled(element) {
134    return element.scrollHeight - element.scrollTop <= element.clientHeight;
135  }
136
137  function onTouchMoveBackdrop(event) {
138    if (event.targetTouches.length > 1) {
139      return;
140    }
141    event.preventDefault();
142  }
143
144  function onKeydownTabKeyFirstTabbable(event) {
145    if(event.key !== "Tab" || !event.shiftKey) {
146      return;
147    }
148    event.preventDefault();
149    lastTabbable.focus();
150  }
151
152  function onKeydownTabKeyLastTabbable(event) {
153    if(event.key !== "Tab" || event.shiftKey) {
154      return;
155    }
156    event.preventDefault();
157    firstTabbable.focus();
158  }
159
160  function onKeydownEsc(event) {
161    if(!drawerOpen || event.key !== "Escape") {
162      return;
163    }
164    event.preventDefault();
165    closeDrawer();
166  }
167
168  openButton.addEventListener("click", onClickOpenButton, false);
169  closeButton.addEventListener("click", onClickCloseButton, false);
170  backdrop.addEventListener("click", onClickCloseButton, false);
171  drawer.addEventListener("transitionend", onTransitionendDrawer, false);
172
173  firstTabbable.addEventListener("keydown", onKeydownTabKeyFirstTabbable, false);
174  lastTabbable.addEventListener("keydown", onKeydownTabKeyLastTabbable, false);
175  window.addEventListener("keydown", onKeydownEsc, false);
176
177  scrollableTarget.addEventListener("touchstart", onTouchStart, false);
178  scrollableTarget.addEventListener("touchmove", onTouchMove, false);
179  backdrop.addEventListener("touchmove", onTouchMoveBackdrop, false);
180})();

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.