PageSourceSearch

https://www.futuremediahubs.com/modules/contrib/easy_responsive_images/js/resizer.js?tlek9s

js futuremediahubs.com collected 2026-10-05 20:01:45 UTC 9,037 bytes, 217 lines download raw bytes

1/**
2 * @file
3 * Attach the JavaScript to responsive images to load the best image style.
4 */
5(function (Drupal, drupalSettings) {
6  'use strict';
7
8  Drupal.behaviors.easyResponsiveImages = {
9    attach: function (context) {
10      // Fetch all images containing a "data-srcset" attribute.
11      const images = context.querySelectorAll('img[data-srcset]');
12
13      // Return early if no images were found.
14      if (!images || images.length === 0) {
15        return;
16      }
17
18      // Find the best suitable image to display.
19      const updateImage = function (image) {
20        // When an image is already loading, we need to wait until loading has
21        // finished. Different observers might try to update the image
22        // simultaneously.
23        if (image.hasAttribute('data-loading')) {
24          setTimeout(function () {
25            updateImage(image);
26          }, 100);
27          return;
28        }
29
30        // Check if we need to apply a multiplier to the width check and load
31        // bigger images.
32        let multiplier = 1;
33        const dataMultiplier = image.getAttribute('data-multiplier');
34        if (dataMultiplier !== null) {
35          multiplier = parseFloat(dataMultiplier.replace('x', ''));
36        }
37        const multiplierMax = multiplier >= 1 ? multiplier : 1;
38        const multiplierFactor = window.devicePixelRatio < multiplierMax ? window.devicePixelRatio : multiplierMax;
39
40        // Check if we need to use the container height to select the best
41        // image.
42        const cover = image.hasAttribute('data-cover');
43
44        // Check the available width for the image based on the multiplier.
45        const imgWidth = Math.floor(image.clientWidth);
46        const imgHeight = Math.floor(image.clientHeight);
47        const parentWidth = Math.floor(image.parentNode.clientWidth);
48        const parentHeight = Math.floor(image.parentNode.clientHeight);
49        const availableWidth = parentWidth > imgWidth ? parentWidth : imgWidth;
50        const availableHeight = parentHeight > imgHeight ? parentHeight : imgHeight;
51        const targetWidth = multiplierFactor * availableWidth;
52        const targetHeight = multiplierFactor * availableHeight;
53
54        // Get the current width and height of the image.
55        const attrWidth = image.getAttribute('width');
56        const attrHeight = image.getAttribute('height');
57
58        // Get all the source values and the current image source.
59        const sources = image.getAttribute('data-srcset').split(',');
60        const currentSrc = image.getAttribute('src');
61
62        // Fetch the image ratio if it is available.
63        let ratioWidth, ratioHeight;
64        if (image.hasAttribute('data-ratio')) {
65          [ratioWidth, ratioHeight] = image.getAttribute('data-ratio').split(':');
66        }
67
68        // If the selected image has a src attribute, and is already bigger than
69        // the available width, we do not update the image.
70        if (image.src && attrWidth && attrWidth > availableWidth && attrHeight && attrHeight > availableHeight) {
71          image.setAttribute('data-loaded', 1);
72          return;
73        }
74
75        // Find the best matching source based on actual image space.
76        let source, responsiveImgPath, responsiveImgWidth, responsiveImgHeight;
77        for (source of sources) {
78          let array = source.split(' ');
79          responsiveImgPath = array[0];
80          responsiveImgWidth = array[1].slice(0, -1);
81          if (cover && parentWidth && parentHeight) {
82            responsiveImgHeight = (responsiveImgWidth * parentWidth) / parentHeight;
83          }
84          else if (ratioWidth && ratioHeight) {
85            responsiveImgHeight = (responsiveImgWidth * ratioHeight) / ratioWidth;
86          }
87          else if (attrWidth && attrHeight) {
88            responsiveImgHeight = (responsiveImgWidth * attrHeight) / attrWidth;
89          }
90          // Stop looking if the image is bigger than the target width and the
91          // image height is bigger than the target height.
92          if (targetWidth < responsiveImgWidth && (!cover || !responsiveImgHeight || targetHeight < responsiveImgHeight)) {
93            break;
94          }
95        }
96
97        // If the image is already loaded, we do not update the image.
98        if (responsiveImgPath === currentSrc) {
99          image.setAttribute('data-loaded', 1);
100          return;
101        }
102
103        // Set a temporary attribute to prevent multiple observers from updating
104        // the image simultaneously.
105        image.setAttribute('data-loading', 1);
106
107        // Update the "src" with the new image path.
108        image.src = responsiveImgPath;
109
110        // Calculate the height from the dimensions of the initial width and
111        // height since this can also cause layout shifts.
112        image.setAttribute('width', availableWidth);
113        if (cover && parentWidth && parentHeight) {
114          image.setAttribute('height', Math.round((availableWidth * parentHeight) / parentWidth));
115        }
116        else if (ratioWidth && ratioHeight) {
117          image.setAttribute('height', Math.round((availableWidth * ratioHeight) / ratioWidth));
118        }
119        else if (attrWidth && attrHeight) {
120          image.setAttribute('height', Math.round((availableWidth * attrHeight) / attrWidth));
121        }
122
123        // Set the "data-loaded" attribute when the image is loaded.
124        image.onload = function () {
125          this.setAttribute('data-loaded', 1);
126
127          // Remove the temporary attribute when the image finishes loading.
128          image.removeAttribute('data-loading');
129
130          // Update the image width/height onload to the actual image
131          // dimensions.
132          if (cover || !ratioWidth || !ratioHeight) {
133            this.setAttribute('width', Math.floor(this.width));
134            this.setAttribute('height', Math.floor(this.height));
135          }
136        };
137      };
138
139      // Create a ResizeObserver to update the image "src" attribute when its
140      // parent container resizes.
141      const resizeObserver = new ResizeObserver((entries) => {
142        for (let entry of entries) {
143          const images = entry.target.querySelectorAll('img[data-srcset]');
144          images.forEach((image) => {
145            updateImage(image);
146          });
147        }
148      });
149
150      // Define an offset to preload lazyloaded images before they are visible
151      // in the viewport. Default offsets are based on connection as documented
152      // on https://web.dev/articles/browser-level-image-lazy-loading#distance-from-viewport
153      let lazyLoadingThreshold = 1250;
154
155      if(drupalSettings?.easyResponsiveImages?.lazyLoadingThreshold) {
156        lazyLoadingThreshold = parseInt(drupalSettings.easyResponsiveImages.lazyLoadingThreshold);
157      }
158
159      const connection = navigator.connection;
160      const connectionType = connection?.effectiveType;
161      const connectionSaveData = connection?.saveData;
162
163      // Double the preloadOffset for 3G connections or when saveData is enabled.
164      if(connectionType === "3G" || connectionSaveData) {
165        lazyLoadingThreshold = lazyLoadingThreshold * 2;
166      }
167
168      // Create an IntersectionObserver to update the image "src" attribute when
169      // it is visible in the viewport.
170      const intersectionObserver = new IntersectionObserver((entries) => {
171        for (let entry of entries) {
172          if (entry.isIntersecting) {
173            // Remove the IntersectionObserver when the image is visible.
174            intersectionObserver.unobserve(entry.target);
175
176            // Load the correct image.
177            updateImage(entry.target);
178
179            // Attach the ResizeObserver to the image container.
180            resizeObserver.observe(entry.target.parentNode);
181          }
182        }
183      }, {rootMargin: lazyLoadingThreshold + 'px'});
184
185      // Attach the ResizeObserver and IntersectionObserver to the images.
186      images.forEach((image) => {
187        // Make sure we only attach the observers once.
188        if (image.hasAttribute('data-observed')) {
189          return;
190        }
191
192        // Mark that the image the observers are attached.
193        image.setAttribute('data-observed', '1');
194
195        // Attach the IntersectionObserver to the image if it is using lazy
196        // loading, otherwise attach the ResizeObserver.
197        if(image.hasAttribute('loading') && image.getAttribute('loading') === 'lazy') {
198          // When an image is using lazy loading, we unset the src to prevent it
199          // from being loaded directly. This prevents the browser from lazy
200          // loading the default image before we get the chance to set the
201          // correct src.
202          image.removeAttribute('src');
203
204          // Attach the IntersectionObserver to the image.
205          intersectionObserver.observe(image);
206        } else {
207          // Load the correct image directly.
208          updateImage(image);
209
210          // Attach the ResizeObserver to the image container.
211          resizeObserver.observe(image.parentNode);
212        }
213      });
214    },
215  };
216
217})(Drupal, drupalSettings);

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.