Render Responsive Image Glass Html Helper with Sitecore Image Processor Module

In today’s world of multi-sized devices / screens, responsive design is almost a must for trendy websites.
The <picture> tag provides a nice solution to the need for serving up resized / different images based on the viewport / orientation of the client screen.

Responsive

I found this article very useful for reading up on the <picture> tag.
http://webdesign.tutsplus.com/tutorials/quick-tip-how-to-use-html5-picture-for-responsive-images–cms-21015

This allows us to efficiently use bandwidth and serve up appropriately sized images based on the screen size AND pixel density. In addition, we could also use different images altogether based on the viewport.

In one of our Sitecore projects we extensively used picture tags. We used it in tandem with the Sitecore Image Processor Module to resize images based on view port. In our solution, we didn’t make any changes based on screen orientation (which is definitely an option too in the picture tag). We served up differently sized versions of the same image based on the viewport.

We created a glass html helper method to help render the picture tag for a given sitecore item field, passing in the max size of the image per breakpoint.

        public HtmlString RenderResponsivePicture<T>(T model, Expression<Func<T, object>> field, List<Dimensions> dimensions,
            bool isEditable = false, NameValueCollection parameters = null, bool crop = true)
        {
            Image imageField = field.Compile().Invoke(model) as Image;

            if (imageField != null)
            {
                if (Sitecore.Context.PageMode.IsPageEditor)
                {
                    return new HtmlString(GlassHtml.RenderImage(model, field, null, isEditable));
                }
                if (imageField.IsValid())
                {
                    StringBuilder responsiveImageHtml = new StringBuilder();
                    StringBuilder parametersHtml = new StringBuilder();
                    if (parameters != null)
                    {
                        var items = parameters.AllKeys.SelectMany(parameters.GetValues, (k, v) => new { key = k, value = v });
                        foreach (var item in items)
                        {
                            parametersHtml.Append(" " + item.key + "=\"" + item.value + "\"");
                        }
                    }

                    responsiveImageHtml.Append("<picture" + (parametersHtml.Length == 0 ? "" : parametersHtml.ToString()) + ">");

                    if (dimensions != null && dimensions.Count() > 1)
                    {
                        foreach (Dimensions param in dimensions.Take(dimensions.Count - 1))
                        {
                            responsiveImageHtml.Append
                                (string.Format(
                                    "<!--[if IE 9]><video style='display: none;'><![endif]--><source media=\"(min-width: {0}px)\" srcset=\"{1}\"><!--[if IE 9]></video><![endif]-->",
                                    param.ScreenSize, param.IsUrlBlank ? string.Empty : ImageHelper.GetResizedSitecoreUrl(imageField.Src, param.Width, param.Height, crop)));
                        }
                        Dimensions lastparam = dimensions.Last();
                        responsiveImageHtml.Append(string.Format("<img srcset=\"{0}\" alt=\"{1}\">",
                            ImageHelper.GetResizedSitecoreUrl(lastparam.IsUrlBlank ? string.Empty : imageField.Src, lastparam.Width, lastparam.Height), imageField.Alt));
                    }
                    responsiveImageHtml.Append("</picture>");

                    return new HtmlString(responsiveImageHtml.ToString());
                }
            }

            return new HtmlString(string.Empty);
        }

        public HtmlString RenderResponsivePicture(Expression<Func<TModel, object>> field,
            List<Dimensions> dimensions, bool isEditable = false, NameValueCollection parameters = null, bool crop = true)
        {
            return RenderResponsivePicture(Model, field, dimensions, isEditable, parameters, crop);
        }

You’ll note that we have an additional line in there to allow for page editor support! So in page editor mode, we would render this as a regular img tag.
Additionally, we had multiple instances where the design called for not showing a particular image in the mobile view at all. To support this, we also added the ability to pass in a token which signifies that a given breakpoint url needs to be blank.

Please note, the List takes the dimensions in the order: Desktop –> Tab –> Mobile.
You could ofcourse choose to be more explicit and have different parameters for each breakpoint.

Where Dimensions is an internal class:

    public class Dimensions
    {
        public int ScreenSize { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public bool IsUrlBlank { get; set; }

        public Dimensions(int width, int height)
        {
            Width = width;
            Height = height;
        }

        public Dimensions()
        { }
    }

In our project, we also had to support IE, so we used PictureFill as well, and you’ll see that the output html also outputs a video tag around the picture tag for IE9 as is needed to support the browser.

Additionally, we also had to render certain images on our site, which were not a part of a sitecore field. A good example of this, would be using an existing image repository for say product images, or user uploaded images which we store in a different location. For this we utilized the following code, which takes in an image url and a set of dimensions and builds the picture tag for you:

        public HtmlString RenderResponsiveNonFieldPicture(string url, List<Dimensions> dimensions,
            string altText = null, NameValueCollection parameters = null)
        {
            if (string.IsNullOrWhiteSpace(url))
            {
                return new HtmlString(string.Empty);
            }

            StringBuilder responsiveImageHtml = new StringBuilder();
            StringBuilder parametersHtml = new StringBuilder();
            if (parameters != null)
            {
                var items = parameters.AllKeys.SelectMany(parameters.GetValues, (k, v) => new { key = k, value = v });
                foreach (var item in items)
                {
                    parametersHtml.Append(" " + item.key + "=\"" + item.value + "\"");
                }
            }

            responsiveImageHtml.Append("<picture" + (parametersHtml.Length == 0 ? "" : parametersHtml.ToString()) + ">");
            if (dimensions != null && dimensions.Count() > 1)
            {
                foreach (Dimensions param in dimensions.Take(dimensions.Count - 1))
                {
                    responsiveImageHtml.Append
                        (string.Format("<!--[if IE 9]><video style='display: none;'><![endif]--><source media=\"(min-width: {0}px)\" srcset=\"{1}\"><!--[if IE 9]></video><![endif]-->",
                        param.ScreenSize, param.IsUrlBlank ? string.Empty : ImageHelper.GetResizedExternalUrl(url, param.Width, param.Height)));
                }
                Dimensions lastparam = dimensions.Last();
                responsiveImageHtml.Append(string.Format("<img srcset=\"{0}\" alt=\"{1}\">",
                    ImageHelper.GetResizedExternalUrl(lastparam.IsUrlBlank ? string.Empty : url, lastparam.Width, lastparam.Height), altText ?? string.Empty));
            }
            responsiveImageHtml.Append("</picture>");

            return new HtmlString(responsiveImageHtml.ToString());
        }

There are 2 underlying methods here which actually build the url for the resized images. For images in Sitecore, we used the Sitecore Image Processor Module. Going through the documentation, you’ll see that all we need to do is add the querystring parameter ‘usecustomfunctions’ to kick off the Image processor module resizing. So here’s the method we used:

public static string GetResizedSitecoreUrl(string imageUrl, int width, int height, bool centerCrop = true)
        {
            if (string.IsNullOrWhiteSpace(imageUrl)) return string.Empty;

            if (height > 0)
                imageUrl = imageUrl + (imageUrl.Contains('?') ? "&h=" : "?h=") + height;
            if (width > 0)
                imageUrl = imageUrl + (imageUrl.Contains('?') ? "&w=" : "?w=") + width;
            if (centerCrop)
                imageUrl = imageUrl + (imageUrl.Contains('?') ? "&" : "?") + "usecustomfunctions=1&centercrop=1";

            return imageUrl;
        }

For external images, we ended up writing out own code to resize images. You’ll see, we also added an additional control to determine the quality of the resized image. You could also choose to build out your own image resizing module with this code.
We used a http handler for this, and made it asynchronous, so that the image resizing happens asynchronously!

So the method we use to get the resized url basically created a url to the http handler, passing in the width / height parameters:

        public static string GetResizedExternalUrl(string url, int width, int height, bool crop = true)
        {
            if (string.IsNullOrWhiteSpace(url))
            {
                return url;
            }

            return HttpUtility.HtmlDecode(Constants.Url.ImageResizer + "?path=" + HttpUtility.HtmlEncode(url)
                + "&w=" + width) + "&h=" + height + "&crop=" + (crop ? "true" : "false"));
        }

In the http handler, we do the actual image resizing, which you can see here: Resize Images with Crop and Quality Control.

Advertisements

, , , , , , ,

  1. Resize and lazy load images in Rich Text fields in Sitecore | Tech Musingz

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: