PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.7
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.7
4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 3.12.1 All 134 releases
optimole-wp / assets / js / modules / image-detector.js

image-detector.js in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization 4.2.7, at assets/js/modules/image-detector.js

152 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Optimole Image Detection Module
3 * Handles above-the-fold image detection and dimension analysis
4 */
5
6 import { optmlLogger } from './logger.js';
7
8 /**
9 * Image detection utilities
10 */
11 export const optmlImageDetector = {
12 /**
13 * Detect images with missing width/height attributes and calculate their dimensions
14 * @param {NodeList} images - Collection of image elements
15 * @returns {Object} Object mapping image IDs to their dimensions
16 */
17 detectImageDimensions: function(images) {
18 const imageDimensions = {};
19
20 images.forEach(img => {
21 try {
22 const id = parseInt(img.getAttribute('data-opt-id'), 10);
23 if (isNaN(id)) return;
24
25 const hasOptSrc = img.hasAttribute('data-opt-src');
26 const hasOptLazyLoaded = img.hasAttribute('data-opt-lazy-loaded');
27
28 // If image has data-opt-src and data-opt-lazy-loaded, use optimized dimensions to fix https://github.com/Codeinwp/optimole-service/issues/1588#issuecomment-3373656185
29 if (hasOptSrc && hasOptLazyLoaded) {
30 const optimizedWidth = parseInt(img.getAttribute('data-opt-optimized-width'), 10);
31 const optimizedHeight = parseInt(img.getAttribute('data-opt-optimized-height'), 10);
32
33 if (!isNaN(optimizedWidth) && !isNaN(optimizedHeight) && optimizedWidth > 0 && optimizedHeight > 0) {
34 imageDimensions[id] = {
35 w: optimizedWidth,
36 h: optimizedHeight
37 };
38
39 optmlLogger.info(`Image ${id} using optimized dimensions:`, {
40 optimizedDimensions: `${optimizedWidth}x${optimizedHeight}`
41 });
42 return; // Skip further processing for this image
43 }
44 }
45
46 const hasWidth = img.hasAttribute('width') && img.getAttribute('width') !== '';
47 const hasHeight = img.hasAttribute('height') && img.getAttribute('height') !== '';
48
49 // Only process images that are missing width or height attributes
50 if (!hasWidth || !hasHeight) {
51 const naturalWidth = img.naturalWidth || 0;
52 const naturalHeight = img.naturalHeight || 0;
53
54 // Only add if we have valid natural dimensions
55 if (naturalWidth > 0 && naturalHeight > 0) {
56 imageDimensions[id] = {
57 w: naturalWidth,
58 h: naturalHeight
59 };
60
61 optmlLogger.info(`Image ${id} missing dimensions:`, {
62 missingWidth: !hasWidth,
63 missingHeight: !hasHeight,
64 naturalDimensions: `${naturalWidth}x${naturalHeight}`
65 });
66 }
67 }
68 } catch (error) {
69 optmlLogger.error('Error detecting dimensions for image:', img, error);
70 }
71 });
72
73 return imageDimensions;
74 },
75
76 /**
77 * Setup intersection observer for image detection
78 * @param {Array} aboveTheFoldImages - Array to store above-fold image IDs
79 * @param {Map} selectorMap - Map for background selectors
80 * @returns {IntersectionObserver} Configured intersection observer
81 */
82 createIntersectionObserver: function(aboveTheFoldImages, selectorMap) {
83 return new IntersectionObserver(entries => {
84 entries.forEach(entry => {
85 if (entry.isIntersecting) {
86 const element = entry.target;
87
88 // Handle img elements with data-opt-id
89 if (element.tagName === 'IMG') {
90 const id = parseInt(element.getAttribute('data-opt-id'), 10);
91 if (!isNaN(id) && !aboveTheFoldImages.includes(id)) {
92 aboveTheFoldImages.push(id);
93 }
94 }
95 // Handle background image elements
96 else if (element.hasAttribute('data-optml-bg-selector')) {
97 const baseSelector = element.getAttribute('data-optml-bg-selector');
98 const specificSelector = element.getAttribute('data-optml-specific-selector');
99
100 if (baseSelector && specificSelector && selectorMap.has(baseSelector)) {
101 // Add this specific selector to the above-fold list for this base selector
102 const aboveTheFoldSelectors = selectorMap.get(baseSelector);
103 if (!aboveTheFoldSelectors.includes(specificSelector)) {
104 aboveTheFoldSelectors.push(specificSelector);
105 optmlLogger.info(`Element with selector "${specificSelector}" is above the fold`);
106 }
107 }
108 }
109 }
110 });
111 }, {
112 threshold: 0.1 // Consider element visible when 10% is in viewport
113 });
114 },
115
116 /**
117 * Observe all images with data-opt-id attributes
118 * @param {IntersectionObserver} observer - Intersection observer instance
119 * @returns {Object} Object containing images and observedElements map
120 */
121 observeOptimoleImages: function(observer) {
122 // Get all images with data-opt-id for processing
123 const allOptimoleImages = document.querySelectorAll('img[data-opt-id]');
124 const observedElements = new Map();
125
126 // Observe all images with data-opt-id
127 allOptimoleImages.forEach(img => {
128 const id = parseInt(img.getAttribute('data-opt-id'), 10);
129 if (isNaN(id)) {
130 optmlLogger.warn('Invalid data-opt-id:', img.getAttribute('data-opt-id'));
131 return;
132 }
133 observedElements.set(img, id);
134 observer.observe(img);
135 });
136
137 return { allOptimoleImages, observedElements };
138 },
139
140 /**
141 * Clean up temporary data attributes from background elements
142 */
143 cleanupBackgroundElements: function() {
144 // After observation is complete, process below-the-fold elements
145 document.querySelectorAll('[data-optml-bg-selector]').forEach(element => {
146 // Remove temporary data attributes
147 element.removeAttribute('data-optml-bg-selector');
148 element.removeAttribute('data-optml-specific-selector');
149 });
150 }
151 };
152