1/* 2 * FixedScrollBlock 3 */ 4;(function($, window) { 5 'use strict'; 6 var isMobileDevice = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch) || /Windows Phone/.test(navigator.userAgent); 7 8 function FixedScrollBlock(options) { 9 this.options = $.extend({ 10 fixedActiveClass: 'fixed-position', 11 slideBlock: '[data-scroll-block]', 12 positionType: 'auto', 13 fixedOnlyIfFits: true, 14 container: null, 15 animDelay: 100, 16 animSpeed: 200, 17 extraBottom: 0, 18 extraTop: 0 19 }, options); 20 this.initStructure(); 21 this.attachEvents(); 22 } 23 FixedScrollBlock.prototype = { 24 initStructure: function() { 25 // find elements 26 this.win = $(window); 27 this.container = $(this.options.container); 28 this.slideBlock = this.container.find(this.options.slideBlock); 29 30 // detect method 31 if(this.options.positionType === 'auto') { 32 this.options.positionType = isMobileDevice ? 'absolute' : 'fixed'; 33 } 34 }, 35 attachEvents: function() { 36 var self = this; 37 38 // bind events 39 this.onResize = function() { 40 self.resizeHandler(); 41 }; 42 this.onScroll = function() { 43 self.scrollHandler(); 44 }; 45 46 // handle events 47 this.win.on({ 48 resize: this.onResize, 49 scroll: this.onScroll 50 }); 51 52 // initial handler call 53 this.resizeHandler(); 54 }, 55 recalculateOffsets: function() { 56 var defaultOffset = this.slideBlock.offset(), 57 defaultPosition = this.slideBlock.position(), 58 holderOffset = this.container.offset(), 59 windowSize = this.win.height(); 60 61 this.data = { 62 windowHeight: this.win.height(), 63 windowWidth: this.win.width(), 64 65 blockPositionLeft: defaultPosition.left, 66 blockPositionTop: defaultPosition.top, 67 68 blockOffsetLeft: defaultOffset.left, 69 blockOffsetTop: defaultOffset.top, 70 blockHeight: this.slideBlock.innerHeight(), 71 72 holderOffsetLeft: holderOffset.left, 73 holderOffsetTop: holderOffset.top, 74 holderHeight: this.container.innerHeight() 75 }; 76 }, 77 isVisible: function() { 78 return this.slideBlock.prop('offsetHeight'); 79 }, 80 fitsInViewport: function() { 81 if(this.options.fixedOnlyIfFits && this.data) { 82 return this.data.blockHeight + this.options.extraTop <= this.data.windowHeight; 83 } else { 84 return true; 85 } 86 }, 87 resizeHandler: function() { 88 if (this.destroyed) return; 89 if(this.isVisible()) { 90 FixedScrollBlock.stickyMethods[this.options.positionType].onResize.apply(this, arguments); 91 this.scrollHandler(); 92 } 93 }, 94 scrollHandler: function() { 95 if (this.destroyed) return; 96 if(this.isVisible()) { 97 if(!this.data) { 98 this.resizeHandler(); 99 return; 100 } 101 this.currentScrollTop = this.win.scrollTop(); 102 this.currentScrollLeft = this.win.scrollLeft(); 103 FixedScrollBlock.stickyMethods[this.options.positionType].onScroll.apply(this, arguments); 104 } 105 }, 106 refresh: function() { 107 // refresh dimensions and state if needed 108 if(this.data) { 109 this.data.holderHeight = this.container.innerHeight(); 110 this.data.blockHeight = this.slideBlock.innerHeight(); 111 this.scrollHandler(); 112 } 113 }, 114 destroy: function() { 115 this.destroyed = true; 116 // remove event handlers and styles 117 this.slideBlock.removeAttr('style').removeClass(this.options.fixedActiveClass); 118 this.win.off({ 119 resize: this.onResize, 120 scroll: this.onScroll 121 }); 122 } 123 }; 124 125 // sticky methods 126 FixedScrollBlock.stickyMethods = { 127 fixed: { 128 onResize: function() { 129 this.slideBlock.removeAttr('style'); 130 this.recalculateOffsets(); 131 }, 132 onScroll: function() { 133 if(this.fitsInViewport() && this.currentScrollTop + this.options.extraTop > this.data.blockOffsetTop) { 134 if(this.currentScrollTop + this.options.extraTop + this.data.blockHeight > this.data.holderOffsetTop + this.data.holderHeight - this.options.extraBottom) { 135 this.slideBlock.css({ 136 position: 'absolute', 137 top: this.data.blockPositionTop + this.data.holderHeight - this.data.blockHeight - this.options.extraBottom - (this.data.blockOffsetTop - this.data.holderOffsetTop), 138 left: this.data.blockPositionLeft 139 }); 140 } else { 141 this.slideBlock.css({ 142 position: 'fixed', 143 top: this.options.extraTop, 144 left: this.data.blockOffsetLeft - this.currentScrollLeft 145 }); 146 } 147 this.slideBlock.addClass(this.options.fixedActiveClass); 148 } else { 149 this.slideBlock.removeClass(this.options.fixedActiveClass).removeAttr('style'); 150 } 151 } 152 }, 153 absolute: { 154 onResize: function() { 155 this.slideBlock.removeAttr('style'); 156 this.recalculateOffsets(); 157 158 this.slideBlock.css({ 159 position: 'absolute', 160 top: this.data.blockPositionTop, 161 left: this.data.blockPositionLeft 162 }); 163 164 this.slideBlock.addClass(this.options.fixedActiveClass); 165 }, 166 onScroll: function() { 167 var self = this; 168 clearTimeout(this.animTimer); 169 this.animTimer = setTimeout(function() { 170 var currentScrollTop = self.currentScrollTop + self.options.extraTop, 171 initialPosition = self.data.blockPositionTop - (self.data.blockOffsetTop - self.data.holderOffsetTop), 172 maxTopPosition = self.data.holderHeight - self.data.blockHeight - self.options.extraBottom, 173 currentTopPosition = initialPosition + Math.min(currentScrollTop - self.data.holderOffsetTop, maxTopPosition), 174 calcTopPosition = self.fitsInViewport() && currentScrollTop > self.data.blockOffsetTop ? currentTopPosition : self.data.blockPositionTop; 175 176 self.slideBlock.stop().animate({ 177 top: calcTopPosition 178 }, self.options.animSpeed); 179 }, this.options.animDelay); 180 } 181 } 182 }; 183 184 // jQuery plugin interface 185 $.fn.fixedScrollBlock = function(options) { 186 return this.each(function() { 187 var params = $.extend({}, options, {container: this}), 188 instance = new FixedScrollBlock(params); 189 $.data(this, 'FixedScrollBlock', instance); 190 }); 191 }; 192 193 // module exports 194 window.FixedScrollBlock = FixedScrollBlock; 195}(jQuery, this));
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.