vanilla-lazyload
2 months ago
jquery.lazy.av.min.js
4 years ago
jquery.lazy.iframe.min.js
4 years ago
jquery.lazy.js
3 years ago
jquery.lazy.min.js
3 years ago
lazyload.min.js
4 years ago
loader.js
3 years ago
two_delay.js
2 years ago
two_elementor_video_to_iframe.js
4 years ago
two_lazyload.js
3 years ago
two_merge_google_font_faces.js
3 years ago
two_worker.js
2 years ago
two_yt_vi_lazyload.js
3 years ago
two_yt_vi_lazyload.min.js
3 years ago
jquery.lazy.js
876 lines
| 1 | /*! |
| 2 | * jQuery & Zepto Lazy - v1.7.10 |
| 3 | * http://jquery.eisbehr.de/lazy/ |
| 4 | * |
| 5 | * Copyright 2012 - 2018, Daniel 'Eisbehr' Kern |
| 6 | * |
| 7 | * Dual licensed under the MIT and GPL-2.0 licenses: |
| 8 | * http://www.opensource.org/licenses/mit-license.php |
| 9 | * http://www.gnu.org/licenses/gpl-2.0.html |
| 10 | * |
| 11 | * $("img.lazy").lazy(); |
| 12 | */ |
| 13 | |
| 14 | ;(function(window, undefined) { |
| 15 | "use strict"; |
| 16 | |
| 17 | // noinspection JSUnresolvedVariable |
| 18 | /** |
| 19 | * library instance - here and not in construct to be shorter in minimization |
| 20 | * @return void |
| 21 | */ |
| 22 | var $ = window.jQuery || window.Zepto, |
| 23 | |
| 24 | /** |
| 25 | * unique plugin instance id counter |
| 26 | * @type {number} |
| 27 | */ |
| 28 | lazyInstanceId = 0, |
| 29 | |
| 30 | /** |
| 31 | * helper to register window load for jQuery 3 |
| 32 | * @type {boolean} |
| 33 | */ |
| 34 | windowLoaded = false; |
| 35 | |
| 36 | /** |
| 37 | * make lazy available to jquery - and make it a bit more case-insensitive :) |
| 38 | * @access public |
| 39 | * @type {function} |
| 40 | * @param {object} settings |
| 41 | * @return {LazyPlugin} |
| 42 | */ |
| 43 | $.fn.Lazy = $.fn.lazy = function(settings) { |
| 44 | return new LazyPlugin(this, settings); |
| 45 | }; |
| 46 | |
| 47 | /** |
| 48 | * helper to add plugins to lazy prototype configuration |
| 49 | * @access public |
| 50 | * @type {function} |
| 51 | * @param {string|Array} names |
| 52 | * @param {string|Array|function} [elements] |
| 53 | * @param {function} loader |
| 54 | * @return void |
| 55 | */ |
| 56 | $.Lazy = $.lazy = function(names, elements, loader) { |
| 57 | // make second parameter optional |
| 58 | if ($.isFunction(elements)) { |
| 59 | loader = elements; |
| 60 | elements = []; |
| 61 | } |
| 62 | |
| 63 | // exit here if parameter is not a callable function |
| 64 | if (!$.isFunction(loader)) { |
| 65 | return; |
| 66 | } |
| 67 | |
| 68 | // make parameters an array of names to be sure |
| 69 | names = $.isArray(names) ? names : [names]; |
| 70 | elements = $.isArray(elements) ? elements : [elements]; |
| 71 | |
| 72 | var config = LazyPlugin.prototype.config, |
| 73 | forced = config._f || (config._f = {}); |
| 74 | |
| 75 | // add the loader plugin for every name |
| 76 | for (var i = 0, l = names.length; i < l; i++) { |
| 77 | if (config[names[i]] === undefined || $.isFunction(config[names[i]])) { |
| 78 | config[names[i]] = loader; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | // add forced elements loader |
| 83 | for (var c = 0, a = elements.length; c < a; c++) { |
| 84 | forced[elements[c]] = names[0]; |
| 85 | } |
| 86 | }; |
| 87 | |
| 88 | /** |
| 89 | * contains all logic and the whole element handling |
| 90 | * is packed in a private function outside class to reduce memory usage, because it will not be created on every plugin instance |
| 91 | * @access private |
| 92 | * @type {function} |
| 93 | * @param {LazyPlugin} instance |
| 94 | * @param {object} config |
| 95 | * @param {object|Array} items |
| 96 | * @param {object} events |
| 97 | * @param {string} namespace |
| 98 | * @return void |
| 99 | */ |
| 100 | function _executeLazy(instance, config, items, events, namespace) { |
| 101 | /** |
| 102 | * a helper to trigger the 'onFinishedAll' callback after all other events |
| 103 | * @access private |
| 104 | * @type {number} |
| 105 | */ |
| 106 | var _awaitingAfterLoad = 0, |
| 107 | |
| 108 | /** |
| 109 | * visible content width |
| 110 | * @access private |
| 111 | * @type {number} |
| 112 | */ |
| 113 | _actualWidth = -1, |
| 114 | |
| 115 | /** |
| 116 | * visible content height |
| 117 | * @access private |
| 118 | * @type {number} |
| 119 | */ |
| 120 | _actualHeight = -1, |
| 121 | |
| 122 | /** |
| 123 | * determine possibly detected high pixel density |
| 124 | * @access private |
| 125 | * @type {boolean} |
| 126 | */ |
| 127 | _isRetinaDisplay = false, |
| 128 | |
| 129 | /** |
| 130 | * dictionary entry for better minimization |
| 131 | * @access private |
| 132 | * @type {string} |
| 133 | */ |
| 134 | _afterLoad = 'afterLoad', |
| 135 | |
| 136 | /** |
| 137 | * dictionary entry for better minimization |
| 138 | * @access private |
| 139 | * @type {string} |
| 140 | */ |
| 141 | _load = 'load', |
| 142 | |
| 143 | /** |
| 144 | * dictionary entry for better minimization |
| 145 | * @access private |
| 146 | * @type {string} |
| 147 | */ |
| 148 | _error = 'error', |
| 149 | |
| 150 | /** |
| 151 | * dictionary entry for better minimization |
| 152 | * @access private |
| 153 | * @type {string} |
| 154 | */ |
| 155 | _img = 'img', |
| 156 | |
| 157 | /** |
| 158 | * dictionary entry for better minimization |
| 159 | * @access private |
| 160 | * @type {string} |
| 161 | */ |
| 162 | _src = 'src', |
| 163 | |
| 164 | /** |
| 165 | * dictionary entry for better minimization |
| 166 | * @access private |
| 167 | * @type {string} |
| 168 | */ |
| 169 | _srcset = 'srcset', |
| 170 | |
| 171 | /** |
| 172 | * dictionary entry for better minimization |
| 173 | * @access private |
| 174 | * @type {string} |
| 175 | */ |
| 176 | _sizes = 'sizes', |
| 177 | |
| 178 | /** |
| 179 | * dictionary entry for better minimization |
| 180 | * @access private |
| 181 | * @type {string} |
| 182 | */ |
| 183 | _backgroundImage = 'background-image'; |
| 184 | |
| 185 | /** |
| 186 | * initialize plugin |
| 187 | * bind loading to events or set delay time to load all items at once |
| 188 | * @access private |
| 189 | * @return void |
| 190 | */ |
| 191 | function _initialize() { |
| 192 | // detect actual device pixel ratio |
| 193 | // noinspection JSUnresolvedVariable |
| 194 | _isRetinaDisplay = window.devicePixelRatio > 1; |
| 195 | |
| 196 | // prepare all initial items |
| 197 | items = _prepareItems(items); |
| 198 | |
| 199 | // if delay time is set load all items at once after delay time |
| 200 | if (config.delay >= 0) { |
| 201 | setTimeout(function() { |
| 202 | _lazyLoadItems(true); |
| 203 | }, config.delay); |
| 204 | } |
| 205 | |
| 206 | // if no delay is set or combine usage is active bind events |
| 207 | if (config.delay < 0 || config.combined) { |
| 208 | // create unique event function |
| 209 | events.e = _throttle(config.throttle, function(event) { |
| 210 | // reset detected window size on resize event |
| 211 | if (event.type === 'resize') { |
| 212 | _actualWidth = _actualHeight = -1; |
| 213 | } |
| 214 | |
| 215 | // execute 'lazy magic' |
| 216 | _lazyLoadItems(event.all); |
| 217 | }); |
| 218 | |
| 219 | // create function to add new items to instance |
| 220 | events.a = function(additionalItems) { |
| 221 | additionalItems = _prepareItems(additionalItems); |
| 222 | items.push.apply(items, additionalItems); |
| 223 | }; |
| 224 | |
| 225 | // create function to get all instance items left |
| 226 | events.g = function() { |
| 227 | // filter loaded items before return in case internal filter was not running until now |
| 228 | return (items = $(items).filter(function() { |
| 229 | return !$(this).data(config.loadedName); |
| 230 | })); |
| 231 | }; |
| 232 | |
| 233 | // create function to force loading elements |
| 234 | events.f = function(forcedItems) { |
| 235 | for (var i = 0; i < forcedItems.length; i++) { |
| 236 | // only handle item if available in current instance |
| 237 | // use a compare function, because Zepto can't handle object parameter for filter |
| 238 | // var item = items.filter(forcedItems[i]); |
| 239 | /* jshint loopfunc: true */ |
| 240 | var item = items.filter(function() { |
| 241 | return this === forcedItems[i]; |
| 242 | }); |
| 243 | |
| 244 | if (item.length) { |
| 245 | _lazyLoadItems(false, item); |
| 246 | } |
| 247 | } |
| 248 | }; |
| 249 | |
| 250 | // load initial items |
| 251 | _lazyLoadItems(); |
| 252 | |
| 253 | // bind lazy load functions to scroll and resize event |
| 254 | // noinspection JSUnresolvedVariable |
| 255 | $(config.appendScroll).on('scroll.' + namespace + ' resize.' + namespace, events.e); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | /** |
| 260 | * prepare items before handle them |
| 261 | * @access private |
| 262 | * @param {Array|object|jQuery} items |
| 263 | * @return {Array|object|jQuery} |
| 264 | */ |
| 265 | function _prepareItems(items) { |
| 266 | // fetch used configurations before loops |
| 267 | var defaultImage = config.defaultImage, |
| 268 | placeholder = config.placeholder, |
| 269 | imageBase = config.imageBase, |
| 270 | srcsetAttribute = config.srcsetAttribute, |
| 271 | loaderAttribute = config.loaderAttribute, |
| 272 | forcedTags = config._f || {}; |
| 273 | |
| 274 | // filter items and only add those who not handled yet and got needed attributes available |
| 275 | items = $(items).filter(function() { |
| 276 | var element = $(this), |
| 277 | tag = _getElementTagName(this); |
| 278 | |
| 279 | return !element.data(config.handledName) && |
| 280 | (element.attr(config.attribute) || element.attr(srcsetAttribute) || element.attr(loaderAttribute) || forcedTags[tag] !== undefined); |
| 281 | }) |
| 282 | |
| 283 | // append plugin instance to all elements |
| 284 | .data('plugin_' + config.name, instance); |
| 285 | |
| 286 | for (var i = 0, l = items.length; i < l; i++) { |
| 287 | var element = $(items[i]), |
| 288 | tag = _getElementTagName(items[i]), |
| 289 | elementImageBase = element.attr(config.imageBaseAttribute) || imageBase; |
| 290 | |
| 291 | // generate and update source set if an image base is set |
| 292 | if (tag === _img && elementImageBase && element.attr(srcsetAttribute)) { |
| 293 | element.attr(srcsetAttribute, _getCorrectedSrcSet(element.attr(srcsetAttribute), elementImageBase)); |
| 294 | } |
| 295 | |
| 296 | // add loader to forced element types |
| 297 | if (forcedTags[tag] !== undefined && !element.attr(loaderAttribute)) { |
| 298 | element.attr(loaderAttribute, forcedTags[tag]); |
| 299 | } |
| 300 | |
| 301 | // set default image on every element without source |
| 302 | if (tag === _img && defaultImage && !element.attr(_src)) { |
| 303 | element.attr(_src, defaultImage); |
| 304 | } |
| 305 | |
| 306 | // set placeholder on every element without background image |
| 307 | else if (tag !== _img && placeholder && (!element.css(_backgroundImage) || element.css(_backgroundImage) === 'none')) { |
| 308 | element.css(_backgroundImage, "url('" + placeholder + "')"); |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | return items; |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * the 'lazy magic' - check all items |
| 317 | * @access private |
| 318 | * @param {boolean} [allItems] |
| 319 | * @param {object} [forced] |
| 320 | * @return void |
| 321 | */ |
| 322 | function _lazyLoadItems(allItems, forced) { |
| 323 | // skip if no items where left |
| 324 | if (!items.length) { |
| 325 | // destroy instance if option is enabled |
| 326 | if (config.autoDestroy) { |
| 327 | // noinspection JSUnresolvedFunction |
| 328 | instance.destroy(); |
| 329 | } |
| 330 | |
| 331 | return; |
| 332 | } |
| 333 | |
| 334 | var elements = forced || items, |
| 335 | loadTriggered = false, |
| 336 | imageBase = config.imageBase || '', |
| 337 | srcsetAttribute = config.srcsetAttribute, |
| 338 | handledName = config.handledName; |
| 339 | |
| 340 | // loop all available items |
| 341 | for (var i = 0; i < elements.length; i++) { |
| 342 | // item is at least in loadable area |
| 343 | if (allItems || forced || _isInLoadableArea(elements[i])) { |
| 344 | var element = $(elements[i]), |
| 345 | tag = _getElementTagName(elements[i]), |
| 346 | attribute = element.attr(config.attribute), |
| 347 | elementImageBase = element.attr(config.imageBaseAttribute) || imageBase, |
| 348 | customLoader = element.attr(config.loaderAttribute); |
| 349 | |
| 350 | // is not already handled |
| 351 | if (!element.data(handledName) && |
| 352 | // and is visible or visibility doesn't matter |
| 353 | (!config.visibleOnly || element.is(':visible')) && ( |
| 354 | // and image source or source set attribute is available |
| 355 | (attribute || element.attr(srcsetAttribute)) && ( |
| 356 | // and is image tag where attribute is not equal source or source set |
| 357 | (tag === _img && (elementImageBase + attribute !== element.attr(_src) || element.attr(srcsetAttribute) !== element.attr(_srcset))) || |
| 358 | // or is non image tag where attribute is not equal background |
| 359 | (tag !== _img && elementImageBase + attribute !== element.css(_backgroundImage)) |
| 360 | ) || |
| 361 | // or custom loader is available |
| 362 | customLoader)) |
| 363 | { |
| 364 | // mark element always as handled as this point to prevent double handling |
| 365 | loadTriggered = true; |
| 366 | element.data(handledName, true); |
| 367 | |
| 368 | // load item |
| 369 | _handleItem(element, tag, elementImageBase, customLoader); |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | // when something was loaded remove them from remaining items |
| 375 | if (loadTriggered) { |
| 376 | items = $(items).filter(function() { |
| 377 | return !$(this).data(handledName); |
| 378 | }); |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | /** |
| 383 | * load the given element the lazy way |
| 384 | * @access private |
| 385 | * @param {object} element |
| 386 | * @param {string} tag |
| 387 | * @param {string} imageBase |
| 388 | * @param {function} [customLoader] |
| 389 | * @return void |
| 390 | */ |
| 391 | function _handleItem(element, tag, imageBase, customLoader) { |
| 392 | // increment count of items waiting for after load |
| 393 | ++_awaitingAfterLoad; |
| 394 | |
| 395 | // extended error callback for correct 'onFinishedAll' handling |
| 396 | var errorCallback = function() { |
| 397 | _triggerCallback('onError', element); |
| 398 | _reduceAwaiting(); |
| 399 | |
| 400 | // prevent further callback calls |
| 401 | errorCallback = $.noop; |
| 402 | }; |
| 403 | |
| 404 | // trigger function before loading image |
| 405 | _triggerCallback('beforeLoad', element); |
| 406 | |
| 407 | // fetch all double used data here for better code minimization |
| 408 | var srcAttribute = config.attribute, |
| 409 | srcsetAttribute = config.srcsetAttribute, |
| 410 | sizesAttribute = config.sizesAttribute, |
| 411 | retinaAttribute = config.retinaAttribute, |
| 412 | removeAttribute = config.removeAttribute, |
| 413 | loadedName = config.loadedName, |
| 414 | elementRetina = element.attr(retinaAttribute); |
| 415 | |
| 416 | // handle custom loader |
| 417 | if (customLoader) { |
| 418 | // on load callback |
| 419 | var loadCallback = function() { |
| 420 | // remove attribute from element |
| 421 | if (removeAttribute) { |
| 422 | element.removeAttr(config.loaderAttribute); |
| 423 | } |
| 424 | |
| 425 | // mark element as loaded |
| 426 | element.data(loadedName, true); |
| 427 | |
| 428 | // call after load event |
| 429 | _triggerCallback(_afterLoad, element); |
| 430 | |
| 431 | // remove item from waiting queue and possibly trigger finished event |
| 432 | // it's needed to be asynchronous to run after filter was in _lazyLoadItems |
| 433 | setTimeout(_reduceAwaiting, 1); |
| 434 | |
| 435 | // prevent further callback calls |
| 436 | loadCallback = $.noop; |
| 437 | }; |
| 438 | |
| 439 | // bind error event to trigger callback and reduce waiting amount |
| 440 | element.off(_error).one(_error, errorCallback) |
| 441 | |
| 442 | // bind after load callback to element |
| 443 | .one(_load, loadCallback); |
| 444 | |
| 445 | // trigger custom loader and handle response |
| 446 | if (!_triggerCallback(customLoader, element, function(response) { |
| 447 | if(response) { |
| 448 | element.off(_load); |
| 449 | loadCallback(); |
| 450 | } |
| 451 | else { |
| 452 | element.off(_error); |
| 453 | errorCallback(); |
| 454 | } |
| 455 | })) { |
| 456 | element.trigger(_error); |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | // handle images |
| 461 | else { |
| 462 | // create image object |
| 463 | var imageObj = $(new Image()); |
| 464 | |
| 465 | // bind error event to trigger callback and reduce waiting amount |
| 466 | imageObj.one(_error, errorCallback) |
| 467 | |
| 468 | // bind after load callback to image |
| 469 | .one(_load, function() { |
| 470 | // remove element from view |
| 471 | element.hide(); |
| 472 | |
| 473 | // set image back to element |
| 474 | // do it as single 'attr' calls, to be sure 'src' is set after 'srcset' |
| 475 | if (tag === _img) { |
| 476 | element.attr(_sizes, imageObj.attr(_sizes)) |
| 477 | .attr(_srcset, imageObj.attr(_srcset)) |
| 478 | .attr(_src, imageObj.attr(_src)); |
| 479 | } |
| 480 | else { |
| 481 | if(element.attr("data-full-bg-image")){ |
| 482 | element.css(_backgroundImage, element.attr("data-full-bg-image")); |
| 483 | }else{ |
| 484 | element.css(_backgroundImage, "url('" + imageObj.attr(_src) + "')"); |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | // bring it back with some effect! |
| 489 | element[config.effect](config.effectTime); |
| 490 | |
| 491 | // remove attribute from element |
| 492 | if (removeAttribute) { |
| 493 | element.removeAttr(srcAttribute + ' ' + srcsetAttribute + ' ' + retinaAttribute + ' ' + config.imageBaseAttribute); |
| 494 | |
| 495 | // only remove 'sizes' attribute, if it was a custom one |
| 496 | if (sizesAttribute !== _sizes) { |
| 497 | element.removeAttr(sizesAttribute); |
| 498 | } |
| 499 | } |
| 500 | |
| 501 | // mark element as loaded |
| 502 | element.data(loadedName, true); |
| 503 | |
| 504 | // call after load event |
| 505 | _triggerCallback(_afterLoad, element); |
| 506 | |
| 507 | // cleanup image object |
| 508 | imageObj.remove(); |
| 509 | |
| 510 | // remove item from waiting queue and possibly trigger finished event |
| 511 | _reduceAwaiting(); |
| 512 | }); |
| 513 | |
| 514 | // set sources |
| 515 | // do it as single 'attr' calls, to be sure 'src' is set after 'srcset' |
| 516 | var imageSrc = (_isRetinaDisplay && elementRetina ? elementRetina : element.attr(srcAttribute)) || ''; |
| 517 | imageObj.attr(_sizes, element.attr(sizesAttribute)) |
| 518 | .attr(_srcset, element.attr(srcsetAttribute)) |
| 519 | .attr(_src, imageSrc ? imageBase + imageSrc : null); |
| 520 | |
| 521 | // call after load even on cached image |
| 522 | imageObj.complete && imageObj.trigger(_load); // jshint ignore : line |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | /** |
| 527 | * check if the given element is inside the current viewport or threshold |
| 528 | * @access private |
| 529 | * @param {object} element |
| 530 | * @return {boolean} |
| 531 | */ |
| 532 | function _isInLoadableArea(element) { |
| 533 | var elementBound = element.getBoundingClientRect(), |
| 534 | direction = config.scrollDirection, |
| 535 | threshold = config.threshold, |
| 536 | vertical = // check if element is in loadable area from top |
| 537 | ((_getActualHeight() + threshold) > elementBound.top) && |
| 538 | // check if element is even in loadable are from bottom |
| 539 | (-threshold < elementBound.bottom), |
| 540 | horizontal = // check if element is in loadable area from left |
| 541 | ((_getActualWidth() + threshold) > elementBound.left) && |
| 542 | // check if element is even in loadable area from right |
| 543 | (-threshold < elementBound.right); |
| 544 | |
| 545 | if (direction === 'vertical') { |
| 546 | return vertical; |
| 547 | } |
| 548 | else if (direction === 'horizontal') { |
| 549 | return horizontal; |
| 550 | } |
| 551 | |
| 552 | return vertical && horizontal; |
| 553 | } |
| 554 | |
| 555 | /** |
| 556 | * receive the current viewed width of the browser |
| 557 | * @access private |
| 558 | * @return {number} |
| 559 | */ |
| 560 | function _getActualWidth() { |
| 561 | return _actualWidth >= 0 ? _actualWidth : (_actualWidth = $(window).width()); |
| 562 | } |
| 563 | |
| 564 | /** |
| 565 | * receive the current viewed height of the browser |
| 566 | * @access private |
| 567 | * @return {number} |
| 568 | */ |
| 569 | function _getActualHeight() { |
| 570 | return _actualHeight >= 0 ? _actualHeight : (_actualHeight = $(window).height()); |
| 571 | } |
| 572 | |
| 573 | /** |
| 574 | * get lowercase tag name of an element |
| 575 | * @access private |
| 576 | * @param {object} element |
| 577 | * @returns {string} |
| 578 | */ |
| 579 | function _getElementTagName(element) { |
| 580 | return element.tagName.toLowerCase(); |
| 581 | } |
| 582 | |
| 583 | /** |
| 584 | * prepend image base to all srcset entries |
| 585 | * @access private |
| 586 | * @param {string} srcset |
| 587 | * @param {string} imageBase |
| 588 | * @returns {string} |
| 589 | */ |
| 590 | function _getCorrectedSrcSet(srcset, imageBase) { |
| 591 | if (imageBase) { |
| 592 | // trim, remove unnecessary spaces and split entries |
| 593 | var entries = srcset.split(','); |
| 594 | srcset = ''; |
| 595 | |
| 596 | for (var i = 0, l = entries.length; i < l; i++) { |
| 597 | srcset += imageBase + entries[i].trim() + (i !== l - 1 ? ',' : ''); |
| 598 | } |
| 599 | } |
| 600 | |
| 601 | return srcset; |
| 602 | } |
| 603 | |
| 604 | /** |
| 605 | * helper function to throttle down event triggering |
| 606 | * @access private |
| 607 | * @param {number} delay |
| 608 | * @param {function} callback |
| 609 | * @return {function} |
| 610 | */ |
| 611 | function _throttle(delay, callback) { |
| 612 | var timeout, |
| 613 | lastExecute = 0; |
| 614 | |
| 615 | return function(event, ignoreThrottle) { |
| 616 | var elapsed = +new Date() - lastExecute; |
| 617 | |
| 618 | function run() { |
| 619 | lastExecute = +new Date(); |
| 620 | // noinspection JSUnresolvedFunction |
| 621 | callback.call(instance, event); |
| 622 | } |
| 623 | |
| 624 | timeout && clearTimeout(timeout); // jshint ignore : line |
| 625 | |
| 626 | if (elapsed > delay || !config.enableThrottle || ignoreThrottle) { |
| 627 | run(); |
| 628 | } |
| 629 | else { |
| 630 | timeout = setTimeout(run, delay - elapsed); |
| 631 | } |
| 632 | }; |
| 633 | } |
| 634 | |
| 635 | /** |
| 636 | * reduce count of awaiting elements to 'afterLoad' event and fire 'onFinishedAll' if reached zero |
| 637 | * @access private |
| 638 | * @return void |
| 639 | */ |
| 640 | function _reduceAwaiting() { |
| 641 | --_awaitingAfterLoad; |
| 642 | |
| 643 | // if no items were left trigger finished event |
| 644 | if (!items.length && !_awaitingAfterLoad) { |
| 645 | _triggerCallback('onFinishedAll'); |
| 646 | } |
| 647 | } |
| 648 | |
| 649 | /** |
| 650 | * single implementation to handle callbacks, pass element and set 'this' to current instance |
| 651 | * @access private |
| 652 | * @param {string|function} callback |
| 653 | * @param {object} [element] |
| 654 | * @param {*} [args] |
| 655 | * @return {boolean} |
| 656 | */ |
| 657 | function _triggerCallback(callback, element, args) { |
| 658 | if ((callback = config[callback])) { |
| 659 | // jQuery's internal '$(arguments).slice(1)' are causing problems at least on old iPads |
| 660 | // below is shorthand of 'Array.prototype.slice.call(arguments, 1)' |
| 661 | callback.apply(instance, [].slice.call(arguments, 1)); |
| 662 | return true; |
| 663 | } |
| 664 | |
| 665 | return false; |
| 666 | } |
| 667 | |
| 668 | // if event driven or window is already loaded don't wait for page loading |
| 669 | if (config.bind === 'event' || windowLoaded) { |
| 670 | _initialize(); |
| 671 | } |
| 672 | |
| 673 | // otherwise load initial items and start lazy after page load |
| 674 | else { |
| 675 | // noinspection JSUnresolvedVariable |
| 676 | $(window).on(_load + '.' + namespace, _initialize); |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | /** |
| 681 | * lazy plugin class constructor |
| 682 | * @constructor |
| 683 | * @access private |
| 684 | * @param {object} elements |
| 685 | * @param {object} settings |
| 686 | * @return {object|LazyPlugin} |
| 687 | */ |
| 688 | function LazyPlugin(elements, settings) { |
| 689 | /** |
| 690 | * this lazy plugin instance |
| 691 | * @access private |
| 692 | * @type {object|LazyPlugin|LazyPlugin.prototype} |
| 693 | */ |
| 694 | var _instance = this, |
| 695 | |
| 696 | /** |
| 697 | * this lazy plugin instance configuration |
| 698 | * @access private |
| 699 | * @type {object} |
| 700 | */ |
| 701 | _config = $.extend({}, _instance.config, settings), |
| 702 | |
| 703 | /** |
| 704 | * instance generated event executed on container scroll or resize |
| 705 | * packed in an object to be referenceable and short named because properties will not be minified |
| 706 | * @access private |
| 707 | * @type {object} |
| 708 | */ |
| 709 | _events = {}, |
| 710 | |
| 711 | /** |
| 712 | * unique namespace for instance related events |
| 713 | * @access private |
| 714 | * @type {string} |
| 715 | */ |
| 716 | _namespace = _config.name + '-' + (++lazyInstanceId); |
| 717 | |
| 718 | // noinspection JSUndefinedPropertyAssignment |
| 719 | /** |
| 720 | * wrapper to get or set an entry from plugin instance configuration |
| 721 | * much smaller on minify as direct access |
| 722 | * @access public |
| 723 | * @type {function} |
| 724 | * @param {string} entryName |
| 725 | * @param {*} [value] |
| 726 | * @return {LazyPlugin|*} |
| 727 | */ |
| 728 | _instance.config = function(entryName, value) { |
| 729 | if (value === undefined) { |
| 730 | return _config[entryName]; |
| 731 | } |
| 732 | |
| 733 | _config[entryName] = value; |
| 734 | return _instance; |
| 735 | }; |
| 736 | |
| 737 | // noinspection JSUndefinedPropertyAssignment |
| 738 | /** |
| 739 | * add additional items to current instance |
| 740 | * @access public |
| 741 | * @param {Array|object|string} items |
| 742 | * @return {LazyPlugin} |
| 743 | */ |
| 744 | _instance.addItems = function(items) { |
| 745 | _events.a && _events.a($.type(items) === 'string' ? $(items) : items); // jshint ignore : line |
| 746 | return _instance; |
| 747 | }; |
| 748 | |
| 749 | // noinspection JSUndefinedPropertyAssignment |
| 750 | /** |
| 751 | * get all left items of this instance |
| 752 | * @access public |
| 753 | * @returns {object} |
| 754 | */ |
| 755 | _instance.getItems = function() { |
| 756 | return _events.g ? _events.g() : {}; |
| 757 | }; |
| 758 | |
| 759 | // noinspection JSUndefinedPropertyAssignment |
| 760 | /** |
| 761 | * force lazy to load all items in loadable area right now |
| 762 | * by default without throttle |
| 763 | * @access public |
| 764 | * @type {function} |
| 765 | * @param {boolean} [useThrottle] |
| 766 | * @return {LazyPlugin} |
| 767 | */ |
| 768 | _instance.update = function(useThrottle) { |
| 769 | _events.e && _events.e({}, !useThrottle); // jshint ignore : line |
| 770 | return _instance; |
| 771 | }; |
| 772 | |
| 773 | // noinspection JSUndefinedPropertyAssignment |
| 774 | /** |
| 775 | * force element(s) to load directly, ignoring the viewport |
| 776 | * @access public |
| 777 | * @param {Array|object|string} items |
| 778 | * @return {LazyPlugin} |
| 779 | */ |
| 780 | _instance.force = function(items) { |
| 781 | _events.f && _events.f($.type(items) === 'string' ? $(items) : items); // jshint ignore : line |
| 782 | return _instance; |
| 783 | }; |
| 784 | |
| 785 | // noinspection JSUndefinedPropertyAssignment |
| 786 | /** |
| 787 | * force lazy to load all available items right now |
| 788 | * this call ignores throttling |
| 789 | * @access public |
| 790 | * @type {function} |
| 791 | * @return {LazyPlugin} |
| 792 | */ |
| 793 | _instance.loadAll = function() { |
| 794 | _events.e && _events.e({all: true}, true); // jshint ignore : line |
| 795 | return _instance; |
| 796 | }; |
| 797 | |
| 798 | // noinspection JSUndefinedPropertyAssignment |
| 799 | /** |
| 800 | * destroy this plugin instance |
| 801 | * @access public |
| 802 | * @type {function} |
| 803 | * @return undefined |
| 804 | */ |
| 805 | _instance.destroy = function() { |
| 806 | // unbind instance generated events |
| 807 | // noinspection JSUnresolvedFunction, JSUnresolvedVariable |
| 808 | $(_config.appendScroll).off('.' + _namespace, _events.e); |
| 809 | // noinspection JSUnresolvedVariable |
| 810 | $(window).off('.' + _namespace); |
| 811 | |
| 812 | // clear events |
| 813 | _events = {}; |
| 814 | |
| 815 | return undefined; |
| 816 | }; |
| 817 | |
| 818 | // start using lazy and return all elements to be chainable or instance for further use |
| 819 | // noinspection JSUnresolvedVariable |
| 820 | _executeLazy(_instance, _config, elements, _events, _namespace); |
| 821 | return _config.chainable ? elements : _instance; |
| 822 | } |
| 823 | |
| 824 | /** |
| 825 | * settings and configuration data |
| 826 | * @access public |
| 827 | * @type {object|*} |
| 828 | */ |
| 829 | LazyPlugin.prototype.config = { |
| 830 | // general |
| 831 | name : 'lazy', |
| 832 | chainable : true, |
| 833 | autoDestroy : true, |
| 834 | bind : 'load', |
| 835 | threshold : 500, |
| 836 | visibleOnly : false, |
| 837 | appendScroll : window, |
| 838 | scrollDirection : 'both', |
| 839 | imageBase : null, |
| 840 | defaultImage : 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', |
| 841 | placeholder : null, |
| 842 | delay : -1, |
| 843 | combined : false, |
| 844 | |
| 845 | // attributes |
| 846 | attribute : 'data-src', |
| 847 | srcsetAttribute : 'data-srcset', |
| 848 | sizesAttribute : 'data-sizes', |
| 849 | retinaAttribute : 'data-retina', |
| 850 | loaderAttribute : 'data-loader', |
| 851 | imageBaseAttribute : 'data-imagebase', |
| 852 | removeAttribute : true, |
| 853 | handledName : 'handled', |
| 854 | loadedName : 'loaded', |
| 855 | |
| 856 | // effect |
| 857 | effect : 'show', |
| 858 | effectTime : 0, |
| 859 | |
| 860 | // throttle |
| 861 | enableThrottle : true, |
| 862 | throttle : 250, |
| 863 | |
| 864 | // callbacks |
| 865 | beforeLoad : undefined, |
| 866 | afterLoad : undefined, |
| 867 | onError : undefined, |
| 868 | onFinishedAll : undefined |
| 869 | }; |
| 870 | |
| 871 | // register window load event globally to prevent not loading elements |
| 872 | // since jQuery 3.X ready state is fully async and may be executed after 'load' |
| 873 | $(window).on('load', function() { |
| 874 | windowLoaded = true; |
| 875 | }); |
| 876 | })(window); |