scrollbooster
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("ScrollBooster",[],e):"object"==typeof exports?exports.ScrollBooster=e():t.ScrollBooster=e()}(window,(function(){return function(t){var e={};function i(o){if(e[o])return e[o].exports;var n=e[o]={i:o,l:!1,exports:{}};return t[o].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=t,i.c=e,i.d=function(t,e,o){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)i.d(o,n,function(e){return t[e]}.bind(null,n));return o},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=0)}([function(t,e,i){"use strict";function o(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),i.push.apply(i,o)}return i}function n(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[e]?arguments[e]:{};e%2?o(Object(i),!0).forEach((function(e){s(t,e,i[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):o(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function s(t,e,i){return e in t?Object.defineProperty(t,e,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[e]=i,t}function r(t,e){for(var i=0;i<e.length;i++){var o=e[i];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}i.r(e),i.d(e,"default",(function(){return p}));var h=function(t){return Math.max(t.offsetWidth,t.scrollWidth)},c=function(t){return Math.max(t.offsetHeight,t.scrollHeight)},p=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t);var i={content:e.viewport.children[0],direction:"all",pointerMode:"all",bounce:!0,friction:.05,bounceForce:.1,textSelection:!1,inputsFocus:!0,emulateScroll:!1,onClick:function(){},onUpdate:function(){},shouldScroll:function(){return!0}};this.props=n({},i,{},e),this.props.viewport&&this.props.viewport instanceof Element?this.props.content?(this.viewport={width:this.props.viewport.clientWidth,height:this.props.viewport.clientHeight},this.content={width:h(this.props.content),height:c(this.props.content)},this.position={x:0,y:0},this.velocity={x:0,y:0},this.friction=1-this.props.friction,this.bounceForce=this.props.bounceForce,this.isDragging=!1,this.dragStartPosition={x:0,y:0},this.dragOffset=n({},this.dragStartPosition),this.dragPosition=n({},this.position),this.isTargetScroll=!1,this.targetPosition={x:0,y:0},this.isScrolling=!1,this.scrollOffset={x:0,y:0},this.bounce=this.props.bounce,this.textSelection=this.props.textSelection,this.boundX={from:Math.min(-this.content.width+this.viewport.width,0),to:0},this.boundY={from:Math.min(-this.content.height+this.viewport.height,0),to:0},this.isRunning=!1,this.rafID=null,this.events={},this.animate(),this.handleEvents()):console.error("ScrollBooster init error: Viewport does not have any content"):console.error('ScrollBooster init error: "viewport" config property must be present and must be Element')}var e,i,o;return e=t,(i=[{key:"run",value:function(){var t=this;this.isRunning=!0,cancelAnimationFrame(this.rafID),this.rafID=requestAnimationFrame((function(){return t.animate()}))}},{key:"animate",value:function(){var t=this;this.isRunning&&(this.update(),this.props.onUpdate(this.getState()),this.rafID=requestAnimationFrame((function(){return t.animate()})))}},{key:"isMoving",value:function(){return this.isDragging||this.isScrolling||Math.abs(this.velocity.x)>=.1||Math.abs(this.velocity.y)>=.1}},{key:"update",value:function(){this.applyBoundForce(),this.applyDragForce(),this.applyScrollForce(),this.applyTargetForce(),this.velocity.x*=this.friction,this.velocity.y*=this.friction,"vertical"!==this.props.direction&&(this.position.x+=this.velocity.x),"horizontal"!==this.props.direction&&(this.position.y+=this.velocity.y),this.bounce&&!this.isScrolling||this.isTargetScroll||(this.position.x=Math.max(Math.min(this.position.x,this.boundX.to),this.boundX.from),this.position.y=Math.max(Math.min(this.position.y,this.boundY.to),this.boundY.from)),this.isMoving()||(this.isRunning=!1,this.isTargetScroll=!1)}},{key:"applyForce",value:function(t){this.velocity.x+=t.x,this.velocity.y+=t.y}},{key:"applyBoundForce",value:function(){if(this.bounce&&!this.isDragging){var t=this.position.x<this.boundX.from,e=this.position.x>this.boundX.to,i=this.position.y<this.boundY.from,o=this.position.y>this.boundY.to,n=t||e,s=i||o;if(n||s){var r=t?this.boundX.from:this.boundX.to,h=i?this.boundY.from:this.boundY.to,c=r-this.position.x,p=h-this.position.y,l={x:c*this.bounceForce,y:p*this.bounceForce},a=this.position.x+(this.velocity.x+l.x)/(1-this.friction),u=this.position.y+(this.velocity.y+l.y)/(1-this.friction);(t&&a>=this.boundX.from||e&&a<=this.boundX.to)&&(l.x=c*this.bounceForce-this.velocity.x),(i&&u>=this.boundY.from||o&&u<=this.boundY.to)&&(l.y=p*this.bounceForce-this.velocity.y),this.applyForce({x:n?l.x:0,y:s?l.y:0})}}}},{key:"applyDragForce",value:function(){if(this.isDragging){var t=this.dragPosition.x-this.position.x,e=this.dragPosition.y-this.position.y,i={x:t-this.velocity.x,y:e-this.velocity.y};this.applyForce(i)}}},{key:"applyScrollForce",value:function(){if(this.isScrolling){var t={x:this.scrollOffset.x-this.velocity.x,y:this.scrollOffset.y-this.velocity.y};this.scrollOffset.x=0,this.scrollOffset.y=0,this.applyForce(t)}}},{key:"applyTargetForce",value:function(){if(this.isTargetScroll){var t={x:.08*(this.targetPosition.x-this.position.x)-this.velocity.x,y:.08*(this.targetPosition.y-this.position.y)-this.velocity.y};this.applyForce(t)}}},{key:"scrollTo",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.isTargetScroll=!0,this.targetPosition.x=-t.x||0,this.targetPosition.y=-t.y||0,this.run()}},{key:"setPosition",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.velocity.x=0,this.velocity.y=0,this.position.x=-t.x||0,this.position.y=-t.y||0,this.run()}},{key:"getState",value:function(){return{isMoving:this.isMoving(),isDragging:!(!this.dragOffset.x&&!this.dragOffset.y),position:{x:-this.position.x,y:-this.position.y},dragOffset:this.dragOffset}}},{key:"updateMetrics",value:function(){this.viewport.width=this.props.viewport.clientWidth,this.viewport.height=this.props.viewport.clientHeight,this.content.width=h(this.props.content),this.content.height=c(this.props.content),this.boundX.from=Math.min(-this.content.width+this.viewport.width,0),this.boundY.from=Math.min(-this.content.height+this.viewport.height,0),this.run()}},{key:"handleEvents",value:function(){var t=this,e={x:0,y:0},i=null,o=!1,n=function(i){if(t.isDragging){var n=o?i.touches[0].pageX:i.pageX,s=o?i.touches[0].pageY:i.pageY;t.dragOffset.x=n-e.x,t.dragOffset.y=s-e.y,t.dragPosition.x=t.dragStartPosition.x+t.dragOffset.x,t.dragPosition.y=t.dragStartPosition.y+t.dragOffset.y}};this.events.pointerdown=function(i){var s=(o=!(!i.touches||!i.touches[0]))?i.touches[0]:i,r=s.pageX,h=s.pageY,c=s.clientX,p=s.clientY,l=t.props.viewport,a=l.getBoundingClientRect();if(!(c-a.left>=l.clientLeft+l.clientWidth)&&!(p-a.top>=l.clientTop+l.clientHeight)&&t.props.shouldScroll(t.getState(),i)&&("mouse"!==t.props.pointerMode||!o)&&("touch"!==t.props.pointerMode||o)&&!(t.props.inputsFocus&&["input","textarea","button","select","label"].indexOf(i.target.nodeName.toLowerCase())>-1)){if(t.textSelection){if(function(t,e,i){for(var o=t.childNodes,n=document.createRange(),s=0;s<o.length;s++){var r=o[s];if(3===r.nodeType){n.selectNodeContents(r);var h=n.getBoundingClientRect();if(e>=h.left&&i>=h.top&&e<=h.right&&i<=h.bottom)return r}}return!1}(i.target,c,p))return;(u=window.getSelection?window.getSelection():document.selection)&&(u.removeAllRanges?u.removeAllRanges():u.empty&&u.empty())}var u;t.isDragging=!0,e.x=r,e.y=h,t.dragStartPosition.x=t.position.x,t.dragStartPosition.y=t.position.y,n(i),t.run(),i.preventDefault()}},this.events.pointermove=function(t){n(t)},this.events.pointerup=function(){t.isDragging=!1},this.events.wheel=function(e){t.props.emulateScroll&&(t.velocity.x=0,t.velocity.y=0,t.isScrolling=!0,t.scrollOffset.x=-e.deltaX,t.scrollOffset.y=-e.deltaY,t.run(),clearTimeout(i),i=setTimeout((function(){return t.isScrolling=!1}),80),e.preventDefault())},this.events.scroll=function(){var e=t.props.viewport,i=e.scrollLeft,o=e.scrollTop;Math.abs(t.position.x+i)>3&&(t.position.x=-i,t.velocity.x=0),Math.abs(t.position.y+o)>3&&(t.position.y=-o,t.velocity.y=0)},this.events.click=function(e){return t.props.onClick(t.getState(),e)},this.events.contentLoad=function(){return t.updateMetrics()},this.events.resize=function(){return t.updateMetrics()},this.props.viewport.addEventListener("mousedown",this.events.pointerdown),this.props.viewport.addEventListener("touchstart",this.events.pointerdown),this.props.viewport.addEventListener("click",this.events.click),this.props.viewport.addEventListener("wheel",this.events.wheel),this.props.viewport.addEventListener("scroll",this.events.scroll),this.props.content.addEventListener("load",this.events.contentLoad,!0),window.addEventListener("mousemove",this.events.pointermove),window.addEventListener("touchmove",this.events.pointermove),window.addEventListener("mouseup",this.events.pointerup),window.addEventListener("touchend",this.events.pointerup),window.addEventListener("resize",this.events.resize)}},{key:"updateOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.props=n({},this.props,{},t),this.props.onUpdate(this.getState()),this.run()}},{key:"destroy",value:function(){this.props.viewport.removeEventListener("mousedown",this.events.pointerdown),this.props.viewport.removeEventListener("touchstart",this.events.pointerdown),this.props.viewport.removeEventListener("click",this.events.click),this.props.viewport.removeEventListener("wheel",this.events.wheel),this.props.viewport.removeEventListener("scroll",this.events.scroll),this.props.content.removeEventListener("load",this.events.contentLoad),window.removeEventListener("mousemove",this.events.pointermove),window.removeEventListener("touchmove",this.events.pointermove),window.removeEventListener("mouseup",this.events.pointerup),window.removeEventListener("touchend",this.events.pointerup),window.removeEventListener("resize",this.events.resize)}}])&&r(e.prototype,i),o&&r(e,o),t}()}]).default})); | ||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("ScrollBooster",[],e):"object"==typeof exports?exports.ScrollBooster=e():t.ScrollBooster=e()}(window,(function(){return function(t){var e={};function i(o){if(e[o])return e[o].exports;var n=e[o]={i:o,l:!1,exports:{}};return t[o].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=t,i.c=e,i.d=function(t,e,o){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)i.d(o,n,function(e){return t[e]}.bind(null,n));return o},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=0)}([function(t,e,i){"use strict";function o(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),i.push.apply(i,o)}return i}function n(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[e]?arguments[e]:{};e%2?o(Object(i),!0).forEach((function(e){s(t,e,i[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):o(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function s(t,e,i){return e in t?Object.defineProperty(t,e,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[e]=i,t}function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function p(t,e){for(var i=0;i<e.length;i++){var o=e[i];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}i.r(e),i.d(e,"default",(function(){return c}));var h=function(t){return Math.max(t.offsetHeight,t.scrollHeight)},c=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};r(this,t);var i={content:e.viewport.children[0],direction:"all",pointerMode:"all",scrollMode:void 0,bounce:!0,bounceForce:.1,friction:.05,textSelection:!1,inputsFocus:!0,emulateScroll:!1,onClick:function(){},onUpdate:function(){},shouldScroll:function(){return!0}};if(this.props=n({},i,{},e),this.props.viewport&&this.props.viewport instanceof Element)if(this.props.content){this.isDragging=!1,this.isTargetScroll=!1,this.isScrolling=!1,this.isRunning=!1;var o={x:0,y:0};this.position=n({},o),this.velocity=n({},o),this.dragStartPosition=n({},o),this.dragOffset=n({},o),this.dragPosition=n({},o),this.targetPosition=n({},o),this.scrollOffset=n({},o),this.rafID=null,this.events={},this.updateMetrics(),this.handleEvents()}else console.error("ScrollBooster init error: Viewport does not have any content");else console.error('ScrollBooster init error: "viewport" config property must be present and must be Element')}var e,i,o;return e=t,(i=[{key:"updateOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.props=n({},this.props,{},t),this.props.onUpdate(this.getState()),this.startAnimationLoop()}},{key:"updateMetrics",value:function(){var t;this.viewport={width:this.props.viewport.clientWidth,height:this.props.viewport.clientHeight},this.content={width:(t=this.props.content,Math.max(t.offsetWidth,t.scrollWidth)),height:h(this.props.content)},this.edgeX={from:Math.min(-this.content.width+this.viewport.width,0),to:0},this.edgeY={from:Math.min(-this.content.height+this.viewport.height,0),to:0},this.props.onUpdate(this.getState()),this.startAnimationLoop()}},{key:"startAnimationLoop",value:function(){var t=this;this.isRunning=!0,cancelAnimationFrame(this.rafID),this.rafID=requestAnimationFrame((function(){return t.animate()}))}},{key:"animate",value:function(){var t=this;if(this.isRunning){this.updateScrollPosition(),this.isMoving()||(this.isRunning=!1,this.isTargetScroll=!1);var e=this.getState();this.setContentPosition(e),this.props.onUpdate(e),this.rafID=requestAnimationFrame((function(){return t.animate()}))}}},{key:"updateScrollPosition",value:function(){this.applyEdgeForce(),this.applyDragForce(),this.applyScrollForce(),this.applyTargetForce();var t=1-this.props.friction;this.velocity.x*=t,this.velocity.y*=t,"vertical"!==this.props.direction&&(this.position.x+=this.velocity.x),"horizontal"!==this.props.direction&&(this.position.y+=this.velocity.y),this.props.bounce&&!this.isScrolling||this.isTargetScroll||(this.position.x=Math.max(Math.min(this.position.x,this.edgeX.to),this.edgeX.from),this.position.y=Math.max(Math.min(this.position.y,this.edgeY.to),this.edgeY.from))}},{key:"applyForce",value:function(t){this.velocity.x+=t.x,this.velocity.y+=t.y}},{key:"applyEdgeForce",value:function(){if(this.props.bounce&&!this.isDragging){var t=this.position.x<this.edgeX.from,e=this.position.x>this.edgeX.to,i=this.position.y<this.edgeY.from,o=this.position.y>this.edgeY.to,n=t||e,s=i||o;if(n||s){var r=t?this.edgeX.from:this.edgeX.to,p=i?this.edgeY.from:this.edgeY.to,h=r-this.position.x,c=p-this.position.y,a={x:h*this.props.bounceForce,y:c*this.props.bounceForce},l=this.position.x+(this.velocity.x+a.x)/this.props.friction,u=this.position.y+(this.velocity.y+a.y)/this.props.friction;(t&&l>=this.edgeX.from||e&&l<=this.edgeX.to)&&(a.x=h*this.props.bounceForce-this.velocity.x),(i&&u>=this.edgeY.from||o&&u<=this.edgeY.to)&&(a.y=c*this.props.bounceForce-this.velocity.y),this.applyForce({x:n?a.x:0,y:s?a.y:0})}}}},{key:"applyDragForce",value:function(){if(this.isDragging){var t=this.dragPosition.x-this.position.x,e=this.dragPosition.y-this.position.y;this.applyForce({x:t-this.velocity.x,y:e-this.velocity.y})}}},{key:"applyScrollForce",value:function(){this.isScrolling&&(this.applyForce({x:this.scrollOffset.x-this.velocity.x,y:this.scrollOffset.y-this.velocity.y}),this.scrollOffset.x=0,this.scrollOffset.y=0)}},{key:"applyTargetForce",value:function(){this.isTargetScroll&&this.applyForce({x:.08*(this.targetPosition.x-this.position.x)-this.velocity.x,y:.08*(this.targetPosition.y-this.position.y)-this.velocity.y})}},{key:"isMoving",value:function(){return this.isDragging||this.isScrolling||Math.abs(this.velocity.x)>=.01||Math.abs(this.velocity.y)>=.01}},{key:"scrollTo",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.isTargetScroll=!0,this.targetPosition.x=-t.x||0,this.targetPosition.y=-t.y||0,this.startAnimationLoop()}},{key:"setPosition",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.velocity.x=0,this.velocity.y=0,this.position.x=-t.x||0,this.position.y=-t.y||0,this.startAnimationLoop()}},{key:"getState",value:function(){return{isMoving:this.isMoving(),isDragging:!(!this.dragOffset.x&&!this.dragOffset.y),position:{x:-this.position.x,y:-this.position.y},dragOffset:this.dragOffset,borderCollision:{left:this.position.x>=this.edgeX.to,right:this.position.x<=this.edgeX.from,top:this.position.y>=this.edgeY.to,bottom:this.position.y<=this.edgeY.from}}}},{key:"setContentPosition",value:function(t){"transform"===this.props.scrollMode&&(this.props.content.style.transform="translate(".concat(-t.position.x,"px, ").concat(-t.position.y,"px)")),"native"===this.props.scrollMode&&(this.props.viewport.scrollTop=t.position.y,this.props.viewport.scrollLeft=t.position.x)}},{key:"handleEvents",value:function(){var t=this,e={x:0,y:0},i=null,o=!1,n=function(i){if(t.isDragging){var n=o?i.touches[0].pageX:i.pageX,s=o?i.touches[0].pageY:i.pageY;t.dragOffset.x=n-e.x,t.dragOffset.y=s-e.y,t.dragPosition.x=t.dragStartPosition.x+t.dragOffset.x,t.dragPosition.y=t.dragStartPosition.y+t.dragOffset.y}};this.events.pointerdown=function(i){var s=(o=!(!i.touches||!i.touches[0]))?i.touches[0]:i,r=s.pageX,p=s.pageY,h=s.clientX,c=s.clientY,a=t.props.viewport,l=a.getBoundingClientRect();if(!(h-l.left>=a.clientLeft+a.clientWidth)&&!(c-l.top>=a.clientTop+a.clientHeight)&&t.props.shouldScroll(t.getState(),i)&&2!==i.button&&("mouse"!==t.props.pointerMode||!o)&&("touch"!==t.props.pointerMode||o)&&!(t.props.inputsFocus&&["input","textarea","button","select","label"].indexOf(i.target.nodeName.toLowerCase())>-1)){if(t.props.textSelection){if(function(t,e,i){for(var o=t.childNodes,n=document.createRange(),s=0;s<o.length;s++){var r=o[s];if(3===r.nodeType){n.selectNodeContents(r);var p=n.getBoundingClientRect();if(e>=p.left&&i>=p.top&&e<=p.right&&i<=p.bottom)return r}}return!1}(i.target,h,c))return;(u=window.getSelection?window.getSelection():document.selection)&&(u.removeAllRanges?u.removeAllRanges():u.empty&&u.empty())}var u;t.isDragging=!0,e.x=r,e.y=p,t.dragStartPosition.x=t.position.x,t.dragStartPosition.y=t.position.y,n(i),t.startAnimationLoop(),i.preventDefault()}},this.events.pointermove=function(t){n(t)},this.events.pointerup=function(){t.isDragging=!1},this.events.wheel=function(e){t.props.emulateScroll&&(t.velocity.x=0,t.velocity.y=0,t.isScrolling=!0,t.scrollOffset.x=-e.deltaX,t.scrollOffset.y=-e.deltaY,t.startAnimationLoop(),clearTimeout(i),i=setTimeout((function(){return t.isScrolling=!1}),80),e.preventDefault())},this.events.scroll=function(){var e=t.props.viewport,i=e.scrollLeft,o=e.scrollTop;Math.abs(t.position.x+i)>3&&(t.position.x=-i,t.velocity.x=0),Math.abs(t.position.y+o)>3&&(t.position.y=-o,t.velocity.y=0)},this.events.click=function(e){return t.props.onClick(t.getState(),e)},this.events.contentLoad=function(){return t.updateMetrics()},this.events.resize=function(){return t.updateMetrics()},this.props.viewport.addEventListener("mousedown",this.events.pointerdown),this.props.viewport.addEventListener("touchstart",this.events.pointerdown),this.props.viewport.addEventListener("click",this.events.click),this.props.viewport.addEventListener("wheel",this.events.wheel),this.props.viewport.addEventListener("scroll",this.events.scroll),this.props.content.addEventListener("load",this.events.contentLoad,!0),window.addEventListener("mousemove",this.events.pointermove),window.addEventListener("touchmove",this.events.pointermove),window.addEventListener("mouseup",this.events.pointerup),window.addEventListener("touchend",this.events.pointerup),window.addEventListener("resize",this.events.resize)}},{key:"destroy",value:function(){this.props.viewport.removeEventListener("mousedown",this.events.pointerdown),this.props.viewport.removeEventListener("touchstart",this.events.pointerdown),this.props.viewport.removeEventListener("click",this.events.click),this.props.viewport.removeEventListener("wheel",this.events.wheel),this.props.viewport.removeEventListener("scroll",this.events.scroll),this.props.content.removeEventListener("load",this.events.contentLoad),window.removeEventListener("mousemove",this.events.pointermove),window.removeEventListener("touchmove",this.events.pointermove),window.removeEventListener("mouseup",this.events.pointerup),window.removeEventListener("touchend",this.events.pointerup),window.removeEventListener("resize",this.events.resize)}}])&&p(e.prototype,i),o&&p(e,o),t}()}]).default})); | ||
//# sourceMappingURL=scrollbooster.min.js.map |
{ | ||
"name": "scrollbooster", | ||
"description": "Enjoyable content drag-to-scroll library", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"author": "Ilya Shubin <pixelwake@gmail.com>", | ||
@@ -14,3 +14,6 @@ "license": "MIT", | ||
}, | ||
"browserslist": ["> 0.25%", "not dead"], | ||
"browserslist": [ | ||
"> 0.25%", | ||
"not dead" | ||
], | ||
"repository": { | ||
@@ -17,0 +20,0 @@ "type": "git", |
@@ -5,2 +5,4 @@ # ScrollBooster | ||
Easy to setup yet flexible enough to support any custom scrolling logic. | ||
### Installation | ||
@@ -19,3 +21,3 @@ | ||
``` html | ||
<script src="https://unpkg.com/scrollbooster@2.0.0/dist/scrollbooster.min.js"></script> | ||
<script src="https://unpkg.com/scrollbooster@2/dist/scrollbooster.min.js"></script> | ||
``` | ||
@@ -30,15 +32,5 @@ | ||
const viewport = document.querySelector('.viewport'); | ||
const content = document.querySelector('.scrollable-content'); | ||
new ScrollBooster({ | ||
viewport, | ||
content, | ||
onUpdate: (state) => { | ||
content.style.transform = `translate( | ||
${-state.position.x}px, | ||
${-state.position.y}px | ||
)`; | ||
}, | ||
// other options (see below) | ||
viewport: document.querySelector('.viewport'), | ||
scrollMode: 'transform' | ||
}); | ||
@@ -55,2 +47,3 @@ ``` | ||
content | DOM Node | viewport child element | Scrollable content element inside viewport | ||
scrollMode | String | undefined | Scroll technique - via CSS transform or natively. Could be 'transform' or 'native' | ||
direction | String | 'all' | Scroll direction. Could be 'horizontal', 'vertical' or 'all' | ||
@@ -93,2 +86,3 @@ bounce | Boolean | true | Enables elastic bounce effect when hitting viewport borders | ||
// state contains useful metrics: position, dragOffset, isDragging, isMoving, borderCollision | ||
// you can control scroll rendering manually without 'scrollMethod' option: | ||
content.style.transform = `translate( | ||
@@ -101,3 +95,3 @@ ${-state.position.x}px, | ||
// disable scroll if clicked on button | ||
const isButton = event.taget.nodeName.toLowerCase() === 'button'; | ||
const isButton = event.target.nodeName.toLowerCase() === 'button'; | ||
return !isButton; | ||
@@ -107,3 +101,3 @@ }, | ||
// prevent default link event | ||
const isLink = event.taget.nodeName.toLowerCase() === 'link'; | ||
const isLink = event.target.nodeName.toLowerCase() === 'link'; | ||
if (isLink) { | ||
@@ -117,3 +111,3 @@ event.preventDefault(); | ||
sb.updateMetrics(); | ||
sb.setPosition({ x: 100, y: 100 }); | ||
sb.scrollTo({ x: 100, y: 100 }); | ||
sb.updateOptions({ emulateScroll: false }); | ||
@@ -120,0 +114,0 @@ sb.destroy(); |
820
src/index.js
@@ -5,465 +5,527 @@ const getFullWidth = (elem) => Math.max(elem.offsetWidth, elem.scrollWidth); | ||
const textNodeFromPoint = (element, x, y) => { | ||
const nodes = element.childNodes; | ||
const range = document.createRange(); | ||
for (let i = 0; i < nodes.length; i++) { | ||
const node = nodes[i]; | ||
if (node.nodeType !== 3) { continue; } | ||
range.selectNodeContents(node); | ||
const rect = range.getBoundingClientRect(); | ||
if (x >= rect.left && y >= rect.top && x <= rect.right && y <= rect.bottom) { | ||
return node; | ||
const nodes = element.childNodes; | ||
const range = document.createRange(); | ||
for (let i = 0; i < nodes.length; i++) { | ||
const node = nodes[i]; | ||
if (node.nodeType !== 3) { | ||
continue; | ||
} | ||
range.selectNodeContents(node); | ||
const rect = range.getBoundingClientRect(); | ||
if (x >= rect.left && y >= rect.top && x <= rect.right && y <= rect.bottom) { | ||
return node; | ||
} | ||
} | ||
} | ||
return false; | ||
return false; | ||
}; | ||
const clearTextSelection = () => { | ||
const selection = window.getSelection ? window.getSelection() : document.selection; | ||
if (!selection) { return; } | ||
if (selection.removeAllRanges) { | ||
selection.removeAllRanges(); | ||
} else if (selection.empty) { | ||
selection.empty(); | ||
} | ||
const selection = window.getSelection ? window.getSelection() : document.selection; | ||
if (!selection) { | ||
return; | ||
} | ||
if (selection.removeAllRanges) { | ||
selection.removeAllRanges(); | ||
} else if (selection.empty) { | ||
selection.empty(); | ||
} | ||
}; | ||
export default class ScrollBooster { | ||
constructor(options = {}) { | ||
const defaults = { | ||
content: options.viewport.children[0], | ||
direction: 'all', // 'vertical', 'horizontal' | ||
pointerMode: 'all', // 'touch', 'mouse' | ||
bounce: true, | ||
friction: 0.05, | ||
bounceForce: 0.1, | ||
textSelection: false, | ||
inputsFocus: true, | ||
emulateScroll: false, | ||
onClick() {}, | ||
onUpdate() {}, | ||
shouldScroll() { return true } | ||
}; | ||
/** | ||
* Create ScrollBooster instance | ||
* @param {Object} options - options object | ||
* @param {Element} options.viewport - container element | ||
* @param {Element} options.content - scrollable content element | ||
* @param {String} options.direction - scroll direction | ||
* @param {String} options.pointerMode - mouse or touch support | ||
* @param {String} options.scrollMode - predefined scrolling technique | ||
* @param {Boolean} options.bounce - bounce effect | ||
* @param {Number} options.bounceForce - bounce effect factor | ||
* @param {Number} options.friction - scroll friction factor | ||
* @param {Boolean} options.textSelection - enables text selection | ||
* @param {Boolean} options.inputsFocus - enables focus on input elements | ||
* @param {Boolean} options.emulateScroll - enables mousewheel emulation | ||
* @param {Function} options.onClick - click handler | ||
* @param {Function} options.onUpdate - state update handler | ||
* @param {Function} options.shouldScroll - predicate to allow or disable scroll | ||
*/ | ||
constructor(options = {}) { | ||
const defaults = { | ||
content: options.viewport.children[0], | ||
direction: 'all', // 'vertical', 'horizontal' | ||
pointerMode: 'all', // 'touch', 'mouse' | ||
scrollMode: undefined, // 'transform', 'native' | ||
bounce: true, | ||
bounceForce: 0.1, | ||
friction: 0.05, | ||
textSelection: false, | ||
inputsFocus: true, | ||
emulateScroll: false, | ||
onClick() {}, | ||
onUpdate() {}, | ||
shouldScroll() { | ||
return true; | ||
}, | ||
}; | ||
this.props = { ...defaults, ...options }; | ||
this.props = { ...defaults, ...options }; | ||
if (!this.props.viewport || !(this.props.viewport instanceof Element)) { | ||
console.error(`ScrollBooster init error: "viewport" config property must be present and must be Element`); | ||
return; | ||
} | ||
if (!this.props.viewport || !(this.props.viewport instanceof Element)) { | ||
console.error(`ScrollBooster init error: "viewport" config property must be present and must be Element`); | ||
return; | ||
} | ||
if (!this.props.content) { | ||
console.error(`ScrollBooster init error: Viewport does not have any content`); | ||
return; | ||
} | ||
if (!this.props.content) { | ||
console.error(`ScrollBooster init error: Viewport does not have any content`); | ||
return; | ||
} | ||
this.viewport = { | ||
width: this.props.viewport.clientWidth, | ||
height: this.props.viewport.clientHeight | ||
}; | ||
this.content = { | ||
width: getFullWidth(this.props.content), | ||
height: getFullHeight(this.props.content) | ||
}; | ||
this.isDragging = false; | ||
this.isTargetScroll = false; | ||
this.isScrolling = false; | ||
this.isRunning = false; | ||
this.position = { x: 0, y: 0 }; | ||
this.velocity = { x: 0, y: 0 }; | ||
this.friction = 1 - this.props.friction; | ||
this.bounceForce = this.props.bounceForce; | ||
const START_COORDINATES = { x: 0, y: 0 }; | ||
this.isDragging = false; | ||
this.dragStartPosition = { x: 0, y: 0 }; | ||
this.dragOffset = { ...this.dragStartPosition }; | ||
this.dragPosition = { ...this.position }; | ||
this.position = { ...START_COORDINATES }; | ||
this.velocity = { ...START_COORDINATES }; | ||
this.dragStartPosition = { ...START_COORDINATES }; | ||
this.dragOffset = { ...START_COORDINATES }; | ||
this.dragPosition = { ...START_COORDINATES }; | ||
this.targetPosition = { ...START_COORDINATES }; | ||
this.scrollOffset = { ...START_COORDINATES }; | ||
this.isTargetScroll = false; | ||
this.targetPosition = { x: 0, y: 0 }; | ||
this.rafID = null; | ||
this.events = {}; | ||
this.isScrolling = false; | ||
this.scrollOffset = { x: 0, y: 0 }; | ||
this.updateMetrics(); | ||
this.handleEvents(); | ||
} | ||
this.bounce = this.props.bounce; | ||
this.textSelection = this.props.textSelection; | ||
/** | ||
* Update options object with new given values | ||
*/ | ||
updateOptions(options = {}) { | ||
this.props = { ...this.props, ...options }; | ||
this.props.onUpdate(this.getState()); | ||
this.startAnimationLoop(); | ||
} | ||
this.boundX = { | ||
from: Math.min(-this.content.width + this.viewport.width, 0), | ||
to: 0 | ||
}; | ||
this.boundY = { | ||
from: Math.min(-this.content.height + this.viewport.height, 0), | ||
to: 0 | ||
}; | ||
/** | ||
* Update DOM container elements metrics (width and height) | ||
*/ | ||
updateMetrics() { | ||
this.viewport = { | ||
width: this.props.viewport.clientWidth, | ||
height: this.props.viewport.clientHeight, | ||
}; | ||
this.content = { | ||
width: getFullWidth(this.props.content), | ||
height: getFullHeight(this.props.content), | ||
}; | ||
this.edgeX = { | ||
from: Math.min(-this.content.width + this.viewport.width, 0), | ||
to: 0, | ||
}; | ||
this.edgeY = { | ||
from: Math.min(-this.content.height + this.viewport.height, 0), | ||
to: 0, | ||
}; | ||
this.isRunning = false; | ||
this.rafID = null; | ||
this.props.onUpdate(this.getState()); | ||
this.startAnimationLoop(); | ||
} | ||
this.events = {}; | ||
/** | ||
* Run animation loop | ||
*/ | ||
startAnimationLoop() { | ||
this.isRunning = true; | ||
cancelAnimationFrame(this.rafID); | ||
this.rafID = requestAnimationFrame(() => this.animate()); | ||
} | ||
this.animate(); | ||
this.handleEvents(); | ||
} | ||
/** | ||
* Run update loop | ||
*/ | ||
run() { | ||
this.isRunning = true; | ||
cancelAnimationFrame(this.rafID); | ||
this.rafID = requestAnimationFrame(() => this.animate()); | ||
} | ||
animate() { | ||
if (!this.isRunning) { | ||
return; | ||
/** | ||
* Main animation loop | ||
*/ | ||
animate() { | ||
if (!this.isRunning) { | ||
return; | ||
} | ||
this.updateScrollPosition(); | ||
// stop animation loop if nothing moves | ||
if (!this.isMoving()) { | ||
this.isRunning = false; | ||
this.isTargetScroll = false; | ||
} | ||
const state = this.getState(); | ||
this.setContentPosition(state); | ||
this.props.onUpdate(state); | ||
this.rafID = requestAnimationFrame(() => this.animate()); | ||
} | ||
this.update(); | ||
this.props.onUpdate(this.getState()); | ||
this.rafID = requestAnimationFrame(() => this.animate()); | ||
} | ||
isMoving() { | ||
return this.isDragging || this.isScrolling || Math.abs(this.velocity.x) >= 0.01 || Math.abs(this.velocity.y) >= 0.01; | ||
} | ||
/** | ||
* Calculate and set new scroll position | ||
*/ | ||
updateScrollPosition() { | ||
this.applyEdgeForce(); | ||
this.applyDragForce(); | ||
this.applyScrollForce(); | ||
this.applyTargetForce(); | ||
update() { | ||
this.applyBoundForce(); | ||
this.applyDragForce(); | ||
this.applyScrollForce(); | ||
this.applyTargetForce(); | ||
const inverseFriction = 1 - this.props.friction; | ||
this.velocity.x *= inverseFriction; | ||
this.velocity.y *= inverseFriction; | ||
this.velocity.x *= this.friction; | ||
this.velocity.y *= this.friction; | ||
if (this.props.direction !== 'vertical') { | ||
this.position.x += this.velocity.x; | ||
} | ||
if (this.props.direction !== 'horizontal') { | ||
this.position.y += this.velocity.y; | ||
} | ||
if (this.props.direction !== 'vertical') { | ||
this.position.x += this.velocity.x; | ||
// disable bounce effect | ||
if ((!this.props.bounce || this.isScrolling) && !this.isTargetScroll) { | ||
this.position.x = Math.max(Math.min(this.position.x, this.edgeX.to), this.edgeX.from); | ||
this.position.y = Math.max(Math.min(this.position.y, this.edgeY.to), this.edgeY.from); | ||
} | ||
} | ||
if (this.props.direction !== 'horizontal') { | ||
this.position.y += this.velocity.y; | ||
} | ||
// disable bounce effect | ||
if ((!this.bounce || this.isScrolling) && !this.isTargetScroll) { | ||
this.position.x = Math.max(Math.min(this.position.x, this.boundX.to), this.boundX.from); | ||
this.position.y = Math.max(Math.min(this.position.y, this.boundY.to), this.boundY.from); | ||
/** | ||
* Increase general scroll velocity by given force amount | ||
*/ | ||
applyForce(force) { | ||
this.velocity.x += force.x; | ||
this.velocity.y += force.y; | ||
} | ||
// stop update loop if nothing moves | ||
if (!this.isMoving()) { | ||
this.isRunning = false; | ||
this.isTargetScroll = false; | ||
} | ||
} | ||
/** | ||
* Apply force for bounce effect | ||
*/ | ||
applyEdgeForce() { | ||
if (!this.props.bounce || this.isDragging) { | ||
return; | ||
} | ||
applyForce(force) { | ||
this.velocity.x += force.x; | ||
this.velocity.y += force.y; | ||
} | ||
// scrolled past viewport edges | ||
const beyondXFrom = this.position.x < this.edgeX.from; | ||
const beyondXTo = this.position.x > this.edgeX.to; | ||
const beyondYFrom = this.position.y < this.edgeY.from; | ||
const beyondYTo = this.position.y > this.edgeY.to; | ||
const beyondX = beyondXFrom || beyondXTo; | ||
const beyondY = beyondYFrom || beyondYTo; | ||
/** | ||
* Apply force for bounce effect | ||
*/ | ||
applyBoundForce() { | ||
if (!this.bounce || this.isDragging) { | ||
return; | ||
} | ||
if (!beyondX && !beyondY) { | ||
return; | ||
} | ||
// scrolled past viewport boundaries | ||
const beyondXFrom = this.position.x < this.boundX.from; | ||
const beyondXTo = this.position.x > this.boundX.to; | ||
const beyondYFrom = this.position.y < this.boundY.from; | ||
const beyondYTo = this.position.y > this.boundY.to; | ||
const beyondX = beyondXFrom || beyondXTo; | ||
const beyondY = beyondYFrom || beyondYTo; | ||
const edge = { | ||
x: beyondXFrom ? this.edgeX.from : this.edgeX.to, | ||
y: beyondYFrom ? this.edgeY.from : this.edgeY.to, | ||
}; | ||
if (!beyondX && !beyondY) { | ||
return; | ||
} | ||
const distanceToEdge = { | ||
x: edge.x - this.position.x, | ||
y: edge.y - this.position.y, | ||
}; | ||
const bound = { | ||
x: beyondXFrom ? this.boundX.from : this.boundX.to, | ||
y: beyondYFrom ? this.boundY.from : this.boundY.to, | ||
}; | ||
const force = { | ||
x: distanceToEdge.x * this.props.bounceForce, | ||
y: distanceToEdge.y * this.props.bounceForce, | ||
}; | ||
const distanceToBound = { | ||
x: bound.x - this.position.x, | ||
y: bound.y - this.position.y, | ||
}; | ||
const restPosition = { | ||
x: this.position.x + (this.velocity.x + force.x) / this.props.friction, | ||
y: this.position.y + (this.velocity.y + force.y) / this.props.friction, | ||
}; | ||
const force = { | ||
x: distanceToBound.x * this.bounceForce, | ||
y: distanceToBound.y * this.bounceForce, | ||
}; | ||
if ((beyondXFrom && restPosition.x >= this.edgeX.from) || (beyondXTo && restPosition.x <= this.edgeX.to)) { | ||
force.x = distanceToEdge.x * this.props.bounceForce - this.velocity.x; | ||
} | ||
const restPosition = { | ||
x: this.position.x + (this.velocity.x + force.x) / (1 - this.friction), | ||
y: this.position.y + (this.velocity.y + force.y) / (1 - this.friction), | ||
}; | ||
if ((beyondYFrom && restPosition.y >= this.edgeY.from) || (beyondYTo && restPosition.y <= this.edgeY.to)) { | ||
force.y = distanceToEdge.y * this.props.bounceForce - this.velocity.y; | ||
} | ||
if ((beyondXFrom && restPosition.x >= this.boundX.from) || (beyondXTo && restPosition.x <= this.boundX.to)) { | ||
force.x = distanceToBound.x * this.bounceForce - this.velocity.x; | ||
this.applyForce({ | ||
x: beyondX ? force.x : 0, | ||
y: beyondY ? force.y : 0, | ||
}); | ||
} | ||
if ((beyondYFrom && restPosition.y >= this.boundY.from) || (beyondYTo && restPosition.y <= this.boundY.to)) { | ||
force.y = distanceToBound.y * this.bounceForce - this.velocity.y; | ||
} | ||
/** | ||
* Apply force to move content while dragging with mouse/touch | ||
*/ | ||
applyDragForce() { | ||
if (!this.isDragging) { | ||
return; | ||
} | ||
this.applyForce({ | ||
x: beyondX ? force.x : 0, | ||
y: beyondY ? force.y : 0, | ||
}); | ||
} | ||
const dragVelocity = { | ||
x: this.dragPosition.x - this.position.x, | ||
y: this.dragPosition.y - this.position.y, | ||
}; | ||
/** | ||
* Apply force to move content while dragging with mouse/touch | ||
*/ | ||
applyDragForce() { | ||
if (!this.isDragging) { | ||
return; | ||
this.applyForce({ | ||
x: dragVelocity.x - this.velocity.x, | ||
y: dragVelocity.y - this.velocity.y, | ||
}); | ||
} | ||
const dragVelocity = { | ||
x: this.dragPosition.x - this.position.x, | ||
y: this.dragPosition.y - this.position.y | ||
}; | ||
/** | ||
* Apply force to emulate mouse wheel or trackpad | ||
*/ | ||
applyScrollForce() { | ||
if (!this.isScrolling) { | ||
return; | ||
} | ||
this.applyForce({ | ||
x: dragVelocity.x - this.velocity.x, | ||
y: dragVelocity.y - this.velocity.y | ||
}); | ||
} | ||
this.applyForce({ | ||
x: this.scrollOffset.x - this.velocity.x, | ||
y: this.scrollOffset.y - this.velocity.y, | ||
}); | ||
/** | ||
* Apply force to emulate mouse wheel or trackpad | ||
*/ | ||
applyScrollForce() { | ||
if (!this.isScrolling) { | ||
return; | ||
this.scrollOffset.x = 0; | ||
this.scrollOffset.y = 0; | ||
} | ||
this.applyForce({ | ||
x: this.scrollOffset.x - this.velocity.x, | ||
y: this.scrollOffset.y - this.velocity.y | ||
}); | ||
/** | ||
* Apply force to scroll to given target coordinate | ||
*/ | ||
applyTargetForce() { | ||
if (!this.isTargetScroll) { | ||
return; | ||
} | ||
this.scrollOffset.x = 0; | ||
this.scrollOffset.y = 0; | ||
} | ||
this.applyForce({ | ||
x: (this.targetPosition.x - this.position.x) * 0.08 - this.velocity.x, | ||
y: (this.targetPosition.y - this.position.y) * 0.08 - this.velocity.y, | ||
}); | ||
} | ||
applyTargetForce() { | ||
if (!this.isTargetScroll) { | ||
return | ||
/** | ||
* Check if scrolling happening | ||
*/ | ||
isMoving() { | ||
return ( | ||
this.isDragging || | ||
this.isScrolling || | ||
Math.abs(this.velocity.x) >= 0.01 || | ||
Math.abs(this.velocity.y) >= 0.01 | ||
); | ||
} | ||
this.applyForce({ | ||
x: (this.targetPosition.x - this.position.x) * 0.08 - this.velocity.x, | ||
y: (this.targetPosition.y - this.position.y) * 0.08 - this.velocity.y, | ||
}); | ||
} | ||
/** | ||
* Set scroll target coordinate for smooth scroll | ||
*/ | ||
scrollTo(position = {}) { | ||
this.isTargetScroll = true; | ||
this.targetPosition.x = -position.x || 0; | ||
this.targetPosition.y = -position.y || 0; | ||
this.startAnimationLoop(); | ||
} | ||
/** | ||
* Smooth scroll to target position | ||
*/ | ||
scrollTo(position = {}) { | ||
this.isTargetScroll = true; | ||
this.targetPosition.x = -position.x || 0; | ||
this.targetPosition.y = -position.y || 0; | ||
this.run(); | ||
} | ||
/** | ||
* Manual position setting | ||
*/ | ||
setPosition(position = {}) { | ||
this.velocity.x = 0; | ||
this.velocity.y = 0; | ||
this.position.x = -position.x || 0; | ||
this.position.y = -position.y || 0; | ||
this.startAnimationLoop(); | ||
} | ||
/** | ||
* Manual position setting | ||
*/ | ||
setPosition(position = {}) { | ||
this.velocity.x = 0; | ||
this.velocity.y = 0; | ||
this.position.x = -position.x || 0; | ||
this.position.y = -position.y || 0; | ||
this.run(); | ||
} | ||
/** | ||
* Get latest metrics and coordinates | ||
*/ | ||
getState() { | ||
return { | ||
isMoving: this.isMoving(), | ||
isDragging: !!(this.dragOffset.x || this.dragOffset.y), | ||
position: { x: -this.position.x, y: -this.position.y }, | ||
dragOffset: this.dragOffset, | ||
borderCollision: { | ||
left: this.position.x >= this.edgeX.to, | ||
right: this.position.x <= this.edgeX.from, | ||
top: this.position.y >= this.edgeY.to, | ||
bottom: this.position.y <= this.edgeY.from, | ||
}, | ||
}; | ||
} | ||
/** | ||
* Get latest metrics and coordinates | ||
*/ | ||
getState() { | ||
return { | ||
isMoving: this.isMoving(), | ||
isDragging: !!(this.dragOffset.x || this.dragOffset.y), | ||
position: { x: -this.position.x, y: -this.position.y }, | ||
dragOffset: this.dragOffset, | ||
borderCollision: { | ||
left: this.position.x >= this.boundX.to, | ||
right: this.position.x <= this.boundX.from, | ||
top: this.position.y >= this.boundY.to, | ||
bottom: this.position.y <= this.boundY.from, | ||
} | ||
}; | ||
} | ||
/** | ||
* Update DOM container elements metrics (width and height) | ||
*/ | ||
setContentPosition(state) { | ||
if (this.props.scrollMode === 'transform') { | ||
this.props.content.style.transform = `translate(${-state.position.x}px, ${-state.position.y}px)`; | ||
} | ||
if (this.props.scrollMode === 'native') { | ||
this.props.viewport.scrollTop = state.position.y; | ||
this.props.viewport.scrollLeft = state.position.x; | ||
} | ||
} | ||
updateMetrics() { | ||
this.viewport.width = this.props.viewport.clientWidth; | ||
this.viewport.height = this.props.viewport.clientHeight; | ||
/** | ||
* Register all DOM events | ||
*/ | ||
handleEvents() { | ||
const dragOrigin = { x: 0, y: 0 }; | ||
let wheelTimer = null; | ||
let isTouch = false; | ||
this.content.width = getFullWidth(this.props.content); | ||
this.content.height = getFullHeight(this.props.content); | ||
const setDragPosition = (event) => { | ||
if (!this.isDragging) { | ||
return; | ||
} | ||
this.boundX.from = Math.min(-this.content.width + this.viewport.width, 0); | ||
this.boundY.from = Math.min(-this.content.height + this.viewport.height, 0); | ||
const pageX = isTouch ? event.touches[0].pageX : event.pageX; | ||
const pageY = isTouch ? event.touches[0].pageY : event.pageY; | ||
this.run(); | ||
} | ||
this.dragOffset.x = pageX - dragOrigin.x; | ||
this.dragOffset.y = pageY - dragOrigin.y; | ||
handleEvents() { | ||
const dragOrigin = { x: 0, y: 0 }; | ||
let wheelTimer = null; | ||
let isTouch = false; | ||
this.dragPosition.x = this.dragStartPosition.x + this.dragOffset.x; | ||
this.dragPosition.y = this.dragStartPosition.y + this.dragOffset.y; | ||
}; | ||
const setDragPosition = (event) => { | ||
if (!this.isDragging) { | ||
return | ||
} | ||
this.events.pointerdown = (event) => { | ||
isTouch = !!(event.touches && event.touches[0]); | ||
const pageX = isTouch ? event.touches[0].pageX : event.pageX; | ||
const pageY = isTouch ? event.touches[0].pageY: event.pageY; | ||
const eventData = isTouch ? event.touches[0] : event; | ||
const { pageX, pageY, clientX, clientY } = eventData; | ||
this.dragOffset.x = pageX - dragOrigin.x; | ||
this.dragOffset.y = pageY - dragOrigin.y; | ||
const { viewport } = this.props; | ||
const rect = viewport.getBoundingClientRect(); | ||
this.dragPosition.x = this.dragStartPosition.x + this.dragOffset.x; | ||
this.dragPosition.y = this.dragStartPosition.y + this.dragOffset.y; | ||
}; | ||
// click on vertical scrollbar | ||
if (clientX - rect.left >= viewport.clientLeft + viewport.clientWidth) { | ||
return; | ||
} | ||
this.events.pointerdown = (event) => { | ||
isTouch = !!(event.touches && event.touches[0]); | ||
// click on horizontal scrollbar | ||
if (clientY - rect.top >= viewport.clientTop + viewport.clientHeight) { | ||
return; | ||
} | ||
const eventData = isTouch ? event.touches[0] : event; | ||
const { pageX, pageY, clientX, clientY } = eventData | ||
// interaction disabled by user | ||
if (!this.props.shouldScroll(this.getState(), event)) { | ||
return; | ||
} | ||
const { viewport } = this.props; | ||
const rect = viewport.getBoundingClientRect(); | ||
// disable right mouse button scroll | ||
if (event.button === 2) { | ||
return; | ||
} | ||
// click on vertical scrollbar | ||
if (clientX - rect.left >= viewport.clientLeft + viewport.clientWidth) { | ||
return; | ||
} | ||
// disable on mobile | ||
if (this.props.pointerMode === 'mouse' && isTouch) { | ||
return; | ||
} | ||
// click on horizontal scrollbar | ||
if (clientY - rect.top >= viewport.clientTop + viewport.clientHeight) { | ||
return; | ||
} | ||
// disable on desktop | ||
if (this.props.pointerMode === 'touch' && !isTouch) { | ||
return; | ||
} | ||
// interaction disabled by user | ||
if (!this.props.shouldScroll(this.getState(), event)) { | ||
return; | ||
} | ||
// focus on form input elements | ||
const formNodes = ['input', 'textarea', 'button', 'select', 'label']; | ||
if (this.props.inputsFocus && formNodes.indexOf(event.target.nodeName.toLowerCase()) > -1) { | ||
return; | ||
} | ||
// disable on mobile | ||
if (this.props.pointerMode === 'mouse' && isTouch) { | ||
return; | ||
} | ||
// handle text selection | ||
if (this.props.textSelection) { | ||
const textNode = textNodeFromPoint(event.target, clientX, clientY); | ||
if (textNode) { | ||
return; | ||
} | ||
clearTextSelection(); | ||
} | ||
// disable on desktop | ||
if (this.props.pointerMode === 'touch' && !isTouch) { | ||
return; | ||
} | ||
this.isDragging = true; | ||
// focus on form input elements | ||
const formNodes = ['input', 'textarea', 'button', 'select', 'label']; | ||
if (this.props.inputsFocus && formNodes.indexOf(event.target.nodeName.toLowerCase()) > -1) { | ||
return; | ||
} | ||
dragOrigin.x = pageX; | ||
dragOrigin.y = pageY; | ||
this.dragStartPosition.x = this.position.x; | ||
this.dragStartPosition.y = this.position.y; | ||
// handle text selection | ||
if (this.textSelection) { | ||
const textNode = textNodeFromPoint(event.target, clientX, clientY); | ||
if (textNode) { | ||
return; | ||
} | ||
clearTextSelection(); | ||
} | ||
setDragPosition(event); | ||
this.startAnimationLoop(); | ||
event.preventDefault(); | ||
}; | ||
this.isDragging = true; | ||
this.events.pointermove = (event) => { | ||
setDragPosition(event); | ||
}; | ||
dragOrigin.x = pageX; | ||
dragOrigin.y = pageY; | ||
this.dragStartPosition.x = this.position.x; | ||
this.dragStartPosition.y = this.position.y; | ||
this.events.pointerup = () => { | ||
this.isDragging = false; | ||
}; | ||
setDragPosition(event); | ||
this.run(); | ||
event.preventDefault() | ||
}; | ||
this.events.wheel = (event) => { | ||
if (!this.props.emulateScroll) { | ||
return; | ||
} | ||
this.velocity.x = 0; | ||
this.velocity.y = 0; | ||
this.isScrolling = true; | ||
this.events.pointermove = (event) => { | ||
setDragPosition(event); | ||
}; | ||
this.scrollOffset.x = -event.deltaX; | ||
this.scrollOffset.y = -event.deltaY; | ||
this.events.pointerup = () => { | ||
this.isDragging = false; | ||
}; | ||
this.startAnimationLoop(); | ||
this.events.wheel = (event) => { | ||
if (!this.props.emulateScroll) { | ||
return; | ||
} | ||
this.velocity.x = 0; | ||
this.velocity.y = 0; | ||
this.isScrolling = true; | ||
clearTimeout(wheelTimer); | ||
wheelTimer = setTimeout(() => (this.isScrolling = false), 80); | ||
event.preventDefault(); | ||
}; | ||
this.scrollOffset.x = -event.deltaX; | ||
this.scrollOffset.y = -event.deltaY; | ||
this.events.scroll = () => { | ||
const { scrollLeft, scrollTop } = this.props.viewport; | ||
if (Math.abs(this.position.x + scrollLeft) > 3) { | ||
this.position.x = -scrollLeft; | ||
this.velocity.x = 0; | ||
} | ||
if (Math.abs(this.position.y + scrollTop) > 3) { | ||
this.position.y = -scrollTop; | ||
this.velocity.y = 0; | ||
} | ||
}; | ||
this.run(); | ||
this.events.click = (event) => this.props.onClick(this.getState(), event); | ||
this.events.contentLoad = () => this.updateMetrics(); | ||
this.events.resize = () => this.updateMetrics(); | ||
clearTimeout(wheelTimer); | ||
wheelTimer = setTimeout(() => (this.isScrolling = false), 80); | ||
event.preventDefault(); | ||
}; | ||
this.props.viewport.addEventListener('mousedown', this.events.pointerdown); | ||
this.props.viewport.addEventListener('touchstart', this.events.pointerdown); | ||
this.props.viewport.addEventListener('click', this.events.click); | ||
this.props.viewport.addEventListener('wheel', this.events.wheel); | ||
this.props.viewport.addEventListener('scroll', this.events.scroll); | ||
this.props.content.addEventListener('load', this.events.contentLoad, true); | ||
window.addEventListener('mousemove', this.events.pointermove); | ||
window.addEventListener('touchmove', this.events.pointermove); | ||
window.addEventListener('mouseup', this.events.pointerup); | ||
window.addEventListener('touchend', this.events.pointerup); | ||
window.addEventListener('resize', this.events.resize); | ||
} | ||
this.events.scroll = () => { | ||
const { scrollLeft, scrollTop } = this.props.viewport; | ||
if (Math.abs(this.position.x + scrollLeft) > 3) { | ||
this.position.x = -scrollLeft; | ||
this.velocity.x = 0; | ||
} | ||
if (Math.abs(this.position.y + scrollTop) > 3) { | ||
this.position.y = -scrollTop; | ||
this.velocity.y = 0; | ||
} | ||
}; | ||
this.events.click = (event) => this.props.onClick(this.getState(), event); | ||
this.events.contentLoad = () => this.updateMetrics(); | ||
this.events.resize = () => this.updateMetrics(); | ||
this.props.viewport.addEventListener('mousedown', this.events.pointerdown); | ||
this.props.viewport.addEventListener('touchstart', this.events.pointerdown); | ||
this.props.viewport.addEventListener('click', this.events.click); | ||
this.props.viewport.addEventListener('wheel', this.events.wheel); | ||
this.props.viewport.addEventListener('scroll', this.events.scroll); | ||
this.props.content.addEventListener('load', this.events.contentLoad, true); | ||
window.addEventListener('mousemove', this.events.pointermove); | ||
window.addEventListener('touchmove', this.events.pointermove); | ||
window.addEventListener('mouseup', this.events.pointerup); | ||
window.addEventListener('touchend', this.events.pointerup); | ||
window.addEventListener('resize', this.events.resize); | ||
} | ||
updateOptions(options = {}) { | ||
this.props = { ...this.props, ...options }; | ||
this.props.onUpdate(this.getState()); | ||
this.run(); | ||
} | ||
destroy() { | ||
this.props.viewport.removeEventListener('mousedown', this.events.pointerdown); | ||
this.props.viewport.removeEventListener('touchstart', this.events.pointerdown); | ||
this.props.viewport.removeEventListener('click', this.events.click); | ||
this.props.viewport.removeEventListener('wheel', this.events.wheel); | ||
this.props.viewport.removeEventListener('scroll', this.events.scroll); | ||
this.props.content.removeEventListener('load', this.events.contentLoad); | ||
window.removeEventListener('mousemove', this.events.pointermove); | ||
window.removeEventListener('touchmove', this.events.pointermove); | ||
window.removeEventListener('mouseup', this.events.pointerup); | ||
window.removeEventListener('touchend', this.events.pointerup); | ||
window.removeEventListener('resize', this.events.resize); | ||
} | ||
/** | ||
* Unregister all DOM events | ||
*/ | ||
destroy() { | ||
this.props.viewport.removeEventListener('mousedown', this.events.pointerdown); | ||
this.props.viewport.removeEventListener('touchstart', this.events.pointerdown); | ||
this.props.viewport.removeEventListener('click', this.events.click); | ||
this.props.viewport.removeEventListener('wheel', this.events.wheel); | ||
this.props.viewport.removeEventListener('scroll', this.events.scroll); | ||
this.props.content.removeEventListener('load', this.events.contentLoad); | ||
window.removeEventListener('mousemove', this.events.pointermove); | ||
window.removeEventListener('touchmove', this.events.pointermove); | ||
window.removeEventListener('mouseup', this.events.pointerup); | ||
window.removeEventListener('touchend', this.events.pointerup); | ||
window.removeEventListener('resize', this.events.resize); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
97603
20
669
122