thumbhash-blur.js
1 (function () { 2 'use strict'; 3 4 function initThumbHashPlaceholders() { 5 document.querySelectorAll('.photo-thumb[data-thumbhash]').forEach(function (wrap) { 6 var b64 = wrap.dataset.thumbhash; 7 if (!b64) return; 8 9 // Decode base64 → Uint8Array → PNG data URL 10 var raw = atob(b64); 11 var bytes = new Uint8Array(raw.length); 12 for (var i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i); 13 var dataURL = thumbHashToDataURL(bytes); 14 15 // Use as background of the wrapper so it shows while the real img loads. 16 // Set via CSS custom property so background-size/position from the 17 // stylesheet always apply correctly (avoids Safari inline-style cascade issues). 18 wrap.style.setProperty('--thumbhash-url', 'url(' + dataURL + ')'); 19 wrap.classList.add('thumbhash-active'); 20 21 var img = wrap.querySelector('img.responsive-image'); 22 if (!img) return; 23 24 function onLoaded() { 25 wrap.classList.add('thumbhash-done'); 26 // Remove background after fade completes 27 setTimeout(function () { 28 wrap.style.removeProperty('--thumbhash-url'); 29 wrap.classList.remove('thumbhash-active', 'thumbhash-done'); 30 }, 500); 31 } 32 33 // lb-loaded is added by lightbox.js when the image finishes loading. 34 // We observe the class mutation rather than the 'load' event to stay 35 // in sync with the existing opacity transition. 36 if (img.classList.contains('lb-loaded')) { 37 onLoaded(); 38 } else { 39 var observer = new MutationObserver(function (mutations) { 40 mutations.forEach(function (m) { 41 if (m.type === 'attributes' && img.classList.contains('lb-loaded')) { 42 observer.disconnect(); 43 onLoaded(); 44 } 45 }); 46 }); 47 observer.observe(img, { attributes: true, attributeFilter: ['class'] }); 48 } 49 }); 50 } 51 52 if (document.readyState === 'loading') { 53 document.addEventListener('DOMContentLoaded', initThumbHashPlaceholders); 54 } else { 55 initThumbHashPlaceholders(); 56 } 57 })();