I think I figured it out. it seems that the media queries don't work as I expected.
For example, with these rules:
sizes="
(min-width: 1px) ...
(min-width: 480px) ...
(min-width: 768px) ...
"
one would assume that
- Line #1 is true when screen > 1px wide
- Line #2 is true when screen > 480px, overriding line #1
- Line #3 is true when screen > 768px, overriding line #1 and #2
This is not how it works, at least not in practice. I think that the browser just looks for a rule that evaluates to true and calls it a day.
So it just goes:
- Line #1 is true! Done! Applying the rule! Easy!
When I looked at my rules and the result with this logic in mind, it suddenly made sense that the browser insisted on using the biggest image because the calc() function I use for the first line is:
calc( 100vw * (0.94 * 1.0) * 1.0 )
- which is a complicated way of writing windowWidth * 0.94
.
In other words, the browser assumes that the image is always 94% of the entire width of the window and doesn't apply any of the other calculations that take the breakpoints into account.
Anyway, changing the above rules to this:
sizes="
(min-width: 1px) and (max-width: 479px) ...
(min-width: 480px) and (max-width: 767px) ...
(min-width: 768px) and (max-width: 980px) ...
"
makes sure that the rule only applies up to a certain point. Every time the next line evaluates as true, the other lines don't.
So this is what I went with in the end:
<img src="image-fallback.jpg"
srcset="
image-300x200.jpg 300w,
image-480x320.jpg 480w,
image-600x400.jpg 600w,
image-960x640.jpg 960w,
image-1200x800.jpg 1200w"
sizes="
(min-width: 1px) and (max-width: 479px) calc( 100vw * (0.94 * 1.000) * 1.00 ),
(min-width: 480px) and (max-width: 767px) calc( 100vw * (0.94 * 0.905) * 0.50 ),
(min-width: 768px) and (max-width: 980px) calc( 100vw * (0.94 * 0.850) * 0.33 ),
(min-width: 981px) and (max-width: 1088px) calc( 100vw * (0.94 * 0.835) * 0.25 ),
(min-width: 1089px) calc( 1089px * (0.94 * 0.835) * 0.25 ),
280px"
/>
And here's a working example of the above:
Image srcset example (working)
Note: once the browser has loaded a larger image, it's reluctant to load smaller ones. That's what the 'force refresh' button is for.
Note #2: For debugging I added more source images compared to my original question. I thought it would be easier to keep it simple with two sizes but I think I would have never figured this out if I hadn't added a bunch. When I only had 2 images, I thought it always chose the biggest. That isn't true, it just never chose the image I expected ;)