UNPKG

two-dimension-scroll

Version:

A smooth scroll library that detects both horizontal and vertical scroll and converts them to vertical smooth scrolling

2 lines (1 loc) 18.7 kB
import{useState as t,useRef as o,useEffect as i,useCallback as e}from"react";function n(){function t(t,o,i){return Math.min(Math.max(t,o),i)}function o(){return"undefined"==typeof document?0:Math.max(document.body.scrollHeight-window.innerHeight,document.documentElement.scrollHeight-window.innerHeight,0)}var i="undefined"==typeof window?function(t){return setTimeout(t,16)}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||function(t){return setTimeout(t,16)},e="undefined"==typeof window?clearTimeout:window.cancelAnimationFrame||window.webkitCancelAnimationFrame||clearTimeout;function n(t){t=t||{},this.currentEnvironment=function(){if("undefined"==typeof window)return"desktop";var t=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),o="ontouchstart"in window||navigator.maxTouchPoints>0,i=window.innerWidth<=768,e=/iPad|Android(?!.*Mobile)|Tablet/i.test(navigator.userAgent)&&window.innerWidth>=768;return t&&!e||i&&o?"mobile":e?"tablet":"desktop"}(),this.isMobileDevice="mobile"===this.currentEnvironment,this.isTabletDevice="tablet"===this.currentEnvironment,this.isDesktopDevice="desktop"===this.currentEnvironment;this.options=this.mergeOptions(t,{disabled:!1,debug:!1,ui:{hideScrollbar:!0,showScrollProgress:!1,customScrollbarStyle:!1},desktop:{duration:1e3,horizontalSensitivity:1.2,verticalSensitivity:1.5,lerp:.1,wheelMultiplier:1.1,precisionMode:!0,keyboardScrollAmount:.8,prioritizeVertical:!1,lockTouchDirection:!0,touchDirectionThreshold:15,allowDirectionChange:!0,directionChangeThreshold:25,directionChangeSmoothness:.3,useAngleBasedDirection:!0,horizontalAngleThreshold:20},mobile:{duration:800,horizontalSensitivity:1.8,verticalSensitivity:2.2,lerp:.15,touchMultiplier:2.5,bounceEffect:!0,flingMultiplier:1.2,touchStopThreshold:4,prioritizeVertical:!1,lockTouchDirection:!0,touchDirectionThreshold:20,allowDirectionChange:!0,directionChangeThreshold:30,directionChangeSmoothness:.4,useAngleBasedDirection:!0,horizontalAngleThreshold:5},tablet:{duration:900,horizontalSensitivity:1.5,verticalSensitivity:1.8,lerp:.12,wheelMultiplier:1.05,touchMultiplier:2.2,hybridMode:!0,prioritizeVertical:!1,lockTouchDirection:!0,touchDirectionThreshold:18,allowDirectionChange:!0,directionChangeThreshold:28,directionChangeSmoothness:.35,useAngleBasedDirection:!0,horizontalAngleThreshold:18}}),this.targetScroll=0,this.animatedScroll=0,this.isScrolling=!1,this.isAnimating=!1,this.rafId=null,this.scrollCallbacks=[],this.passive=!1,this.touchStartY=0,this.touchStartX=0,this.touchStartTime=0,this.lastTouchX=0,this.lastTouchY=0,this.lastTouchTime=0,this.touchVelocityX=0,this.touchVelocityY=0,this.touchMoveCount=0,this.touchStopTimer=null,this.isModalOpen=!1,this.touchDirection=null,this.touchDirectionLocked=!1,this.touchStartDeltaX=0,this.touchStartDeltaY=0,this.oppositeDirectionCount=0,this.lastDeltaX=0,this.lastDeltaY=0,this.smoothedDeltaX=0,this.smoothedDeltaY=0,this.directionChangeStartTime=0,this.verticalScrollDirection=null,this.passive=!!function(){if("undefined"==typeof window)return!1;var t=!1;try{var o=Object.defineProperty({},"passive",{get:function(){return t=!0,!0}});window.addEventListener("testPassive",function(){},o),window.removeEventListener("testPassive",function(){},o)}catch(t){}return t}()&&{passive:!1},this.targetScroll="undefined"==typeof window?0:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,this.animatedScroll=this.targetScroll,this.environment=this.currentEnvironment,this.init()}return n.prototype.mergeOptions=function(t,o){var i={};for(var e in o)if("desktop"!==e&&"mobile"!==e&&"tablet"!==e)if("ui"===e){i[e]={};var n=o[e]||{},r=t[e]||{};for(var l in n)i[e][l]=n[l];for(var l in r)i[e][l]=r[l]}else i[e]=void 0!==t[e]?t[e]:o[e];var s=o[this.currentEnvironment]||o.desktop,a=t[this.currentEnvironment]||{};for(var h in s)void 0!==t[h]?i[h]=t[h]:void 0!==a[h]?i[h]=a[h]:i[h]=s[h];return i},n.prototype.init=function(){"undefined"!=typeof window&&(this.disableDefaultScroll(),this.setupScrollbarStyles(),this.bindEvents(),this.startAnimationLoop())},n.prototype.setupScrollbarStyles=function(){var t=document.getElementById("twodimension-scrollbar-styles");t&&t.parentNode&&t.parentNode.removeChild(t);var o=document.createElement("style");o.id="twodimension-scrollbar-styles";var i="";!1!==this.options.ui?.hideScrollbar?i="\n /* 스크롤바 숨김 */\n html::-webkit-scrollbar,\n body::-webkit-scrollbar {\n display: none !important;\n }\n html {\n -ms-overflow-style: none !important;\n scrollbar-width: none !important;\n }\n ":this.options.ui?.customScrollbarStyle&&(i="\n /* 커스텀 스크롤바 */\n html {\n -ms-overflow-style: auto !important;\n scrollbar-width: thin !important;\n }\n html::-webkit-scrollbar {\n width: 8px !important;\n }\n html::-webkit-scrollbar-track {\n background: rgba(0,0,0,0.1) !important;\n }\n html::-webkit-scrollbar-thumb {\n background: rgba(0,0,0,0.3) !important;\n border-radius: 4px !important;\n }\n html::-webkit-scrollbar-thumb:hover {\n background: rgba(0,0,0,0.5) !important;\n }\n "),o.textContent="\n html {\n scroll-behavior: auto !important;\n -webkit-overflow-scrolling: touch;\n }\n "+i,document.head.appendChild(o),this.scrollbarStyleElement=o,this.options.debug&&(!1!==this.options.ui?.hideScrollbar||this.options.ui)},n.prototype.disableDefaultScroll=function(){var t=document.createElement("style");t.id="twodimension-base-styles",t.textContent="\n html {\n overflow-x: hidden;\n scroll-behavior: auto;\n }\n body {\n overflow-x: hidden;\n overscroll-behavior: none;\n -webkit-overflow-scrolling: touch;\n }\n ",document.head.appendChild(t),this.styleElement=t;var o=this;this.preventScroll=function(t){if(!(o.options.disabled||t.isPropagationStopped&&"function"==typeof t.isPropagationStopped&&t.isPropagationStopped())){if(o.isModalOpen){for(var i=!1,e=l=r=t.target,n=0;n<10&&e&&e!==document.body;n++){if(e.classList)if((h=e.classList).contains("modal")||h.contains("modal-overlay")||h.contains("modal-content")||h.contains("modal-wrapper")||"dialog"===e.getAttribute("role")||"true"===e.getAttribute("aria-modal")){i=!0;break}e=e.parentElement}return i?void 0:void t.preventDefault()}for(var r,l=r=t.target,s=null;l&&l!==document.body;)if(l.tagName){var a=l.tagName.toLowerCase(),h=l.classList||[],c=l.getAttribute("role")||"",u=l.getAttribute("aria-modal");if("dialog"===a||h.contains("modal")||h.contains("modal-overlay")||h.contains("modal-content")||h.contains("modal-wrapper")||h.contains("modal-container")||h.contains("dialog")||h.contains("popup")||h.contains("overlay")||h.contains("lightbox")||h.contains("backdrop")||"modal-root"===l.id||"portal-root"===l.id||"dialog"===c||"alertdialog"===c||"modal"===c||"true"===u||h.contains("ReactModal__Overlay")||h.contains("ReactModal__Content")){s=l;break}l=l.parentElement}else l=l.parentElement;if(s){if(o.isModalOpen)return;var d=o.findScrollableElement(r,s);if(d){var p=d.scrollTop,m=d.scrollHeight-d.clientHeight;if("wheel"===t.type){var v=t.deltaY||t.detail||t.wheelDelta,b=!1;if((v<0&&p<=0||v>0&&p>=m)&&(b=!0),b)return void t.preventDefault()}else t.type}}else t.preventDefault()}},document.addEventListener("wheel",this.preventScroll,{passive:!1}),document.addEventListener("touchmove",this.preventScroll,{passive:!1})},n.prototype.bindEvents=function(){if("undefined"!=typeof window){var t=this;this.wheelHandler=function(o){t.onWheel(o)},this.options.debug&&(this.globalWheelDetector=function(t){},document.addEventListener("wheel",this.globalWheelDetector,{capture:!0,passive:!0}));var o={passive:!1,capture:!1};document.addEventListener("wheel",this.wheelHandler,o),document.addEventListener("mousewheel",this.wheelHandler,o),document.addEventListener("DOMMouseScroll",this.wheelHandler,o),document.addEventListener("touchstart",function(o){t.onTouchStart(o)},this.passive),document.addEventListener("touchmove",function(o){t.onTouchMove(o)},this.passive),document.addEventListener("touchend",function(o){t.onTouchEnd(o)},this.passive)}},n.prototype.startAnimationLoop=function(){var e=this;!function n(){var r,l,s,a=e.animatedScroll;e.animatedScroll=(r=e.animatedScroll,l=e.targetScroll,(1-(s=e.options.lerp))*r+s*l);var h=o();e.animatedScroll=t(e.animatedScroll,0,h),e.targetScroll=t(e.targetScroll,0,h);var c=Math.abs(e.targetScroll-e.animatedScroll),u=Math.abs(e.animatedScroll-a);if(c<.5&&u<.1)return e.animatedScroll=e.targetScroll,window.scrollTo(0,e.animatedScroll),e.isScrolling=!1,void(e.rafId=null);if(c>.1||u>.05)if(window.scrollTo(0,e.animatedScroll),c>.5){e.isScrolling=!0;for(var d={deltaX:0,deltaY:e.targetScroll-e.animatedScroll,scrollTop:e.animatedScroll,direction:e.targetScroll>e.animatedScroll?1:-1,type:"smooth"},p=0;p<e.scrollCallbacks.length;p++)e.scrollCallbacks[p](d)}else e.isScrolling=!1;e.rafId=i(n)}()},n.prototype.onWheel=function(t){if(this.options.debug,!this.options.disabled){if(this.isModalOpen)return this.options.debug,void this.preventScroll(t);var o=t.deltaX,i=t.deltaY;1===t.deltaMode?(o*=40,i*=40):2===t.deltaMode&&(o*=.8*window.innerHeight,i*=.8*window.innerHeight);var e=this.options.horizontalSensitivity||1,n=this.options.verticalSensitivity||1,r=this.options.wheelMultiplier||1,l=o*e,s=i*n;isNaN(l)&&(l=0),isNaN(s)&&(s=0),this.options.debug;var a=this.calculateCombinedDelta(l,s);isNaN(a)&&(a=0),this.addToScroll(a*r)}},n.prototype.onTouchStart=function(t){if(!this.options.disabled){var o=t.touches[0];this.touchStartX=o.clientX,this.touchStartY=o.clientY,this.touchStartTime=Date.now(),this.lastTouchX=o.clientX,this.lastTouchY=o.clientY,this.lastTouchTime=this.touchStartTime,this.touchVelocityX=0,this.touchVelocityY=0,this.touchMoveCount=0,this.touchDirection=null,this.touchDirectionLocked=!1,this.touchStartDeltaX=0,this.touchStartDeltaY=0,this.oppositeDirectionCount=0,this.lastDeltaX=0,this.lastDeltaY=0,this.smoothedDeltaX=0,this.smoothedDeltaY=0,this.directionChangeStartTime=0,this.verticalScrollDirection=null,this.touchStopTimer&&(clearTimeout(this.touchStopTimer),this.touchStopTimer=null)}},n.prototype.onTouchMove=function(t){if(this.options.debug,!this.options.disabled){var o=t.touches[0],i=Date.now(),e=this.lastTouchX-o.clientX,n=this.lastTouchY-o.clientY;if(Math.sqrt(e*e+n*n)>this.options.touchStopThreshold){this.touchStopTimer&&(clearTimeout(this.touchStopTimer),this.touchStopTimer=null);var r=i-this.lastTouchTime;r>0&&(this.touchVelocityX=e/r,this.touchVelocityY=n/r);var l=e*this.options.horizontalSensitivity*this.options.touchMultiplier,s=n*this.options.verticalSensitivity*this.options.touchMultiplier;if(this.isModalOpen)this.preventScroll(t),this.options.debug;else if(Math.abs(l)>3||Math.abs(s)>3){var a=this.calculateCombinedDelta(l,s);this.addToScroll(a)}this.lastTouchX=o.clientX,this.lastTouchY=o.clientY,this.lastTouchTime=i,this.touchMoveCount++}else{var h=this;this.touchStopTimer||(this.touchStopTimer=setTimeout(function(){h.touchVelocityX*=.8,h.touchVelocityY*=.8,h.touchStopTimer=null},100))}}},n.prototype.onTouchEnd=function(t){if(!this.options.disabled){this.touchStopTimer&&(clearTimeout(this.touchStopTimer),this.touchStopTimer=null);var o=t.changedTouches[0],i=Date.now()-this.touchStartTime,e=this.touchStartY-o.clientY;if(i<300&&Math.abs(e)>50&&this.touchMoveCount>3){var n=400*this.touchVelocityY*(this.options.flingMultiplier||1);Math.abs(n)>50&&(this.addToScroll(n),this.options.debug)}this.touchVelocityX=0,this.touchVelocityY=0,this.touchMoveCount=0}},n.prototype.calculateCombinedDelta=function(t,o){if(this.options.useAngleBasedDirection){var i=this.options.horizontalAngleThreshold||20;this.options.prioritizeVertical&&(i=15);var e=Math.atan2(Math.abs(o),Math.abs(t))*(180/Math.PI);if(this.options.debug,e<=i)return t;var n=Math.sqrt(t*t+o*o);return this.touchDirectionLocked||(this.verticalScrollDirection=o>0?"down":"up",this.touchDirectionLocked=!0,this.options.debug),"down"===this.verticalScrollDirection?n:-n}if(this.options.lockTouchDirection){var r=this.options.touchDirectionThreshold||15,l=!1!==this.options.allowDirectionChange,s=this.options.directionChangeThreshold||25,a=this.options.directionChangeSmoothness||.3;if(this.smoothedDeltaX=this.smoothedDeltaX*(1-a)+t*a,this.smoothedDeltaY=this.smoothedDeltaY*(1-a)+o*a,!this.touchDirectionLocked&&(Math.abs(t)>r||Math.abs(o)>r)&&(this.options.prioritizeVertical?this.touchDirection=Math.abs(o)>5?"vertical":"horizontal":this.touchDirection=Math.abs(t)>Math.abs(o)?"horizontal":"vertical",this.touchDirectionLocked=!0,this.oppositeDirectionCount=0,this.options.debug),this.touchDirectionLocked&&l){var h="horizontal"===this.touchDirection,c=h?t:o,u=h?o:t;Math.abs(u)>Math.abs(c)&&Math.abs(u)>s?(this.oppositeDirectionCount++,1===this.oppositeDirectionCount&&(this.directionChangeStartTime=Date.now()),this.oppositeDirectionCount>=3&&(this.touchDirection=h?"vertical":"horizontal",this.oppositeDirectionCount=0,this.options.debug)):this.oppositeDirectionCount=Math.max(0,this.oppositeDirectionCount-.5);var d="horizontal"===this.touchDirection?this.smoothedDeltaX:this.smoothedDeltaY;if(0!==this.lastDeltaX||0!==this.lastDeltaY){var p="horizontal"===this.touchDirection?this.lastDeltaX:this.lastDeltaY;if(Math.abs(d-p)>50){var m=p+50*Math.sign(d-p);return this.lastDeltaX="horizontal"===this.touchDirection?m:t,this.lastDeltaY="vertical"===this.touchDirection?m:o,m}}return this.lastDeltaX=t,this.lastDeltaY=o,d}if(this.touchDirectionLocked)return"horizontal"===this.touchDirection?t:o}if(this.options.prioritizeVertical)return 0!==o?o:t;var v=Math.abs(t),b=Math.abs(o);if(v>.7*b)return t;if(b>.7*v)return o;n=Math.sqrt(t*t+o*o),e=Math.atan2(o,t);return Math.abs(e)<Math.PI/3||Math.abs(e)>2*Math.PI/3?t>0?n:-n:o>0?n:-n},n.prototype.pauseForModal=function(){this.isModalOpen=!0,this.savedScrollPosition=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,document.body&&(document.body.classList.add("twodimension-modal-open"),document.body.style.overflow="hidden",document.body.style.touchAction="none",document.body.style.top="-"+this.savedScrollPosition+"px"),document.documentElement&&(document.documentElement.style.overflow="hidden"),this.options.debug},n.prototype.resumeFromModal=function(){this.isModalOpen=!1,document.body&&(document.body.classList.remove("twodimension-modal-open"),document.body.style.overflow="",document.body.style.touchAction="",document.body.style.top=""),document.documentElement&&(document.documentElement.style.overflow=""),this.touchStartX=0,this.touchStartY=0,this.touchStartTime=0,this.lastTouchX=0,this.lastTouchY=0,this.lastTouchTime=0,this.touchVelocityX=0,this.touchVelocityY=0,this.touchMoveCount=0,this.touchStopTimer&&(clearTimeout(this.touchStopTimer),this.touchStopTimer=null),"number"==typeof this.savedScrollPosition&&(window.scrollTo(0,this.savedScrollPosition),this.targetScroll=this.savedScrollPosition,this.animatedScroll=this.savedScrollPosition),this.options.debug},n.prototype.isInModalMode=function(){return this.isModalOpen||!1},n.prototype.addToScroll=function(i){if(!isNaN(i)){(isNaN(this.targetScroll)||void 0===this.targetScroll)&&(this.targetScroll=0);var e=o(),n=this.targetScroll;this.targetScroll=t(this.targetScroll+i,0,e),isNaN(this.targetScroll)?this.targetScroll=n:(this.options.debug,Math.abs(this.targetScroll-n)>.1&&!this.rafId&&(this.options.debug,this.startAnimationLoop()))}},n.prototype.scrollTo=function(i,e){var n=o();this.targetScroll=t(i,0,n),e&&e.immediate&&(this.animatedScroll=this.targetScroll,window.scrollTo(0,this.animatedScroll)),!this.rafId&&Math.abs(this.targetScroll-this.animatedScroll)>.1&&this.startAnimationLoop()},n.prototype.on=function(t){this.scrollCallbacks.push(t)},n.prototype.off=function(t){var o=this.scrollCallbacks.indexOf(t);o>-1&&this.scrollCallbacks.splice(o,1)},n.prototype.disable=function(){this.options.disabled=!0},n.prototype.enable=function(){this.options.disabled=!1},n.prototype.showScrollbar=function(t){"boolean"==typeof t&&(this.options.ui.hideScrollbar=!t),this.setupScrollbarStyles()},n.prototype.toggleScrollbar=function(){this.options.ui.hideScrollbar=!this.options.ui.hideScrollbar,this.setupScrollbarStyles()},n.prototype.getScrollbarVisibility=function(){return{visible:!this.options.ui.hideScrollbar,hideScrollbar:this.options.ui.hideScrollbar}},n.prototype.isScrollbarVisible=function(){return!this.options.ui.hideScrollbar},n.prototype.getCurrentPosition=function(){return this.animatedScroll},n.prototype.getMaxPosition=function(){return o()},n.prototype.destroy=function(){this.touchStopTimer&&(clearTimeout(this.touchStopTimer),this.touchStopTimer=null),this.rafId&&(e(this.rafId),this.rafId=null),this.styleElement&&this.styleElement.parentNode&&(this.styleElement.parentNode.removeChild(this.styleElement),this.styleElement=null),this.scrollbarStyleElement&&this.scrollbarStyleElement.parentNode&&(this.scrollbarStyleElement.parentNode.removeChild(this.scrollbarStyleElement),this.scrollbarStyleElement=null);try{document.removeEventListener("wheel",this.preventScroll),document.removeEventListener("touchmove",this.preventScroll)}catch(t){}this.scrollCallbacks=[],this.targetScroll=0,this.animatedScroll=0,this.isScrolling=!1,this.isAnimating=!1,this.isModalOpen=!1},n.prototype.cleanup=function(){return this.destroy.bind(this)},n}function r(r={},l={}){const[s,a]=t(!1),[h,c]=t(0),[u,d]=t({position:0,maxPosition:0,progress:0}),p=o(null);i(()=>{const t=setTimeout(()=>{try{const t=n(),o={...{debug:!0,desktop:{duration:1e3,horizontalSensitivity:1.2,verticalSensitivity:1.5,lerp:.1,wheelMultiplier:1.1,precisionMode:!0,keyboardScrollAmount:.8},mobile:{duration:800,horizontalSensitivity:1.8,verticalSensitivity:2.2,lerp:.15,touchMultiplier:2.5,bounceEffect:!0,flingMultiplier:1.2,touchStopThreshold:4},tablet:{duration:900,horizontalSensitivity:1.5,verticalSensitivity:1.8,lerp:.12,wheelMultiplier:1.05,touchMultiplier:2.2,hybridMode:!0}},...r},i=new t(o);p.current=i;const e=t=>{c(t.scrollTop),d({position:t.scrollTop,maxPosition:i.getMaxPosition?.()||0,progress:i.getMaxPosition?.()>0?t.scrollTop/i.getMaxPosition()*100:0}),o.debug};i.on(e),a(!0)}catch(t){}},100);return()=>{clearTimeout(t),p.current&&p.current.cleanup?.()}},[]);return{isReady:s,scrollPosition:h,scrollInfo:u,scrollTo:e(t=>{p.current&&p.current.scrollTo&&p.current.scrollTo(t)},[]),instance:p.current}}export{r as default,r as useTwoDimensionScroll};