tua-body-scroll-lock
Version:
🔐Body scroll locking that just works with everything
7 lines (6 loc) • 4.23 kB
JavaScript
/**
* tua-body-scroll-lock v1.6.3
* (c) 2026 Evinma, BuptStEve
* @license MIT
*/
const t=()=>"undefined"==typeof window,e=t=>{t=t||navigator.userAgent;const e=/(iPad).*OS\s([\d_]+)/.test(t);return{ios:!e&&/(iPhone\sOS)\s([\d_]+)/.test(t)||e,android:/(Android);?[\s/]+([\d.]+)?/.test(t)}};function n(e){if(t())return!1;if(!e)throw new Error("options must be provided");let n=!1;const o={get passive(){n=!0}},i=()=>{},l="__TUA_BSL_TEST_PASSIVE__";window.addEventListener(l,i,o),window.removeEventListener(l,i,o);const{capture:s}=e;return n?e:void 0!==s&&s}function o(){return"__BSL_PREVENT_DEFAULT__"in window||(window.__BSL_PREVENT_DEFAULT__=function(t){t.cancelable&&t.preventDefault()}),window.__BSL_PREVENT_DEFAULT__}function i(t){return t?Array.isArray(t)?t:[t]:[]}function l(t,e,n){const{scrollTop:o,scrollLeft:i,scrollWidth:l,scrollHeight:s,clientWidth:c,clientHeight:r}=t,d=function(t){const e=window.getComputedStyle(t).flexDirection;return{column:"column-reverse"===e,row:"row-reverse"===e}}(t);let u,a,h,f;return d.column?(u=n>0&&Math.abs(o)+r+1>=s,a=n<0&&Math.abs(o)<=1):(u=n>0&&0===o,a=n<0&&o+r+1>=s),d.row?(h=e>0&&Math.abs(i)+c+1>=l,f=e<0&&Math.abs(i)<=1):(h=e>0&&0===i,f=e<0&&i+c+1>=l),{isOnTop:u,isOnBottom:a,isOnLeft:h,isOnRight:f}}const s={lockedNum:0,lockedElements:[],unLockCallback:null,documentListenerAdded:!1,initialClientPos:{clientX:0,clientY:0}};function c(e){if(t())return s;if(!(null==e?void 0:e.useGlobalLockState))return c.lockState;const n="__BSL_LOCK_STATE__"in window?Object.assign(Object.assign({},s),window.__BSL_LOCK_STATE__):s;return window.__BSL_LOCK_STATE__=n,n}function r(t){var e;const n=document.documentElement,o=Object.assign({},n.style),i=null===(e=null==t?void 0:t.withPaddingRight)||void 0===e||e,l=window.innerWidth-n.clientWidth,s=parseInt(window.getComputedStyle(n).paddingRight,10);return n.style.overflow="hidden",n.style.boxSizing="border-box",i&&(n.style.paddingRight=`${l+s}px`),()=>{["overflow","boxSizing","paddingRight"].forEach((t=>{n.style[t]=o[t]||""}))}}function d(s,d){if(t())return;const u=e(),a=c(d);u.ios?(i(s).filter((t=>t&&-1===a.lockedElements.indexOf(t))).forEach((t=>{t.ontouchstart=t=>{const{clientX:e,clientY:n}=t.targetTouches[0];a.initialClientPos={clientX:e,clientY:n}},t.ontouchmove=e=>{!function(t,e,n){if(e){const i=t.targetTouches[0].clientX-n.clientX,s=t.targetTouches[0].clientY-n.clientY,c=Math.abs(s)>Math.abs(i),{isOnTop:r,isOnBottom:d,isOnLeft:u,isOnRight:a}=l(e,i,s);if(c&&(r||d)||!c&&(u||a))return o()(t)}t.stopPropagation()}(e,t,a.initialClientPos)},a.lockedElements.push(t)})),function(t){if(!e().ios)return;if(t.documentListenerAdded)return;document.addEventListener("touchmove",o(),n({passive:!1})),t.documentListenerAdded=!0}(a),(null==d?void 0:d.setOverflowForIOS)&&(a.unLockCallback=r(d))):a.lockedNum<=0&&(a.unLockCallback=u.android?function(t){const e=document.documentElement,n=document.body,o=e.scrollTop||n.scrollTop,i=Object.assign({},e.style),l=Object.assign({},n.style);return e.style.height="100%",e.style.overflow="hidden",n.style.top=`-${o}px`,n.style.width="100%",n.style.height="auto",n.style.position="fixed",n.style.overflow=(null==t?void 0:t.overflowType)||"hidden",()=>{e.style.height=i.height||"",e.style.overflow=i.overflow||"",["top","width","height","overflow","position"].forEach((t=>{n.style[t]=l[t]||""})),"scrollBehavior"in document.documentElement.style?window.scrollTo({top:o,behavior:"instant"}):window.scrollTo(0,o)}}(d):r(d)),a.lockedNum+=1}function u(n,o){var l;if(t())return;const s=c(o);s.lockedNum-=1,s.lockedNum>0||(null===(l=s.unLockCallback)||void 0===l||l.call(s),e().ios&&(i(n).forEach((t=>{const e=s.lockedElements.indexOf(t);t&&-1!==e&&(t.ontouchmove=null,t.ontouchstart=null,s.lockedElements.splice(e,1))})),h(s)))}function a(n){var o;if(t())return;const i=c(n);if(i.lockedNum=0,null===(o=i.unLockCallback)||void 0===o||o.call(i),e().ios){if(i.lockedElements.length){let t=i.lockedElements.pop();for(;t;)t.ontouchmove=null,t.ontouchstart=null,t=i.lockedElements.pop()}h(i)}}function h(t){t.documentListenerAdded&&(document.removeEventListener("touchmove",o(),n({passive:!1})),t.documentListenerAdded=!1)}c.lockState=s;export{a as clearBodyLocks,c as getLockState,d as lock,u as unlock};