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.