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.events.callbacks.onScrollResize.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"); 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(){ 107 this.moveBar.call(this); 108 this.DOM.scope.classList.toggle('fakeScroll--hasBar', this.state.scrollRatio < 1) 109 110 // debounce - get track bounds 111 clearTimeout(this.listeners.timeout__resize); 112 this.listeners.timeout__resize = setTimeout(this.getTrackBounds.bind(this), 200) 113 }, 114 115 onDrag(e){ 116 var delta = e.pageY - this.state.lastPageY; 117 118 raf(() => { 119 var sTop = document.documentElement.scrollTop, 120 isDragWithinTrackBounds = e.pageY >= (this.state.trackBounds.top + sTop) && e.pageY <= (this.state.trackBounds.bottom + sTop); 121 122 if( isDragWithinTrackBounds ) 123 this.DOM.scrollContent.scrollTop = this.state.drag + delta / this.state.scrollRatio; 124 // update variables when mouse position is outside the Track bounds 125 else{ 126 this.state.drag = this.DOM.scrollContent.scrollTop; 127 this.state.lastPageY = e.pageY; 128 } 129 }); 130 }, 131 132 onStopDrag(e){ 133 [this.DOM.bar, document.body].map(el => el.classList.remove('fakeScroll--grabbed')) 134 this.events.drag.call(this, 'off'); 135 setTimeout(()=>{ this.state.drag = false }) 136 }, 137 138 onBarMouseDown(e){ 139 this.state.drag = this.DOM.scrollContent.scrollTop; 140 this.state.lastPageY = e.pageY; 141 142 [this.DOM.bar, document.body].map(el => el.classList.add('fakeScroll--grabbed')) 143 this.events.drag.call(this, 'on'); 144 }, 145 146 onTrackClick(e){ 147 if( this.state.drag ) return; 148 149 var perc = (e.clientY - this.state.trackBounds.top) / (this.state.trackBounds.height - this.state.trackBounds.topPad - this.state.trackBounds.bottomPad), 150 scrollHeight = this.DOM.scrollContent.scrollHeight, 151 ownHeight = this.DOM.scrollWrap.clientHeight, 152 newScrollTop = perc * (scrollHeight - ownHeight); 153 154 if( this.settings.track == 'smooth' ){ 155 this.DOM.scrollContent.style.scrollBehavior = 'smooth'; 156 setTimeout(()=>{ this.DOM.scrollContent.style.scrollBehavior = 'unset' }, 500) 157 } 158 159 this.DOM.scrollContent.scrollTop = newScrollTop; 160 } 161 } 162 }, 163 164 getTrackBounds(){ 165 var bounds = this.DOM.track.getBoundingClientRect(), 166 styles = window.getComputedStyle(this.DOM.track, null); 167 168 bounds.topPad = parseInt(styles.paddingTop, 10); 169 bounds.bottomPad = parseInt(styles.paddingBottom, 10); 170 171 this.state.trackBounds = bounds; 172 return bounds; 173 }, 174 175 moveBar(){ 176 // if( !this.DOM.scrollContent ) return false;
177 178 var _scrollContent = this.DOM.scrollContent, 179 scrollHeight = _scrollContent.scrollHeight, 180 ownHeight = this.DOM.scrollWrap.clientHeight; 181 182 this.state.scrollRatio = this.DOM.track.clientHeight / scrollHeight; 183 184 // update fake scrollbar location on the Y axis using requestAnimationFrame 185 raf(()=> { 186 var height = (ownHeight / scrollHeight) * 100, 187 top = (_scrollContent.scrollTop / scrollHeight ) * 100; 188 189 this.DOM.bar.style.cssText = `height : ${height}%; 190 top : ${top}%; 191 display : ${scrollHeight <= ownHeight ? 'none' : ''}`; 192 }); 193 } 194 } 195 196 /** 197 * Extend the DOM with "fakeScroll" method. The chances of the same name already be taken are slim to none, 198 * But you should now; it's your code you're putting this into. 199 */ 200 Element.prototype.fakeScroll = function( settings ){ 201 this._fakeScroll = this._fakeScroll || new FakeScroll(this, settings || {}); 202 return this._fakeScroll; 203 } 204})();
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.