You want the images on your site to be lazy loaded in the most optimal size and format, and still look really sharp on all devices. You don’t want the content editor to have to care about image size, image format, or what proportions the image should have. PictureRenderer can help you with all of this.
If you are server-rendering your Optimizely CMS pages you can use the add-on PictureRenderer that really simplifies rendering an html picture
element. The picture
element provides a set of images in different sizes and formats. It’s then up to the browser to select the most appropriate image depending on screen resolution, viewport width, network speed, and the rules that you set up.
If you are unfamiliar with the details of the picture
element i recommend reading this and/or this.
I will use the standard Alloy demo site as an example of how to optimize images.
Step 1: Add Baaijte.Optimizely.ImageSharp.Web and PictureRenderer.Optimizely
Add Baaijte.Optimizely.ImageSharp.Web and add the configuration as described in the documentation.
Also add PictureRenderer.Optimizely to your solution.
Step2: Define Picture profiles
A Picture profile describes how an image should be scaled in various cases. You could for example create Picture profiles for: “Top hero image”, “Teaser image” or “Image gallery thumbnail”.
Let’s identify some images used on the Alloy site. On one of the product pages there are two image types that I call “Teaser” and “TeaserWide”. These two image types are shown in various sizes depending on viewport width.
Now I will define the picture profiles for these image types. The picture profile defines what images sizes (widths) that the browser can select from, what size the image should have for different viewport widths, the image aspect ratio, etc.
It’s up to you to define the media conditions to use, and how many different sizes you want the browser to select from. On the Alloy site there are quite many breakpoints where the teaser images change size, I haven’t added media conditions for all.
public static class PictureProfiles
{
// Teaser image
// Up to 575 pixels viewport width, the picture width will be 100% of the viewport, minus 40 pixels (left/right margins).
// Between 575 and 991 pixels viewport width, the picture width will be 250 pixels.
// On larger viewport widths, the picture width will be 420 pixels.
public static readonly PictureProfile Teaser = new()
{
SrcSetWidths = new[] { 250, 500, 750 }, // the different image widths that the browser can select from
Sizes = new[] { "(max-width: 575px) calc((100vw - 40px))", "(max-width: 991px) 250px", "480px" },
AspectRatio = 1.777 // 16:9 = 16/9 = 1.777
};
// TeaserWide behave the same as Teaser but has a different aspect ratio.
public static readonly PictureProfile TeaserWide = new()
{
SrcSetWidths = Teaser.SrcSetWidths,
Sizes = Teaser.Sizes,
AspectRatio = 1.5,
};
}
Step 3: Render the Picture element
Use the Picture Html helper to render a picture
element instead of the original img
element in the views. For this particular product page it’s the TeaserBlockWide and PagePartials/Page views that are used..
<!-- replace this -->
<img class="pb-3 pb-lg-0" src="@Url.ContentUrl(Model.Image)" /> -->
<!-- with this -->
@Html.Picture(Model.Image, PictureProfiles.TeaserWide, cssClass: "pb-3 pb-lg-0")
<!-- replace this -->
<div class="img-wrapper mb-3">
@Html.DisplayFor(m => m.PageImage)
</div>
<!-- with this -->
<div class="img-wrapper mb-3" @Html.EditAttributes(x => x.PageImage)>
@Html.Picture(Model.PageImage, PictureProfiles.Teaser)
</div>
<!-- I added EditAttributes on the surrounding div to keep the possibility for on-page editing. -->
The result
If you re-load the page and view the source you will now see the rendered picture
element, containing the different image versions and the media conditions. The three images where in total 117.1kb, and now the same optimized images are just 27.7kb! The browser doesn’t download a bigger image than what’s needed, and it selects the WebP version, which is a more efficient image format.
This is the result when I’m viewing the page in the largest viewport on my external screen. If I view the same page on my laptop screen which has a much higher pixel density, the browser selects larger images so that they look really crisp on that 4k screen.
You would get a similar result if using your phone, since it also has a screen with high pixel density. But not always. If you have a lousy network connection the browser might select smaller images to speed up the loading. That’s the beauty of the picture
element, the browser can select the most appropriate image for every occasion!
The images are also lazy-loaded. Images at the bottom of the page are not loaded until the user starts to scroll down.
If you for some reason are using a browser that doesn’t support the WebP format, it will fall back to using the original format.
Now, even if the editor uploads a 3Mb image with a completely different aspect ratio, it will still be resized, cropped, and converted to the most optimal format.
But one image is cropped so that it’s basically useless!
If the CMS editor uploads an image in a completely different aspect ratio than how it is shown on the page, or if the same image is shown on multiple places with different aspect ratio, the image might be cropped in a way that is unwanted. The center of the image is used by default.
The ImagePointEditor add-on lets an editor select what part of the image that always should be visible, no matter how it is cropped. More details here.
What if I want to have a separate image for mobile screens?
I have seen sites that have a different image for mobile sized screens. Maybe you want to zoom in on the smaller mobile image.
PictureRenderer can handle this too. You create a Picture profile where you define what image should be shown at specifict viewport widths (or some other media condition).
public static readonly PictureProfile MultiImageSample = new()
{
// First image will be resized to 600px width, and will be shown when viewport width is greater than 600px.
// Second image will be resized to 300px width, and will be shown when viewport width is less than 600px.
MultiImageMediaConditions = new[] { new MediaCondition("(min-width: 600px)", 600), new MediaCondition("(max-width: 600px)", 300) },
AspectRatio = 1.777
};
Then in your view you have an array of image references as parameter to the Picture Html helper.
@Html.Picture(new [] { Model.MyImage, Model.MyMobileImage }, PictureProfiles.MultiImageSample)
This approach gives you more control of exactly which images are shown, but you’ll loose some of the other benefits that the picture
element provides. I guess another approach could be to have multiple picture
elements and switch between those using css.
Why are png images not converted to WepP by default?
In many cases the png format is used for it’s lossless compression. The underlying ImageSharp library can’t (yet) create lossless WebP images, meaning that you may get grainy/blurry parts in an image, instead of the solid colors in the original png.
If you still want png images to be converted to WebP you can define that in your Picture profile. Just add CreateWebpForFormat
, which is an array of the formats that will be converted.
public static readonly PictureProfile MySampleImage = new()
{
SrcSetWidths = ... ,
Sizes = ... ,
CreateWebpForFormat = new []{ PictureRenderer.ImageFormat.Jpeg, PictureRenderer.ImageFormat.Png }
};
What about images added in the rich text editor (TinyMCE)?
Those images can also be rendered using a picture element. It’s described here how to do that.
If you have any suggestions for how PictureRenderer could be improved, please let me know in the comments, or in the GitHub discussion section.
At time of writing I used EPiServer.CMS v12.9.0, Baaijte.Optimizely.ImageSharp.Web v2.0.0, and PictureRenderer.Optimizely v2.2.0
Check out this similar solution if you are using a CMS version less than v12.0.
Ombre background vector created by rawpixel.com – www.freepik.com
Great stuff again (as usual) Erik!
Thanks!
Hi Erik
I have tried Baaijte.Optimizely.ImageSharp.Web on my current project but getting similar error on DXP environment that mention in below thread.
Could you please advise how we can fix it? We are using AspNetCore
Please see below thread:
https://world.optimizely.com/forum/developer-forum/cms-12/thread-container/2022/3/image-resizing-for-optimizely-cms-12-doesnt-work/#286454
Hi Shashikant, I’ve answered in the forum thread. Let me know if that helped.
Hi Erik, I’m playing around with this and so far it is really great. However the design I’m working has different aspect ratios for the images in mobile and desktop layouts. Is there a way to do this?
Hi Valina. Please check the “What if I want to have a separate image for mobile screens?”-section above 😉
Hi Erik,
I’m trying out ImagePointEditor and PictureRenderer with Baaijte.Optimizely.ImageSharp in CMS 12 (12.24.0).
In On-page edit the focus point does not work. I’ts as if it is not applied at all.
I tried setting EditAttributes on the surrounding element but for some reason EditAttributes is not working in the block (I tried it in a page view and there EditAttributes works fine).
But even then, the Picture helper renders the url to the image with path starting with “/Episerver/CMS/Content/globalassets….” the epieditmode=true when in edit mode. So I’m not sure if the EditEttributes would work anyway.
Is there any workaround for this?
Hi Linda. What querystring (the entire querystring) do you get for those images when doing on-page editing?
Hi,
I tried to reply yesterday but it doesn’t seem to have been let through the “security”, it said something about the token being wrong.
Anyway:
the img src in edit mode is:
src=”/EPiServer/CMS/Content/globalassets/testimage.jpg,,21?epieditmode=true&width=640&height=800&quality=80″
Hi again,
I’m now using PictureRenderer with ImagePointEditor and the .
Cloudflare imageresizing has been enabled in DXP and I’ve created a CloudflareProfile.
I also created an ImageSharpProfile just to compare.
I render the image using the Picture helper:
@Html.Picture(Model.ViewModel.Image, PictureProfiles.TestBlockCloudflare)
@Html.Picture(Model.ViewModel.Image, PictureProfiles.TestBlockImageSharp)
When I use the ImageSharpProfile the picture now show in on-page edit, but the picture served by cloudflare does not show in on-page edit. They get this format:
When I manyally edit the src to omit EPiServer/CMS/Content/ and the ,,89 in the end, like this:
src=”/cdn-cgi/image/width=768,format=auto,fit=crop,height=489/globalassets/testimage.jpg”
then the image shows in OPE.
What am I doing wrong here?
Kind regards,
Linda
Not taking anything away from Erik’s great work, I just wanted to chime in that if you want responsive images, allow editors to crop them, and use Cloudflare image resizing in DXP, you might be interested in Adaptive Images for Optimizely: https://adaptiveimages.net
I really like Adaptive Images, and you have a good feature there to add cloudflare in startup.cs if using the image resizing in DXP.
Maybe I can add that to the wishlist in PictureRenderer 😉
The customer wants to use PictureRenderer in this project however, and it’s fun to learn and compare.
Hi Linda. I don’t think you are doing anything wrong. I think there is an issue when used in edit mode.
I will take a look at this “soon” 😉
That’s good then! I can stop tearing my hair out 🙂
I’ve worked around it and am using an ImageSharp profile when in OPE and preview.
I’ll keep my eyes oopen for updates!
Hi Linda, please try with the latest version (v3.1) and see if that helps.
Hi Erik,
I updated to latest version of PictureRenderer (v3.1) and it works nicely now in OPE in the DXP Integration environment!
(I’m currently running CMS 12.26.0 there.)
Nice work!!