1function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 2 3/** 4 * Sticky.js 5 * Library for sticky elements written in vanilla javascript. With this library you can easily set sticky elements on your website. It's also responsive. 6 * 7 * @version 1.2.0 8 * @author Rafal Galus <[email protected]> 9 * @website https://rgalus.github.io/sticky-js/ 10 * @repo https://github.com/rgalus/sticky-js 11 * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE 12 */ 13 14var Sticky = function () { 15 /** 16 * Sticky instance constructor 17 * @constructor 18 * @param {string} selector - Selector which we can find elements 19 * @param {string} options - Global options for sticky elements (could be overwritten by data-{option}="" attributes) 20 */ 21 function Sticky() { 22 var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 23 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 24 25 _classCallCheck(this, Sticky); 26 27 this.selector = selector; 28 this.elements = []; 29 30 this.version = '1.2.0'; 31 32 this.vp = this.getViewportSize(); 33 this.body = document.querySelector('body'); 34 35 this.options = { 36 wrap: options.wrap || false, 37 marginTop: options.marginTop || 0, 38 stickyFor: options.stickyFor || 0, 39 stickyClass: options.stickyClass || null, 40 stickyContainer: options.stickyContainer || 'body' 41 }; 42 43 this.updateScrollTopPosition = this.updateScrollTopPosition.bind(this); 44 45 this.updateScrollTopPosition(); 46 window.addEventListener('load', this.updateScrollTopPosition); 47 window.addEventListener('scroll', this.updateScrollTopPosition); 48 49 this.run(); 50 } 51 52 /** 53 * Function that waits for page to be fully loaded and then renders & activates every sticky element found with specified selector 54 * @function 55 */ 56 57 58 Sticky.prototype.run = function run() { 59 var _this = this; 60 61 // wait for page to be fully loaded 62 var pageLoaded = setInterval(function () { 63 if (document.readyState === 'complete') { 64 clearInterval(pageLoaded); 65 66 var elements = document.querySelectorAll(_this.selector); 67 _this.forEach(elements, function (element) { 68 return _this.renderElement(element); 69 }); 70 } 71 }, 10); 72 }; 73 74 /** 75 * Function that assign needed variables for sticky element, that are used in future for calculations and other 76 * @function 77 * @param {node} element - Element to be rendered 78 */ 79 80 81 Sticky.prototype.renderElement = function renderElement(element) { 82 var _this2 = this; 83 84 // create container for variables needed in future 85 element.sticky = {}; 86 87 // set default variables 88 element.sticky.active = false; 89 90 element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop; 91 element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor; 92 element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass; 93 element.sticky.wrap = element.hasAttribute('data-sticky-wrap') ? true : this.options.wrap; 94 // @todo attribute for stickyContainer 95 // element.sticky.stickyContainer = element.getAttribute('data-sticky-container') || this.options.stickyContainer; 96 element.sticky.stickyContainer = this.options.stickyContainer; 97 98 element.sticky.container = this.getStickyContainer(element); 99 element.sticky.container.rect = this.getRectangle(element.sticky.container); 100 101 element.sticky.rect = this.getRectangle(element); 102 103 // fix when element is image that has not yet loaded and width, height = 0 104 if (element.tagName.toLowerCase() === 'img') { 105 element.onload = function () { 106 return element.sticky.rect = _this2.getRectangle(element); 107 }; 108 } 109 110 if (element.sticky.wrap) { 111 this.wrapElement(element); 112 } 113 114 // activate rendered element 115 this.activate(element); 116 }; 117 118 /** 119 * Wraps element into placeholder element 120 * @function 121 * @param {node} element - Element to be wrapped 122 */ 123 124 125 Sticky.prototype.wrapElement = function wrapElement(element) { 126 element.insertAdjacentHTML('beforebegin', '<span></span>');
127 element.previousSibling.appendChild(element); 128 }; 129 130 /** 131 * Function that activates element when specified conditions are met and then initalise events 132 * @function 133 * @param {node} element - Element to be activated 134 */ 135 136 137 Sticky.prototype.activate = function activate(element) { 138 if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) { 139 element.sticky.active = true; 140 } 141 142 if (this.elements.indexOf(element) < 0) { 143 this.elements.push(element); 144 } 145 146 if (!element.sticky.resizeEvent) { 147 this.initResizeEvents(element); 148 element.sticky.resizeEvent = true; 149 } 150 151 if (!element.sticky.scrollEvent) { 152 this.initScrollEvents(element); 153 element.sticky.scrollEvent = true; 154 } 155 156 this.setPosition(element); 157 }; 158 159 /** 160 * Function which is adding onResizeEvents to window listener and assigns function to element as resizeListener 161 * @function 162 * @param {node} element - Element for which resize events are initialised 163 */ 164 165 166 Sticky.prototype.initResizeEvents = function initResizeEvents(element) { 167 var _this3 = this; 168 169 element.sticky.resizeListener = function () { 170 return _this3.onResizeEvents(element); 171 }; 172 window.addEventListener('resize', element.sticky.resizeListener); 173 }; 174 175 /** 176 * Removes element listener from resize event 177 * @function 178 * @param {node} element - Element from which listener is deleted 179 */ 180 181 182 Sticky.prototype.destroyResizeEvents = function destroyResizeEvents(element) { 183 window.removeEventListener('resize', element.sticky.resizeListener); 184 }; 185 186 /** 187 * Function which is fired when user resize window. It checks if element should be activated or deactivated and then run setPosition function 188 * @function 189 * @param {node} element - Element for which event function is fired 190 */ 191 192 193 Sticky.prototype.onResizeEvents = function onResizeEvents(element) { 194 this.vp = this.getViewportSize(); 195 196 element.sticky.rect = this.getRectangle(element); 197 element.sticky.container.rect = this.getRectangle(element.sticky.container); 198 199 if (element.sticky.rect.top + element.sticky.rect.height < element.sticky.container.rect.top + element.sticky.container.rect.height && element.sticky.stickyFor < this.vp.width && !element.sticky.active) { 200 element.sticky.active = true; 201 } else if (element.sticky.rect.top + element.sticky.rect.height >= element.sticky.container.rect.top + element.sticky.container.rect.height || element.sticky.stickyFor >= this.vp.width && element.sticky.active) { 202 element.sticky.active = false; 203 } 204 205 this.setPosition(element); 206 }; 207 208 /** 209 * Function which is adding onScrollEvents to window listener and assigns function to element as scrollListener 210 * @function 211 * @param {node} element - Element for which scroll events are initialised 212 */ 213 214 215 Sticky.prototype.initScrollEvents = function initScrollEvents(element) { 216 var _this4 = this; 217 218 element.sticky.scrollListener = function () { 219 return _this4.onScrollEvents(element); 220 }; 221 window.addEventListener('scroll', element.sticky.scrollListener); 222 }; 223 224 /** 225 * Removes element listener from scroll event 226 * @function 227 * @param {node} element - Element from which listener is deleted 228 */ 229 230 231 Sticky.prototype.destroyScrollEvents = function destroyScrollEvents(element) { 232 window.removeEventListener('scroll', element.sticky.scrollListener); 233 }; 234 235 /** 236 * Function which is fired when user scroll window. If element is active, function is invoking setPosition function 237 * @function 238 * @param {node} element - Element for which event function is fired 239 */ 240 241 242 Sticky.prototype.onScrollEvents = function onScrollEvents(element) { 243 if (element.sticky.active) { 244 this.setPosition(element); 245 } 246 }; 247 248 /** 249 * Main function for the library. Here are some condition calculations and css appending for sticky element when user scroll window 250 * @function 251 * @param {node} element - Element that will be positioned if it's active 252 */ 253 254 255 Sticky.prototype.setPosition = function setPosition(element) { 256 this.css(element, { position: '', width: '', top: '', left: '' }); 257 258 if (this.vp.height < element.sticky.rect.height || !element.sticky.active) { 259 return; 260 } 261 262 if (!element.sticky.rect.width) { 263 element.sticky.rect = this.getRectangle(element); 264 } 265 266 if (element.sticky.wrap) { 267 this.css(element.parentNode, { 268 display: 'block', 269 width: element.sticky.rect.width + 'px', 270 height: element.sticky.rect.height + 'px' 271 }); 272 } 273 274 if (element.sticky.rect.top === 0 && element.sticky.container === this.body) { 275 this.css(element, { 276 position: 'fixed', 277 top: element.sticky.rect.top + 'px', 278 left: element.sticky.rect.left + 'px', 279 width: element.sticky.rect.width + 'px' 280 }); 281 } else if (this.scrollTop > element.sticky.rect.top - element.sticky.marginTop) { 282 this.css(element, { 283 position: 'fixed', 284 width: element.sticky.rect.width + 'px', 285 left: element.sticky.rect.left + 'px' 286 }); 287 288 if (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop > element.sticky.container.rect.top + element.sticky.container.offsetHeight) { 289 290 if (element.sticky.stickyClass) { 291 element.classList.remove(element.sticky.stickyClass); 292 } 293 294 this.css(element, { 295 top: element.sticky.container.rect.top + element.sticky.container.offsetHeight - (this.scrollTop + element.sticky.rect.height) + 'px' }); 296 } else { 297 if (element.sticky.stickyClass) { 298 element.classList.add(element.sticky.stickyClass); 299 } 300 301 this.css(element, { top: element.sticky.marginTop + 'px' }); 302 } 303 } else { 304 if (element.sticky.stickyClass) { 305 element.classList.remove(element.sticky.stickyClass); 306 } 307 308 this.css(element, { position: '', width: '', top: '', left: '' }); 309 310 if (element.sticky.wrap) { 311 this.css(element.parentNode, { display: '', width: '', height: '' }); 312 } 313 } 314 }; 315 316 /**
317 * Function that updates element sticky rectangle (with sticky container), then activate or deactivate element, then update position if it's active 318 * @function 319 */ 320 321 322 Sticky.prototype.update = function update() { 323 var _this5 = this; 324 325 this.forEach(this.elements, function (element) { 326 element.sticky.rect = _this5.getRectangle(element); 327 element.sticky.container.rect = _this5.getRectangle(element.sticky.container); 328 329 _this5.activate(element); 330 _this5.setPosition(element); 331 }); 332 }; 333 334 /** 335 * Destroys sticky element, remove listeners 336 * @function 337 */ 338 339 340 Sticky.prototype.destroy = function destroy() { 341 var _this6 = this; 342 343 this.forEach(this.elements, function (element) { 344 _this6.destroyResizeEvents(element); 345 _this6.destroyScrollEvents(element); 346 delete element.sticky; 347 }); 348 }; 349 350 /** 351 * Function that returns container element in which sticky element is stuck (if is not specified, then it's stuck to body) 352 * @function 353 * @param {node} element - Element which sticky container are looked for 354 * @return {node} element - Sticky container 355 */ 356 357 358 Sticky.prototype.getStickyContainer = function getStickyContainer(element) { 359 var container = element.parentNode; 360 361 while (!container.hasAttribute('data-sticky-container') && !container.parentNode.querySelector(element.sticky.stickyContainer) && container !== this.body) { 362 container = container.parentNode; 363 } 364 365 return container; 366 }; 367 368 /** 369 * Function that returns element rectangle & position (width, height, top, left) 370 * @function 371 * @param {node} element - Element which position & rectangle are returned 372 * @return {object} 373 */ 374 375 376 Sticky.prototype.getRectangle = function getRectangle(element) { 377 this.css(element, { position: '', width: '', top: '', left: '' }); 378 379 var width = Math.max(element.offsetWidth, element.clientWidth, element.scrollWidth); 380 var height = Math.max(element.offsetHeight, element.clientHeight, element.scrollHeight); 381 382 var top = 0; 383 var left = 0; 384 385 do { 386 top += element.offsetTop || 0; 387 left += element.offsetLeft || 0; 388 element = element.offsetParent; 389 } while (element); 390 391 return { top: top, left: left, width: width, height: height }; 392 }; 393 394 /** 395 * Function that returns viewport dimensions 396 * @function 397 * @return {object} 398 */ 399 400 401 Sticky.prototype.getViewportSize = function getViewportSize() { 402 return { 403 width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), 404 height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0) 405 }; 406 }; 407 408 /** 409 * Function that updates window scroll position 410 * @function 411 * @return {number} 412 */ 413 414 415 Sticky.prototype.updateScrollTopPosition = function updateScrollTopPosition() { 416 this.scrollTop = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0) || 0; 417 }; 418 419 /** 420 * Helper function for loops 421 * @helper 422 * @param {array} 423 * @param {function} callback - Callback function (no need for explanation) 424 */ 425 426 427 Sticky.prototype.forEach = function forEach(array, callback) { 428 for (var i = 0, len = array.length; i < len; i++) { 429 callback(array[i]); 430 } 431 }; 432 433 /** 434 * Helper function to add/remove css properties for specified element. 435 * @helper 436 * @param {node} element - DOM element 437 * @param {object} properties - CSS properties that will be added/removed from specified element 438 */ 439 440 441 Sticky.prototype.css = function css(element, properties) { 442 for (var property in properties) { 443 if (properties.hasOwnProperty(property)) { 444 element.style[property] = properties[property]; 445 } 446 } 447 }; 448 449 return Sticky; 450}(); 451 452/** 453 * Export function that supports AMD, CommonJS and Plain Browser. 454 */ 455 456 457(function (root, factory) { 458 if (typeof exports !== 'undefined') { 459 module.exports = factory; 460 } else if (typeof define === 'function' && define.amd) { 461 define([], factory); 462 } else {
463 root.Sticky = factory; 464 } 465})(this, Sticky); 466 467
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.