PageSourceSearch

https://redhilltactical.com/wp-content/plugins/super-speedy-search/assets/js/bodyscrolllock.js?ver=4.90

js redhilltactical.com collected 2026-10-04 07:37:41 UTC 8,729 bytes, 254 lines download raw bytes

1// Adopted and modified solution from Bohdan Didukh (2017)
2// https://stackoverflow.com/questions/41594997/ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-posi
3
4(function(window, document) {
5    // Older browsers don't support event options, feature detect it.
6    let hasPassiveEvents = false;
7    if (typeof window !== 'undefined') {
8      const passiveTestOptions = {
9        get passive() {
10          hasPassiveEvents = true;
11          return undefined;
12        },
13      };
14      window.addEventListener('testPassive', null, passiveTestOptions);
15      window.removeEventListener('testPassive', null, passiveTestOptions);
16    }
17  
18    const isIosDevice =
19      typeof window !== 'undefined' &&
20      window.navigator &&
21      window.navigator.platform &&
22      (/iP(ad|hone|od)/.test(window.navigator.platform) ||
23        (window.navigator.platform === 'MacIntel' && window.navigator.maxTouchPoints > 1));
24  
25    let locks = [];
26    let documentListenerAdded = false;
27    let initialClientY = -1;
28    let previousBodyOverflowSetting;
29    let previousBodyPosition;
30    let previousBodyPaddingRight;
31  
32    // Lighter version of helper functions
33    const preventDefault = function(rawEvent) {
34      const e = rawEvent || window.event;
35      if (e.preventDefault) e.preventDefault();
36      return false;
37    };
38  
39    const setOverflowHidden = function(options) {
40      // If previousBodyPaddingRight is already set, don't set it again.
41      if (previousBodyPaddingRight === undefined) {
42        const reserveScrollBarGap = !!options && options.reserveScrollBarGap === true;
43        const scrollBarGap = window.innerWidth - document.documentElement.clientWidth;
44  
45        if (reserveScrollBarGap && scrollBarGap > 0) {
46          const computedBodyPaddingRight = parseInt(
47            window.getComputedStyle(document.body).getPropertyValue('padding-right'),
48            10
49          );
50          previousBodyPaddingRight = document.body.style.paddingRight;
51          document.body.style.paddingRight = `${computedBodyPaddingRight + scrollBarGap}px`;
52        }
53      }
54  
55      // If previousBodyOverflowSetting is already set, don't set it again.
56      if (previousBodyOverflowSetting === undefined) {
57        previousBodyOverflowSetting = document.body.style.overflow;
58        document.body.style.overflow = 'hidden';
59      }
60    };
61  
62    const restoreOverflowSetting = function() {
63      if (previousBodyPaddingRight !== undefined) {
64        document.body.style.paddingRight = previousBodyPaddingRight;
65        previousBodyPaddingRight = undefined;
66      }
67  
68      if (previousBodyOverflowSetting !== undefined) {
69        document.body.style.overflow = previousBodyOverflowSetting;
70        previousBodyOverflowSetting = undefined;
71      }
72    };
73  
74    const setPositionFixed = function() {
75      return window.requestAnimationFrame(function() {
76        // If previousBodyPosition is already set, don't set it again.
77        if (previousBodyPosition === undefined) {
78          previousBodyPosition = {
79            position: document.body.style.position,
80            top: document.body.style.top,
81            left: document.body.style.left
82          };
83  
84          // Update the dom inside an animation frame
85          const { scrollY, scrollX, innerHeight } = window;
86          document.body.style.position = 'fixed';
87          document.body.style.top = `${-scrollY}px`;
88          document.body.style.left = `${-scrollX}px`;
89  
90          setTimeout(function() {
91            window.requestAnimationFrame(function() {
92              // Attempt to check if the bottom bar appeared due to the position change
93              const bottomBarHeight = innerHeight - window.innerHeight;
94              if (bottomBarHeight && scrollY >= innerHeight) {
95                // Move the content further up so that the bottom bar doesn't hide it
96                document.body.style.top = `${-(scrollY + bottomBarHeight)}px`;
97              }
98            });
99          }, 300);
100        }
101      });
102    };
103  
104    const restorePositionSetting = function() {
105      if (previousBodyPosition !== undefined) {
106        // Convert the position from "px" to Int
107        const y = -parseInt(document.body.style.top, 10);
108        const x = -parseInt(document.body.style.left, 10);
109  
110        // Restore styles
111        document.body.style.position = previousBodyPosition.position;
112        document.body.style.top = previousBodyPosition.top;
113        document.body.style.left = previousBodyPosition.left;
114  
115        // Restore scroll
116        window.scrollTo(x, y);
117  
118        previousBodyPosition = undefined;
119      }
120    };
121  
122    // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
123    const isTargetElementTotallyScrolled = function(targetElement) {
124      return targetElement ? targetElement.scrollHeight - targetElement.scrollTop <= targetElement.clientHeight : false;
125    };
126  
127    const handleScroll = function(event, targetElement) {
128      const clientY = event.targetTouches[0].clientY - initialClientY;
129  
130      if (targetElement && targetElement.scrollTop === 0 && clientY > 0) {
131        // element is at the top of its scroll.
132        return preventDefault(event);
133      }
134  
135      if (isTargetElementTotallyScrolled(targetElement) && clientY < 0) {
136        // element is at the bottom of its scroll.
137        return preventDefault(event);
138      }
139  
140      event.stopPropagation();
141      return true;
142    };
143  
144    const disableBodyScroll = function(targetElement, options) {
145      // targetElement must be provided
146      if (!targetElement) {
147        console.error(
148          'disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.'
149        );
150        return;
151      }
152  
153      // disableBodyScroll must not have been called on this targetElement before
154      if (locks.some(lock => lock.targetElement === targetElement)) {
155        return;
156      }
157  
158      const lock = {
159        targetElement,
160        options: options || {}
161      };
162  
163      locks = [].concat(locks, [lock]);
164  
165      if (isIosDevice) {
166        setPositionFixed();
167      } else {
168        setOverflowHidden(options);
169      }
170  
171      if (isIosDevice) {
172        targetElement.ontouchstart = function(event) {
173          if (event.targetTouches.length === 1) {
174            // detect single touch.
175            initialClientY = event.targetTouches[0].clientY;
176          }
177        };
178        targetElement.ontouchmove = function(event) {
179          if (event.targetTouches.length === 1) {
180            // detect single touch.
181            handleScroll(event, targetElement);
182          }
183        };
184  
185        if (!documentListenerAdded) {
186          document.addEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined);
187          documentListenerAdded = true;
188        }
189      }
190    };
191  
192    const clearAllBodyScrollLocks = function() {
193      if (isIosDevice) {
194        // Clear all locks ontouchstart/ontouchmove handlers, and the references.
195        locks.forEach(function(lock) {
196          lock.targetElement.ontouchstart = null;
197          lock.targetElement.ontouchmove = null;
198        });
199  
200        if (documentListenerAdded) {
201          document.removeEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined);
202          documentListenerAdded = false;
203        }
204  
205        // Reset initial clientY.
206        initialClientY = -1;
207      }
208  
209      if (isIosDevice) {
210        restorePositionSetting();
211      } else {
212        restoreOverflowSetting();
213      }
214  
215      locks = [];
216    };
217  
218    const enableBodyScroll = function(targetElement) {
219      if (!targetElement) {
220        console.error(
221          'enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.'
222        );
223        return;
224      }
225  
226      locks = locks.filter(function(lock) {
227        return lock.targetElement !== targetElement;
228      });
229  
230      if (isIosDevice) {
231        targetElement.ontouchstart = null;
232        targetElement.ontouchmove = null;
233  
234        if (documentListenerAdded && locks.length === 0) {
235          document.removeEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined);
236          documentListenerAdded = false;
237        }
238      }
239  
240      if (isIosDevice) {
241        restorePositionSetting();
242      } else {
243        restoreOverflowSetting();
244      }
245    };
246  
247    // Expose the functions globally
248    window.bodyScrollLock = {
249      disableBodyScroll: disableBodyScroll,
250      enableBodyScroll: enableBodyScroll,
251      clearAllBodyScrollLocks: clearAllBodyScrollLocks
252    };
253  
254  })(window, document);

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.