Advertisement

#23 Device Pixel Ratio Image Tag Helper

In this article we will create a new image tag helper that will take care of creating img tags that will respond to the device pixel ratio (DPR) of the users device. This allows us to serve a higher pixel density image for devices that can make use of it while saving a users data by sending down a smaller image for lower resolution devices. In this process of doing this we will create a base class that both of our tag helpers can inherit from which will allow us to keep our code as DRY as possible.

Base class

  • WebUi
    • Features
      • Images
        • ImageTagHelper.cs

In the previous article we created a tag helper that we can use to create device width responsive image tags and in this article we are creating a tag helper for dpr responsive image tags. The information that we need in order to create both tags we just have to format it just a bit differently for both. For this reason we are going to create a base class (a) that both of our tag helpers will inherit from.

ImageTagHelper.cs

using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebUi.Features.Images
{
    [HtmlTargetElement(Attributes = "alt,extension,src-fragment,sizes")]
    public abstract class ImageTagHelper : TagHelper
    {
        [HtmlAttributeName("alt")] public string Alt { get; set; }
        [HtmlAttributeName("extension")] public string Extension { get; set; }
        [HtmlAttributeName("lazy-load")] public bool? LazyLoad { get; set; }
        [HtmlAttributeName("src-fragment")] public string SrcFragment { get; set; }
        [HtmlAttributeName("sizes")] public string Sizes { get; set; }

        protected bool IsLazyLoaded => LazyLoad == null || LazyLoad.Value;
        protected string[] SizeStrings { get; private set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "img";
            output.TagMode = TagMode.StartTagOnly;

            if (IsLazyLoaded)
            {
                output.AddClass("lazyload", HtmlEncoder.Default);
            }

            output.Attributes.Add("alt", Alt);

            SizeStrings = Sizes.Split(',');

            Process(output);
        }

        protected abstract void Process(TagHelperOutput output);

        protected string ImageSrc(string size)
        {
            return $"{SrcFragment}-{size}w.{Extension}";
        }

        protected void SetSrcAndSrcset(TagHelperOutput output, string[] srcset)
        {
            string srcKey;
            string srcsetKey;
            if (IsLazyLoaded )
            {
                srcKey = "data-src";
                srcsetKey = "data-srcset";
            }
            else
            {
                srcKey = "src";
                srcsetKey = "srcset";
            }

            output.Attributes.Add(srcKey, ImageSrc(SizeStrings[0]));
            output.Attributes.Add(srcsetKey, string.Join(',', srcset));
        }
    }
}
(a) Base class for our image tag helpers.
Advertisement

Update the responsive image tag helper

  • WebUi
    • Features
      • Images
        • ResponsiveImageTagHelper.cs

Now that we have a base class to use we need to update our responsive image tag helper to make use of it (b).

ResponsiveImageTagHelper.cs

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebUi.Features.Images
{
    [HtmlTargetElement("responsive-image"]
    public class ResponsiveImageTagHelper : ImageTagHelper
    {
        protected override void Process(TagHelperOutput output)
        {
            var srcset = new string[SizeStrings.Length];
            var sizes = new string[SizeStrings.Length];
            for (var i = 0; i < SizeStrings.Length; i++)
            {
                var size = SizeStrings[i];
                srcset[i] = $"{ImageSrc(size)} {size}w";
                sizes[i] = i < SizeStrings.Length - 1
                    ? $"(max-width: {size}px) {size}px"
                    : $"{size}px";
            }

            output.Attributes.Add("sizes", string.Join(',', sizes));

            SetSrcAndSrcset(output, srcset);
        }
    }
}
(b) Updating our tag helper to take advantage of the base class.

The dpr image tag helper

  • WebUi
    • Features
      • Images
        • DprImageTagHelper.cs

Next up is to finally create our dpr image tag helper (c). As I have mentioned previously it is basically the same as our previous tag helper we just need to change how we format the srcset attribute.

DprImageTagHelper.cs

using System.Linq;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebUi.Features.Images
{
    [HtmlTargetElement("dpr-image")]
    public class DprImageTagHelper : ImageTagHelper
    {
        protected override void Process(TagHelperOutput output)
        {
            var min = int.Parse(SizeStrings.First());

            var srcset = new string[SizeStrings.Length];
            for (var i = 0; i < SizeStrings.Length; i++)
            {
                var size = SizeStrings[i];
                srcset[i] = $"{ImageSrc(size)} {int.Parse(size) / min:D}x";
            }

            SetSrcAndSrcset(output, srcset);
        }
    }
}
(c) The code needed to easily create a dpr image tag.

Examples

  • WebUi
    • Pages
      • Image-Tag-Helper.cshtml

Of course now that we have created the tag helper we need to make sure that it is behaving the way we want. To do this we will add an example for lazy loading disabled and for lazy loading enabled (d).

Image-Tag-Helper.cshtml

...
@if (Model.Problem == "resolution-switching")
{
    ...
    else if (Model.Query == "same-size")
    {
        <div class="image-container">
            <dpr-image src-fragment="/images/responsive-images/woman-balloon-chair-dark"
                       sizes="320,640"
                       extension="jpg"
                       alt="Woman sitting on a chair beside a balloon"
                       lazy-load="false"></dpr-image>
            <div class="image-caption">
                <p>Photo by Daria Shevtsova from Pexels</p>
                <p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
            </div>
        </div>
    }
}
...
else if (Model.Problem == "lazy-loading")
{
    ...
    else if (Model.Query == "same-size")
    {
        for(var i = 1; i <= 10; i++)
        {
            <div class="image-container same-size">
                <dpr-image src-fragment="/images/responsive-images/woman-balloon-chair-dark-Copy@(i)"
                           sizes="320,640"
                           extension="jpg"
                           alt="Woman sitting on a chair beside a balloon"></dpr-image>
                <div class="image-caption">
                    <p>Photo by Daria Shevtsova from Pexels</p>
                    <p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
                </div>
            </div>
        }
    }
    ...
}
(d) Adding the examples for both lazy loading enabled and disabled.
Exciton Interactive LLC
Advertisement