/ assets / thumbhash-blur.js
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  })();