1/** 2 * By Yair Even Or (C) 3 * https://github.com/yairEO/fakescroll 4 */ 5;(function(){ 6 raf = window.requestAnimationFrame || function(cb) { return window.setTimeout(cb, 1000 / 60) }; 7 8 function FakeScroll(targetElm, settings){ 9 if( !targetElm ) return; 10 11 this.settings = Object.assign({}, this.defaults, settings || {}); 12 this.callback = settings.callback ? settings.callback : null; 13 14 this.state = {}; 15 this.listeners = {}; 16 17 this.DOM = this.build(targetElm); 18 this.events.binding.call(this, this.DOM); 19 20 // run "moveBar" once 21 setTimeout(this.moveBar.bind(this)); 22 } 23 24 FakeScroll.prototype = { 25 defaults : { 26 classname : "", 27 track : false // "smooth" will enable smooth scroll 28 }, 29 30 /** 31 * Build the DOM needed 32 */ 33 build( targetElm ){ 34 var DOM = {}; 35 scopeHTML = `<div class="fakeScroll__wrap"> 36 <div class="fakeScroll__content"></div> 37 </div> 38 <div class='fakeScroll__track ${this.settings.classname}'> 39 <div class="fakeScroll__bar"></div> 40 </div>`, 41 fragment = document.createDocumentFragment(); 42 43 // move all the children of the target element into a fragment 44 while( targetElm.childNodes.length ){ 45 fragment.appendChild(targetElm.childNodes[0]); 46 } 47 48 targetElm.insertAdjacentHTML('afterbegin', scopeHTML); 49 50 DOM.scope = targetElm; 51 DOM.scrollWrap = targetElm.firstElementChild; 52 DOM.scrollContent = DOM.scrollWrap.firstElementChild; 53 DOM.scrollContent.appendChild(fragment); 54 55 DOM.track = DOM.scrollWrap.nextElementSibling; 56 DOM.bar = DOM.track.firstElementChild; 57 58 DOM.scope.classList.add("fakeScroll__scope"); 59 60 return DOM; 61 }, 62 63 destroy(){ 64 this.events.off.call(this, window, 'resize', 'onScrollResize'); 65 }, 66 67 events : { 68 on(elm, eName, cbName){ 69 // to be able tp unbind the events, callback refferece must be saved somewhere 70 eName.split(' ').forEach(e => { 71 if( !(cbName in this.events.callbacks) ) console.warn(cbName, " doesn't exist in Callbacks: ", this.events.callbacks); 72 73 this.listeners[e] = this.events.callbacks[cbName].bind(this); 74 elm.addEventListener(e, this.listeners[e]) 75 }); 76 77 return this.events; 78 }, 79 80 off(elm, eName, cbName){ 81 eName.split(' ').forEach(e => elm.removeEventListener(e, this.listeners[e])) 82 return this.events; 83 }, 84 85 binding(DOM){ 86 this.events.on.call(this, DOM.scrollContent, 'scroll', 'onScrollResize') 87 .on.call(this, DOM.scope, 'mouseenter', 'onScrollResize') 88 .on.call(this, DOM.bar, 'mousedown', 'onBarMouseDown') 89 .on.call(this, window, 'resize', 'onScrollResize') 90 91 if( this.settings.track ) 92 this.events.on.call(this, DOM.track, 'click', 'onTrackClick') 93 }, 94 95 /** 96 * events only binded when Bar element gets a "mousedown" event 97 * @param {[type]} onOff [description] 98 * @return {[type]} [description] 99 */ 100 drag(onOff){ 101 this.events[onOff].call(this, document, 'mousemove', 'onDrag') 102 [onOff].call(this, document, 'mouseup', 'onStopDrag') 103 }, 104 105 callbacks : { 106 onScrollResize(e){ 107 this.moveBar.call(this); 108 109 // debounce - get track bounds 110 clearTimeout(this.listeners.timeout__resize); 111 this.listeners.timeout__resize = setTimeout(this.getTrackBounds.bind(this), 200) 112 }, 113 114 onDrag(e){ 115 var delta = e.pageY - this.state.lastPageY; 116 117 raf(() => { 118 var sTop = document.documentElement.scrollTop, 119 isDragWithinTrackBounds = e.pageY >= (this.state.trackBounds.top + sTop) && e.pageY <= (this.state.trackBounds.bottom + sTop); 120 121 if( isDragWithinTrackBounds ) 122 this.DOM.scrollContent.scrollTop = this.state.drag + delta / this.state.scrollRatio; 123 // update variables when mouse position is outside the Track bounds 124 else{ 125 this.state.drag = this.DOM.scrollContent.scrollTop; 126 this.state.lastPageY = e.pageY; 127 } 128 }); 129 }, 130 131 onStopDrag(e){ 132 [this.DOM.bar, document.body].map(el => el.classList.remove('fakeScroll--grabbed')) 133 this.events.drag.call(this, 'off'); 134 setTimeout(()=>{ this.state.drag = false }) 135 }, 136 137 onBarMouseDown(e){ 138 this.state.drag = this.DOM.scrollContent.scrollTop; 139 this.state.lastPageY = e.pageY; 140 141 [this.DOM.bar, document.body].map(el => el.classList.add('fakeScroll--grabbed')) 142 this.events.drag.call(this, 'on'); 143 }, 144 145 onTrackClick(e){ 146 if( this.state.drag ) return; 147 148 var perc = (e.clientY - this.state.trackBounds.top) / (this.state.trackBounds.height - this.state.trackBounds.topPad - this.state.trackBounds.bottomPad), 149 scrollHeight = this.DOM.scrollContent.scrollHeight, 150 ownHeight = this.DOM.scrollWrap.clientHeight, 151 newScrollTop = perc * (scrollHeight - ownHeight); 152 153 if( this.settings.track == 'smooth' ){ 154 this.DOM.scrollContent.style.scrollBehavior = 'smooth'; 155 setTimeout(()=>{ this.DOM.scrollContent.style.scrollBehavior = 'unset' }, 500) 156 } 157 158 this.DOM.scrollContent.scrollTop = newScrollTop; 159 } 160 } 161 }, 162 163 getTrackBounds(){ 164 var bounds = this.DOM.track.getBoundingClientRect(), 165 styles = window.getComputedStyle(this.DOM.track, null); 166 167 bounds.topPad = parseInt(styles.paddingTop, 10); 168 bounds.bottomPad = parseInt(styles.paddingBottom, 10); 169 170 this.state.trackBounds = bounds; 171 return bounds; 172 }, 173 174 moveBar(){ 175 // if( !this.DOM.scrollContent ) return false;
176 177 var _scrollContent = this.DOM.scrollContent, 178 scrollHeight = _scrollContent.scrollHeight, 179 ownHeight = this.DOM.scrollWrap.clientHeight; 180 181 this.state.scrollRatio = this.DOM.track.clientHeight / scrollHeight; 182 183 // update fake scrollbar location on the Y axis using requestAnimationFrame 184 raf(()=> { 185 var height = (ownHeight / scrollHeight) * 100, 186 top = (_scrollContent.scrollTop / scrollHeight ) * 100; 187 188 this.DOM.bar.style.cssText = `height : ${height}%; 189 top : ${top}%; 190 display : ${scrollHeight <= ownHeight ? 'none' : ''}`; 191 }); 192 } 193 } 194 195 /** 196 * Extend the DOM with "fakeScroll" method. The chances of the same name already be taken are slim to none, 197 * But you should now; it's your code you're putting this into. 198 */ 199 Element.prototype.fakeScroll = function( settings ){ 200 this._fakeScroll = this._fakeScroll || new FakeScroll(this, settings || {}); 201 return this._fakeScroll; 202 } 203})();
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.