1/** 2 * @file 3 * Provides Intersection Observer API, or bLazy 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 _firstBlazy = 'blazy--first'; 15 var _isNativeExecuted = false; 16 var _isPictureExecuted = false; 17 var _resizeTick = 0; 18 19 /** 20 * Blazy public methods. 21 * 22 * @namespace 23 */ 24 Drupal.blazy = Drupal.blazy || { 25 context: null, 26 init: null, 27 items: [], 28 ratioItems: [], 29 windowWidth: 0, 30 blazySettings: drupalSettings.blazy || {}, 31 ioSettings: drupalSettings.blazyIo || {}, 32 revalidate: false, 33 options: {}, 34 globals: function () { 35 var me = this; 36 var commons = { 37 isUniform: false, 38 success: me.clearing.bind(me), 39 error: me.clearing.bind(me), 40 selector: '.b-lazy', 41 errorClass: 'b-error', 42 successClass: 'b-loaded' 43 }; 44 45 return _db.extend(me.blazySettings, me.ioSettings, commons); 46 }, 47 48 clearing: function (el) { 49 var me = this; 50 var cn = _db.closest(el, '.media'); 51 var an = _db.closest(el, '[' + _dataAnimation + ']'); 52 53 // Clear loading classes. 54 me.clearLoading(el); 55 56 // Reevaluate the element. 57 me.reevaluate(el); 58 59 // Container might be the el itself for BG, do not NULL check here. 60 me.updateContainer(el, cn); 61 62 // Supports blur, animate.css for CSS background, picture, image, media. 63 if (an !== null || me.has(el, _dataAnimation)) { 64 _db.animate(an !== null ? an : el); 65 } 66 67 // Provides event listeners for easy overrides without full overrides. 68 _db.trigger(el, 'blazy.done', {options: me.options}); 69 70 // Initializes the native lazy loading once the first found is loaded. 71 if (!_isNativeExecuted) { 72 _db.trigger(me.context, 'blazy.native', {options: me.options}); 73 74 _isNativeExecuted = true; 75 } 76 }, 77 78 clearLoading: function (el) { 79 // The .b-lazy element can be attached to IMG, or DIV as CSS background. 80 // The .(*)loading can be .media, .grid, .slide__content, .box, etc. 81 var loaders = [ 82 el, 83 _db.closest(el, '.is-loading'), 84 _db.closest(el, '[class*="loading"]') 85 ]; 86 87 _db.forEach(loaders, function (loader) { 88 if (loader !== null) { 89 loader.className = loader.className.replace(/(\S+)loading/, ''); 90 } 91 }); 92 }, 93 94 isLoaded: function (el) { 95 return el !== null && el.classList.contains(this.options.successClass); 96 }, 97 98 reevaluate: function (el) { 99 var me = this; 100 var ie = el.classList.contains('b-responsive') && el.hasAttribute('data-pfsrc'); 101 102 // In case an error, try forcing it. 103 if (me.init !== null && _db.hasClass(el, me.options.errorClass)) { 104 el.classList.remove(me.options.errorClass); 105 106 // This is a rare case, hardly called, just nice to have for errors. 107 window.setTimeout(function () { 108 me.init.load(el); 109 }, 10); 110 } 111 112 // @see http://scottjehl.github.io/picturefill/ 113 if (window.picturefill && ie) { 114 window.picturefill({ 115 reevaluate: true, 116 elements: [el] 117 }); 118 } 119 }, 120 121 has: function (el, attribute) { 122 return el !== null && el.hasAttribute(attribute); 123 }, 124 125 contains: function (el, name) { 126 return el !== null && el.classList.contains(name); 127 }, 128 129 updateContainer: function (el, cn) { 130 var me = this; 131 var isPicture = _db.equal(el.parentNode, 'picture') && me.has(cn, _dataDimensions); 132 133 // Fixed for effect Blur messes up Aspect ratio Fluid calculation. 134 window.setTimeout(function () { 135 if (me.isLoaded(el)) { 136 // Adds context for effetcs: blur, etc. considering BG, or just media. 137 (me.contains(cn, 'media') ? cn : el).classList.add('is-b-loaded'); 138 139 if (isPicture) { 140 me.updatePicture(el, cn); 141 } 142 143 if (me.has(el, _dataBg)) {
144 _db.updateBg(el, me.options.mobileFirst); 145 } 146 } 147 }); 148 }, 149 150 updatePicture: function (el, cn) { 151 var me = this; 152 var pad = Math.round(((el.naturalHeight / el.naturalWidth) * 100), 2); 153 154 cn.style.paddingBottom = pad + '%'; 155 156 // Swap all aspect ratio once to reduce abrupt ratio changes for the rest. 157 if (!_isPictureExecuted) { 158 _db.trigger(me.context, 'blazy.uniform', {pad: pad}); 159 _isPictureExecuted = true; 160 } 161 }, 162 163 /** 164 * Updates the dynamic multi-breakpoint aspect ratio: bg, picture or image. 165 * 166 * This only applies to Responsive images with aspect ratio fluid. 167 * Static ratio (media--ratio--169, etc.) is ignored and uses CSS instead. 168 * 169 * @param {HTMLElement} cn 170 * The .media--ratio--fluid container HTML element. 171 */ 172 updateRatio: function (cn) { 173 var me = this; 174 var dimensions = _db.parse(cn.getAttribute(_dataDimensions)) || ('dimensions' in me.options ? me.options.dimensions : false); 175 176 if (!dimensions) { 177 me.updateFallbackRatio(cn); 178 return; 179 } 180 181 // For picture, this is more a dummy space till the image is downloaded. 182 var isPicture = cn.querySelector('picture') !== null && _resizeTick > 0; 183 var pad = _db.activeWidth(dimensions, isPicture); 184 185 if (pad !== 'undefined') { 186 cn.style.paddingBottom = pad + '%'; 187 } 188 189 // Fix for picture or bg element with resizing. 190 if (_resizeTick > 0 && (isPicture || me.has(cn, _dataBg))) { 191 me.updateContainer((isPicture ? cn.querySelector('img') : cn), cn); 192 } 193 }, 194 195 updateFallbackRatio: function (cn) { 196 // Only rewrites if the style is indeed stripped out by Twig, and not set. 197 if (!cn.hasAttribute('style') && cn.hasAttribute(_dataRatio)) { 198 cn.style.paddingBottom = cn.getAttribute(_dataRatio) + '%'; 199 } 200 }, 201 202 doNativeLazy: function () { 203 var me = this; 204 var doNative = function (el) { 205 // Reset attributes, and let supportive browsers lazy load natively. 206 _db.setAttrs(el, ['srcset', 'src'], true); 207 208 // Also supports PICTURE or (future) VIDEO which contains SOURCEs. 209 _db.setAttrsWithSources(el, false, true); 210 211 // Mark it loaded to prevent Blazy/IO to do any further work. 212 el.classList.add(me.options.successClass); 213 me.clearing(el); 214 }; 215 216 var onNative = function () { 217 _db.forEach(me.items, doNative); 218 }; 219 220 _db.bindEvent(me.context, 'blazy.native', onNative, {once: true}); 221 }, 222 223 isNativeLazy: function () { 224 return 'loading' in HTMLImageElement.prototype; 225 }, 226 227 isIo: function () { 228 return this.ioSettings && this.ioSettings.enabled && 'IntersectionObserver' in window; 229 }, 230 231 isBlazy: function () { 232 return !this.isIo() && 'Blazy' in window; 233 }, 234 235 forEach: function (context) { 236 var el = context.querySelector('[data-blazy]'); 237 var blazies = context.querySelectorAll('.blazy:not(.blazy--on)'); 238 239 // Various use cases: w/o formaters, custom, or basic, and mixed. 240 // The [data-blazy] is set by the module for formatters, or Views gallery. 241 if (blazies.length > 0) { 242 _db.forEach(blazies, doBlazy, context); 243 } 244 245 // Runs basic Blazy if no [data-blazy] found, probably a single image or 246 // a theme that does not use field attributes, or (non-grid) BlazyFilter. 247 if (el === null) { 248 initBlazy(context); 249 } 250 }, 251 252 run: function (opts) { 253 return this.isIo() ? new BioMedia(opts) : new Blazy(opts); 254 }, 255 256 afterInit: function (context) { 257 var me = this; 258 me.ratioItems = context.querySelector('.media--ratio') === null ? [] : context.querySelectorAll('.media--ratio'); 259 var shouldLoop = me.ratioItems.length > 0; 260 261 var swapRatio = function (e) { 262 var pad = e.detail.pad; 263 264 if (pad > 10) { 265 _db.forEach(me.ratioItems, function (cn) { 266 cn.style.paddingBottom = pad + '%'; 267 }, context); 268 } 269 }; 270 271 var checkRatio = function () { 272 me.windowWidth = _db.windowWidth(); 273 274 if (shouldLoop) { 275 _db.forEach(me.ratioItems, me.updateRatio.bind(me), context); 276 } 277 278 // BC with bLazy, native/IO doesn't need to revalidate, bLazy does.
279 // Scenarios: long horizontal containers, Slick carousel slidesToShow > 280 // 3. If any issue, add a class `blazy--revalidate` manually to .blazy. 281 if (!me.isNativeLazy() && (me.isBlazy() || me.revalidate)) { 282 me.init.revalidate(true); 283 } 284 285 // Provides event listeners for easy overrides without full overrides. 286 // Checks for weird contexts, in case spit out during AJAX, etc. 287 if (context.classList && context.classList.contains(_firstBlazy)) { 288 _db.trigger(context, 'blazy.afterInit', { 289 items: me.items || me.ratioItems, 290 windowWidth: me.windowWidth 291 }); 292 } 293 _resizeTick++; 294 }; 295 296 // Checks for aspect ratio, onload event is a bit later. 297 // @todo use Drupal.debounce if it makes any difference. 298 checkRatio(); 299 _db.bindEvent(window, 'resize', Drupal.debounce(checkRatio, 200, true)); 300 301 // Reduces abrupt ratio changes for the rest after the first loaded. 302 if (me.options.isUniform && shouldLoop) { 303 _db.bindEvent(context, 'blazy.uniform', swapRatio, {once: true}); 304 } 305 } 306 307 }; 308 309 /** 310 * Initialize the blazy instance, either basic, advanced, or native. 311 * 312 * The initialization may take once for basic (not using module formatters), 313 * or per .blazy/[data-blazy] formatter when there are one or many on a page. 314 * 315 * @param {HTMLElement} context 316 * This can be document, or .blazy container w/o [data-blazy]. 317 * @param {Object} opts 318 * The options might be empty for basic blazy, not using formatters. 319 */ 320 var initBlazy = function (context, opts) { 321 var me = Drupal.blazy; 322 // Set docroot in case we are in an iframe. 323 var documentElement = context instanceof HTMLDocument ? context : _db.closest(context, 'html'); 324 325 opts = opts || {}; 326 opts.mobileFirst = opts.mobileFirst || false; 327 documentElement = documentElement || document; 328 if (!document.documentElement.isSameNode(documentElement)) { 329 opts.root = documentElement; 330 } 331 332 me.options = _db.extend({}, me.globals(), opts); 333 me.context = context; 334 335 // Old bLazy, not IO, might need scrolling CSS selector like Modal library. 336 // A scrolling modal with an iframe like Entity Browser has no issue since 337 // the scrolling container is the entire DOM. Another use case is parallax. 338 var scrollElms = '#drupal-modal, .is-b-scroll'; 339 if (me.options.container) { 340 scrollElms += ', ' + me.options.container.trim(); 341 } 342 me.options.container = scrollElms; 343 344 // Swap lazy attributes to let supportive browsers lazy load them. 345 // This means Blazy and even IO should not lazy-load them any more. 346 // Ensures to not touch lazy-loaded AJAX, or likely non-supported elements: 347 // Video, DIV, etc. Only IMG and IFRAME are supported for now. 348 var nativeSelector = me.options.selector + '[loading]:not(.' + me.options.successClass + ')'; 349 me.items = documentElement.querySelector(nativeSelector) === null ? [] : documentElement.querySelectorAll(nativeSelector); 350 if (me.isNativeLazy()) { 351 // Intentionally on the second line to not hit it till verified. 352 if (me.items.length > 0) { 353 me.doNativeLazy(); 354 } 355 } 356 357 // Put the blazy/IO instance into a public object for references/ overrides. 358 // If native lazy load is supported, the following will skip internally. 359 me.init = me.run(me.options); 360 361 // Reacts on resizing per 200ms. 362 me.afterInit(context); 363 }; 364 365 /** 366 * Blazy utility functions. 367 * 368 * @param {HTMLElement} elm 369 * The .blazy/[data-blazy] container, not the lazyloaded .b-lazy element. 370 */ 371 function doBlazy(elm) { 372 var me = Drupal.blazy; 373 var dataAttr = elm.getAttribute('data-blazy'); 374 var opts = (!dataAttr || dataAttr === '1') ? {} : (_db.parse(dataAttr) || {}); 375 376 me.revalidate = me.revalidate || elm.classList.contains('blazy--revalidate'); 377 elm.classList.add('blazy--on'); 378 379 // Initializes native, IntersectionObserver, or Blazy instance. 380 // @todo attempts to optimize nested blazies, remove check if any issue. 381 if (_db.closest(elm, '.blazy') === null) { 382 elm.classList.add(_firstBlazy); 383 opts.isUniform = me.contains(elm, 'blazy--field') || me.contains(elm, 'blazy--grid') || me.contains(elm, 'blazy--uniform'); 384 initBlazy(elm, opts); 385 } 386 } 387 388 /** 389 * Attaches blazy behavior to HTML element identified by .blazy/[data-blazy]. 390 * 391 * The .blazy/[data-blazy] is the .b-lazy container, might be .field, etc. 392 * The .b-lazy is the individual IMG, IFRAME, PICTURE, VIDEO, DIV, BODY, etc. 393 * The lazy-loaded element is .b-lazy, not its container. Note the hypen (b-)! 394 * 395 * @type {Drupal~behavior} 396 */ 397 Drupal.behaviors.blazy = { 398 attach: function (context) {
399 // Drupal.attachBehaviors already does this so if this is necessary, 400 // someone does an invalid call. But let's be robust here. 401 // Note: context can be unexpected <script> element with Media library. 402 context = context || document; 403 404 // Originally identified at D7, yet might happen at D8 with AJAX. 405 // Prevents jQuery AJAX messes up where context might be an array. 406 if ('length' in context) { 407 context = context[0]; 408 } 409 410 // Runs Blazy with multi-serving images, and aspect ratio supports. 411 // W/o [data-blazy] to address various scenarios like custom simple works, 412 // or within Views UI which is not easy to set [data-blazy] via UI. 413 _db.once(Drupal.blazy.forEach(context)); 414 } 415 }; 416 417}(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.