1/** 2 * @file 3 * Provides native, Intersection Observer API, or bLazy lazy loader. 4 */ 5 6(function (Drupal, drupalSettings, _db, window, document) { 7 8 'use strict'; 9 10 var _dataAnimation = 'data-animation'; 11 var _dataDimensions = 'data-dimensions'; 12 var _dataBg = 'data-backgrounds'; 13 var _dataRatio = 'data-ratio'; 14 var _isNativeExecuted = false; 15 var _resizeTick = 0; 16 17 /** 18 * Blazy public methods. 19 * 20 * @namespace 21 */ 22 Drupal.blazy = Drupal.blazy || { 23 context: null, 24 init: null, 25 instances: [], 26 items: [], 27 windowWidth: 0, 28 blazySettings: drupalSettings.blazy || {}, 29 ioSettings: drupalSettings.blazyIo || {}, 30 revalidate: false, 31 options: {}, 32 globals: function () { 33 var me = this; 34 var commons = { 35 success: me.clearing.bind(me), 36 error: me.clearing.bind(me), 37 selector: '.b-lazy', 38 errorClass: 'b-error', 39 successClass: 'b-loaded' 40 }; 41 42 return _db.extend(me.blazySettings, me.ioSettings, commons); 43 }, 44 45 clearing: function (el) { 46 var me = this; 47 var cn = _db.closest(el, '.media'); 48 var an = _db.closest(el, '[' + _dataAnimation + ']'); 49 50 // Clear loading classes. 51 me.clearLoading(el); 52 53 // Reevaluate the element. 54 me.reevaluate(el); 55 56 // Container might be the el itself for BG, do not NULL check here. 57 me.updateContainer(el, cn); 58 59 // Supports blur, animate.css for CSS background, picture, image, media. 60 if (an !== null || me.has(el, _dataAnimation)) { 61 _db.animate(an !== null ? an : el); 62 } 63 64 // Provides event listeners for easy overrides without full overrides. 65 // Runs before native to allow native use this on its own onload event. 66 _db.trigger(el, 'blazy.done', {options: me.options}); 67 68 // Initializes the native lazy loading once the first found is loaded. 69 if (!_isNativeExecuted) { 70 _db.trigger(me.context, 'blazy.native', {options: me.options}); 71 72 _isNativeExecuted = true; 73 } 74 }, 75 76 clearLoading: function (el) { 77 // The .b-lazy element can be attached to IMG, or DIV as CSS background. 78 // The .(*)loading can be .media, .grid, .slide__content, .box, etc. 79 var loaders = [ 80 el, 81 _db.closest(el, '.is-loading'), 82 _db.closest(el, '[class*="loading"]') 83 ]; 84 85 _db.forEach(loaders, function (loader) { 86 if (loader !== null) { 87 loader.className = loader.className.replace(/(\S+)loading/g, ''); 88 } 89 }); 90 }, 91 92 isLoaded: function (el) { 93 return el !== null && el.classList.contains(this.options.successClass); 94 }, 95 96 reevaluate: function (el) { 97 var me = this; 98 var ie = el.classList.contains('b-responsive') && el.hasAttribute('data-pfsrc'); 99 100 // In case an error, try forcing it, once. 101 if (me.init !== null && _db.hasClass(el, me.options.errorClass) && !_db.hasClass(el, 'b-checked')) { 102 el.classList.add('b-checked'); 103 104 // This is a rare case, hardly called, just nice to have for errors. 105 window.setTimeout(function () { 106 if (me.has(el, _dataBg)) { 107 _db.updateBg(el, me.options.mobileFirst); 108 } 109 else { 110 me.init.load(el); 111 } 112 }, 100); 113 } 114 115 // @see http://scottjehl.github.io/picturefill/ 116 if (window.picturefill && ie) { 117 window.picturefill({ 118 reevaluate: true, 119 elements: [el] 120 }); 121 } 122 }, 123 124 has: function (el, attribute) { 125 return el !== null && el.hasAttribute(attribute); 126 }, 127 128 contains: function (el, name) { 129 return el !== null && el.classList.contains(name); 130 }, 131 132 updateContainer: function (el, cn) { 133 var me = this; 134 var isPicture = _db.equal(el.parentNode, 'picture') && me.has(cn, _dataDimensions); 135 136 // Fixed for effect Blur messes up Aspect ratio Fluid calculation. 137 window.setTimeout(function () { 138 if (me.isLoaded(el)) { 139 // Adds context for effetcs: blur, etc. considering BG, or just media. 140 (me.contains(cn, 'media') ? cn : el).classList.add('is-b-loaded'); 141 142 // Only applies to ratio fluid. 143 if (isPicture) { 144 me.updatePicture(el, cn); 145 } 146 147 // Basically makes multi-breakpoint BG work for IO or old bLazy once. 148 if (me.has(el, _dataBg)) {
149 _db.updateBg(el, me.options.mobileFirst); 150 } 151 } 152 }); 153 }, 154 155 updatePicture: function (el, cn) { 156 var me = this; 157 var pad = Math.round(((el.naturalHeight / el.naturalWidth) * 100), 2); 158 159 cn.style.paddingBottom = pad + '%'; 160 161 // Swap all aspect ratio once to reduce abrupt ratio changes for the rest. 162 if (me.instances.length > 0) { 163 var picture = function (elm) { 164 if (!('blazyInstance' in elm) && !('blazyUniform' in elm)) { 165 return; 166 } 167 168 if ((elm.blazyInstance === cn.blazyInstance) && (_resizeTick > 1 || !('isBlazyPicture' in elm))) { 169 _db.trigger(elm, 'blazy.uniform.' + elm.blazyInstance, {pad: pad}); 170 elm.isBlazyPicture = true; 171 } 172 }; 173 174 // Uniform sizes must apply to each instance, not globally. 175 _db.forEach(me.instances, function (elm) { 176 Drupal.debounce(picture(elm), 201, true); 177 }, me.context); 178 } 179 }, 180 181 /** 182 * Attempts to fix for Views rewrite stripping out data URI causing 404. 183 * 184 * E.g.: src="image/jpg;base64 should be src="data:image/jpg;base64. 185 * The "Placeholder" 1px.gif via Blazy UI costs extra HTTP requests. This is 186 * a less costly solution, but not bulletproof due to being client-side 187 * which means too late to the party. Yet not bad for 404s below the fold. 188 * This must be run before any lazy (native, bLazy or IO) kicks in. 189 * 190 * @todo remove if a permanent non-client available other than Placeholder. 191 */ 192 fixMissingDataUri: function () { 193 var me = this; 194 var doc = me.context; 195 var sel = me.options.selector + '[src^="image"]:not(.' + me.options.successClass + ')'; 196 var els = doc.querySelector(sel) === null ? [] : doc.querySelectorAll(sel); 197 198 var fixDataUri = function (img) { 199 var src = img.getAttribute('src'); 200 if (src.indexOf('base64') !== -1 || src.indexOf('svg+xml') !== -1) { 201 img.setAttribute('src', src.replace('image', 'data:image')); 202 } 203 }; 204 205 if (els.length > 0) { 206 _db.forEach(els, fixDataUri); 207 } 208 }, 209 210 /** 211 * Updates the dynamic multi-breakpoint aspect ratio: bg, picture or image. 212 * 213 * This only applies to Responsive images with aspect ratio fluid. 214 * Static ratio (media--ratio--169, etc.) is ignored and uses CSS instead. 215 * 216 * @param {HTMLElement} cn 217 * The .media--ratio--fluid container HTML element. 218 */ 219 updateRatio: function (cn) { 220 var me = this; 221 var el = _db.closest(cn, '.blazy'); 222 var dimensions = _db.parse(cn.getAttribute(_dataDimensions)); 223 224 if (!dimensions) { 225 me.updateFallbackRatio(cn); 226 return; 227 } 228 229 // For picture, this is more a dummy space till the image is downloaded. 230 var isPicture = cn.querySelector('picture') !== null && _resizeTick > 0; 231 var pad = _db.activeWidth(dimensions, isPicture); 232 233 // Provides marker for grouping between multiple instances. 234 cn.blazyInstance = 'blazyInstance' in el ? el.blazyInstance : null; 235 if (pad !== 'undefined') { 236 cn.style.paddingBottom = pad + '%'; 237 } 238 239 // Fix for picture or bg element with resizing. 240 if (_resizeTick > 0 && (isPicture || me.has(cn, _dataBg))) { 241 me.updateContainer((isPicture ? cn.querySelector('img') : cn), cn); 242 } 243 }, 244 245 updateFallbackRatio: function (cn) { 246 // Only rewrites if the style is indeed stripped out by Twig, and not set. 247 if (!cn.hasAttribute('style') && cn.hasAttribute(_dataRatio)) { 248 cn.style.paddingBottom = cn.getAttribute(_dataRatio) + '%'; 249 } 250 }, 251 252 /** 253 * Swap lazy attributes to let supportive browsers lazy load them. 254 * 255 * This means Blazy and even IO should not lazy-load them any more. 256 * Ensures to not touch lazy-loaded AJAX, or likely non-supported elements: 257 * Video, DIV, etc. Only IMG and IFRAME are supported for now. 258 * Due to native init is deferred, the first row is still using IO/ bLazy. 259 */ 260 doNativeLazy: function () { 261 var me = this; 262 263 if (!me.isNativeLazy()) { 264 return; 265 } 266 267 var doc = me.context; 268 var sel = me.options.selector + '[loading]:not(.' + me.options.successClass + ')'; 269 270 me.items = doc.querySelector(sel) === null ? [] : doc.querySelectorAll(sel); 271 if (me.items.length === 0) { 272 return; 273 } 274 275 var onNativeEvent = function (e) { 276 var el = e.target; 277 var er = e.type === 'error'; 278 279 // Refines based on actual result, runs clearing, animation, etc. 280 el.classList.add(me.options[er ? 'errorClass' : 'successClass']); 281 me.clearing(el); 282 283 _db.unbindEvent(el, e.type, onNativeEvent); 284 }; 285 286 var doNative = function (el) { 287 // Reset attributes, and let supportive browsers lazy load natively. 288 _db.setAttrs(el, ['srcset', 'src'], true); 289 290 // Also supports PICTURE or (future) VIDEO which contains SOURCEs. 291 _db.setAttrsWithSources(el, false, true); 292 293 // Blur thumbnail is just making use of the swap due to being small. 294 if (me.contains(el, 'b-blur')) { 295 el.removeAttribute('loading'); 296 } 297 else { 298 // Mark it loaded to prevent bLazy/ IO to do any further work. 299 el.classList.add(me.options.successClass); 300 301 // Attempts to make nice with the harsh native, defer clearing, etc. 302 _db.bindEvent(el, 'load', onNativeEvent); 303 _db.bindEvent(el, 'error', onNativeEvent); 304 } 305 }; 306 307 var onNative = function () { 308 _db.forEach(me.items, doNative); 309 }; 310 311 _db.bindEvent(me.context, 'blazy.native', onNative, {once: true}); 312 }, 313 314 isNativeLazy: function () { 315 return 'loading' in HTMLImageElement.prototype; 316 }, 317 318 isIo: function () { 319 return this.ioSettings && this.ioSettings.enabled && 'IntersectionObserver' in window; 320 }, 321 322 isRo: function () { 323 return 'ResizeObserver' in window; 324 }, 325 326 isBlazy: function () { 327 return !this.isIo() && 'Blazy' in window; 328 }, 329
330 forEach: function (context) { 331 var blazies = context.querySelectorAll('.blazy:not(.blazy--on)'); 332 333 // Various use cases: w/o formaters, custom, or basic, and mixed. 334 // The [data-blazy] is set by the module for formatters, or Views gallery. 335 if (blazies.length > 0) { 336 _db.forEach(blazies, doBlazy, context); 337 } 338 339 // Initializes blazy, we'll decouple features from lazy load scripts. 340 // We'll revert to 2.1 if any issue with this. 341 initBlazy(context); 342 }, 343 344 run: function (opts) { 345 return this.isIo() ? new BioMedia(opts) : new Blazy(opts); 346 }, 347 348 afterInit: function () { 349 var me = this; 350 var doc = me.context; 351 var rObserver = false; 352 var ratioItems = doc.querySelector('.media--ratio') === null ? [] : doc.querySelectorAll('.media--ratio'); 353 var shouldLoop = ratioItems.length > 0; 354 355 var loopRatio = function (entries) { 356 me.windowWidth = _db.windowWidth(); 357 358 // BC with bLazy, native/IO doesn't need to revalidate, bLazy does. 359 // Scenarios: long horizontal containers, Slick carousel slidesToShow > 360 // 3. If any issue, add a class `blazy--revalidate` manually to .blazy. 361 if (!me.isNativeLazy() && (me.isBlazy() || me.revalidate)) { 362 me.init.revalidate(true); 363 } 364 365 if (shouldLoop) { 366 _db.forEach(entries, function (entry) { 367 me.updateRatio('target' in entry ? entry.target : entry); 368 }, doc); 369 } 370 371 _resizeTick++; 372 return false; 373 }; 374 375 var checkRatio = function () { 376 return me.isRo() ? new ResizeObserver(loopRatio) : loopRatio(ratioItems); 377 }; 378 379 // Checks for aspect ratio, onload event is a bit later. 380 // Uses ResizeObserver for modern browsers, else degrades. 381 rObserver = checkRatio(); 382 if (rObserver) { 383 if (shouldLoop) { 384 _db.forEach(ratioItems, function (entry) { 385 rObserver.observe(entry); 386 }, doc); 387 } 388 } 389 else { 390 _db.bindEvent(window, 'resize', Drupal.debounce(checkRatio, 200, true)); 391 } 392 } 393 }; 394 395 /** 396 * Initialize the blazy instance, either basic, advanced, or native. 397 * 398 * @param {HTMLElement} context 399 * This can be document, or anything weird. 400 */ 401 var initBlazy = function (context) { 402 var me = Drupal.blazy; 403 var documentElement = context instanceof HTMLDocument ? context : _db.closest(context, 'html'); 404 var opts = {}; 405 406 opts.mobileFirst = opts.mobileFirst || false; 407 408 // Weirdo: documentElement is null after colorbox cbox_close event. 409 documentElement = documentElement || document; 410 411 // Set docroot in case we are in an iframe. 412 if (!document.documentElement.isSameNode(documentElement)) { 413 opts.root = documentElement; 414 } 415 416 me.options = _db.extend({}, me.globals(), opts); 417 me.context = documentElement; 418 419 // Old bLazy, not IO, might need scrolling CSS selector like Modal library. 420 // A scrolling modal with an iframe like Entity Browser has no issue since 421 // the scrolling container is the entire DOM. Another use case is parallax. 422 var scrollElms = '#drupal-modal, .is-b-scroll'; 423 if (me.options.container) { 424 scrollElms += ', ' + me.options.container.trim(); 425 } 426 me.options.container = scrollElms; 427 428 // Attempts to fix for Views rewrite stripping out data URI causing 404. 429 me.fixMissingDataUri(); 430 431 // Swap lazy attributes to let supportive browsers lazy load them. 432 me.doNativeLazy(); 433 434 // Put the blazy/IO instance into a public object for references/ overrides. 435 // If native lazy load is supported, the following will skip internally. 436 me.init = me.run(me.options); 437 438 // Runs after init. 439 me.afterInit(); 440 }; 441 442 /** 443 * Blazy utility functions. 444 * 445 * @param {HTMLElement} elm 446 * The .blazy/[data-blazy] container, not the lazyloaded .b-lazy element. 447 * 448 * @todo reenable initBlazy here if any issue with the following: 449 * Each [data-blazy] may or may not: 450 * - be ajaxified, be lightboxed, have uniform or different sizes, and 451 * have few more unique features per instance, etc. 452 */ 453 function doBlazy(elm) { 454 var me = Drupal.blazy; 455 var dataAttr = elm.getAttribute('data-blazy'); 456 var opts = (!dataAttr || dataAttr === '1') ? {} : (_db.parse(dataAttr) || {}); 457 var isUniform = me.contains(elm, 'blazy--field') || me.contains(elm, 'block-grid') || me.contains(elm, 'blazy--uniform'); 458 var instance = (Math.random() * 10000).toFixed(0); 459 var eventId = 'blazy.uniform.' + instance; 460 var localItems = elm.querySelector('.media--ratio') === null ? [] : elm.querySelectorAll('.media--ratio'); 461 462 me.options = _db.extend(me.options, opts); 463 me.revalidate = me.revalidate || elm.classList.contains('blazy--revalidate'); 464 elm.classList.add('blazy--on'); 465 elm.blazyInstance = instance; 466 467 if (isUniform) { 468 elm.blazyUniform = true; 469 } 470 471 me.instances.push(elm); 472 473 var swapRatio = function (e) { 474 var pad = e.detail.pad || 0; 475 476 if (pad > 10) { 477 _db.forEach(localItems, function (cn) { 478 cn.style.paddingBottom = pad + '%'; 479 }, elm); 480 } 481 }; 482 483 // Reduces abrupt ratio changes for the rest after the first loaded. 484 // To support resizing, use debounce. To disable use {once: true}. 485 if (isUniform && localItems.length > 0) { 486 _db.bindEvent(elm, eventId, swapRatio); 487 } 488 } 489 490 /** 491 * Attaches blazy behavior to HTML element identified by .blazy/[data-blazy]. 492 * 493 * The .blazy/[data-blazy] is the .b-lazy container, might be .field, etc. 494 * The .b-lazy is the individual IMG, IFRAME, PICTURE, VIDEO, DIV, BODY, etc. 495 * The lazy-loaded element is .b-lazy, not its container. Note the hypen (b-)! 496 * 497 * @type {Drupal~behavior} 498 */ 499 Drupal.behaviors.blazy = { 500 attach: function (context) {
501 // Drupal.attachBehaviors already does this so if this is necessary, 502 // someone does an invalid call. But let's be robust here. 503 // Note: context can be unexpected <script> element with Media library. 504 context = context || document; 505 506 // Originally identified at D7, yet might happen at D8 with AJAX. 507 // Prevents jQuery AJAX messes up where context might be an array. 508 if ('length' in context) { 509 context = context[0]; 510 } 511 512 // Runs Blazy with multi-serving images, and aspect ratio supports. 513 // W/o [data-blazy] to address various scenarios like custom simple works, 514 // or within Views UI which is not easy to set [data-blazy] via UI. 515 _db.once(Drupal.blazy.forEach(context)); 516 } 517 }; 518 519}(Drupal, drupalSettings, dBlazy, this, this.document));
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.