-
-
Notifications
You must be signed in to change notification settings - Fork 37
Description
Description
The srcsetWidth(), srcsetWidthWebP(), srcsetMinWidth(), and related methods return incorrect image variants when retina sizes (2x, 3x) are configured in an ImageOptimize field. This appears to be related to the issue addressed in PR #416, though that fix was apparently reverted due to regressions.
Steps to reproduce
- Create an ImageOptimize field with multiple variant widths (e.g., 559, 360, 442, 466, 573)
- Enable retina sizes (2x) for each variant
- In a template, call
srcsetWidthWebP()with DPR mode:{% set optimizedImages = image.ioPostcard %} {{ optimizedImages.srcsetWidthWebP(559, true) }}
Expected behaviour
Should return the 559px variant and its 2x retina version:
.../559px/image.jpg.webp 1x, .../1118px/image.jpg.webp 2x
Actual Behavior
Returns completely unrelated variants:
.../884px/image.jpg.webp 1x, .../932px/image.jpg.webp 1x
Note: 884px and 932px are the 2x versions of other configured variants (442 and 466), not 559.
Source of the Issue
The getSrcsetSubsetArray() method in src/models/OptimizedImage.php uses array index positions to map variantSourceWidths to optimizedImageUrls. This breaks when:
variantSourceWidthscontains duplicates (one entry per retina variant)- Both arrays are sorted independently
array_slice($set, $index, 1, true)attempts to extract variants by index
After sorting, the indices no longer correctly map source widths to actual widths.
Example Data After Sorting
variantSourceWidths: ["360", "360", "442", "442", "466", "466", "559", "559", ...]
optimizedImageUrls keys: [360, 442, 466, 559, 573, 720, 884, 932, 1118, ...]When searching for width 559:
- Matches at indices 6 and 7 in
variantSourceWidths - But
array_slice()at index 6 grabs key 720 (wrong!) - And index 7 grabs key 884 (also wrong!)
Related Issues/PRs
- PR Fix
srcsetwidth filtering #416 - Attempted to fix by removing the sort, but was reverted due to regressions - This affects all
srcset*()methods when retina variants are enabled
Current Workaround
I'm currently using a Composer patch that fixes the getSrcsetSubsetArray() method by:
- Iterating through actual widths in the set
- Determining each width's source width by checking if it's a 1x, 2x, or 3x multiple of any configured variant
- Filtering based on the determined source width rather than array indices
This approach doesn't rely on index positions at all, so it works regardless of sorting and avoids the regressions from PR #416.
The patch is working perfectly on my production site. All srcset*() methods now return correct variants with proper 1x/2x descriptors.
Potential PR
I have a working fix that I'd be happy to submit as a PR if that would be helpful. The solution is, I feel, more robust than PR #416's approach and shouldn't cause regressions since it:
- Doesn't remove sorting (avoiding PR Fix
srcsetwidth filtering #416's issues) - Uses explicit multiplier detection (1x, 2x, 3x)
- Works with both sorted and unsorted data
Let me know if a PR would be welcome.
Patch file contents
--- a/src/models/OptimizedImage.php 2025-11-10 12:25:35
+++ b/src/models/OptimizedImage.php 2025-11-10 12:18:01
@@ -614,39 +614,54 @@
protected function getSrcsetSubsetArray(array $set, int $width, string $comparison): array
{
$subset = [];
- $index = 0;
if (empty($this->variantSourceWidths)) {
return $subset;
}
- // Sort the arrays by numeric key
- ksort($set, SORT_NUMERIC);
- // Sort the arrays by numeric key
- sort($this->variantSourceWidths, SORT_NUMERIC);
- foreach ($this->variantSourceWidths as $variantSourceWidth) {
+
+ // For each actual width in the set, check if its source width matches
+ foreach ($set as $actualWidth => $url) {
+ // Find which variantSourceWidth this actualWidth belongs to
+ // Since retina variants are multiples (1x, 2x, 3x), we need to find the base width
+ $sourceWidth = null;
+ foreach (array_unique($this->variantSourceWidths) as $variantSourceWidth) {
+ // Check if actualWidth is 1x, 2x, or 3x of this variantSourceWidth
+ if ($actualWidth == $variantSourceWidth ||
+ $actualWidth == $variantSourceWidth * 2 ||
+ $actualWidth == $variantSourceWidth * 3) {
+ $sourceWidth = $variantSourceWidth;
+ break;
+ }
+ }
+
+ if ($sourceWidth === null) {
+ continue;
+ }
+
+ // Now check if this sourceWidth matches our comparison criteria
$match = false;
switch ($comparison) {
case 'width':
- if ($variantSourceWidth == $width) {
+ if ($sourceWidth == $width) {
$match = true;
}
break;
case 'minwidth':
- if ($variantSourceWidth >= $width) {
+ if ($sourceWidth >= $width) {
$match = true;
}
break;
case 'maxwidth':
- if ($variantSourceWidth <= $width) {
+ if ($sourceWidth <= $width) {
$match = true;
}
break;
}
+
if ($match) {
- $subset += array_slice($set, $index, 1, true);
+ $subset[$actualWidth] = $url;
}
- $index++;
}
return $subset;Versions
- Craft CMS: 5.7+
- ImageOptimize: 5.0.7
- PHP: 8.2