Advertisement

#22 Responsive Image Tag Helper

In this video we will create a tag helper that will make it easier to create responsive image tags. Our tag helper will use some easy to read and understand attributes to generate the necessary attributes on our image tag. Not only will the tag helper make sure that the attributes are formatted correctly if we should ever need to make a change, to say how we implement lazy loading, all we will need to do is update the tag helper and all of our images will be updated without us ever having to track down all of our image tags and update them manually.

UI Code Behind

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

I will start by just adding the code that I use for the user interface in the video so that if anyone is interested you can use it yourself. We start by seeing the model for the page (a).

Image-Tag-Helper.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebUi.Pages
{
    public class ImageTagHelperModel : PageModel
    {
        [BindProperty(SupportsGet = true)] public string Problem { get; set; }
        [BindProperty(SupportsGet = true)] public string Query { get; set; }

        public void OnGet()
        {
        }
    }
}
(a) The model for the page that I use in the video to demonstrate all of the example code.

Example page

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

Now that we have a model we need a page to show our examples on (b).

Image-Tag-Helper.cshtml

@page
@model ImageTagHelperModel
@{
    ViewData["Title"] = "Image Tag Helper";
    const string baseUrl = "/image-tag-helper";
}

@section Styles
{
    <style>
        .image-container {
            margin: 1em auto;
            display: table;
        }

        picture, img {
            display: block;
        }

        picture {
            margin-bottom: 0.5em;
        }

        .image-caption {
            display: table-caption;
            caption-side: bottom;
        }

        .same-size img {
            width: 320px;
        }

        .nav {
            background-color: #ddd;
            display: flex;
            border-bottom: 1px solid #333;
        }

        .nav a, .nav label {
            padding: 1em;
        }

        .nav label {
            min-width: 110px;
            background-color: #0984e3;
            margin: 0;
            color: white;
        }

        .nav a {
            display: block;
            color: #333;
            text-decoration: none;
        }

        .nav a.active {
            color: white;
            background-color: #333;
        }
    </style>
}

<nav class="nav">
    <label>Problems</label>
    <a class="@( Model.Problem == "resolution-switching" ? "active" : "" )"
       href="@baseUrl?problem=resolution-switching&query=different-sizes">Resolution Switching</a>
    <a class="@( Model.Problem == "art-direction" ? "active" : "" )"
       href="@baseUrl?problem=art-direction&query=picture">Art Direction</a>
    <a class="@( Model.Problem == "lazy-loading" ? "active" : "" )"
       href="@baseUrl?problem=lazy-loading&query=different-sizes">Lazy Loading</a>
</nav>

@if (Model.Problem == "resolution-switching")
{
    <nav class="nav">
        <label>Resolution</label>
        <a class="@(Model.Query == "different-sizes" ? "active" : "")"
           href="@baseUrl?problem=resolution-switching&query=different-sizes">Different Sizes</a>
        <a class="@(Model.Query == "same-size" ? "active" : "")"
           href="@baseUrl?problem=resolution-switching&query=same-size">Same Size</a>
    </nav>

    if (Model.Query == "different-sizes")
    {
        <div class="image-container">
            
            <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.Query == "same-size")
    {
        <div class="image-container">
            
            <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 == "art-direction")
{
    <nav class="nav">
        <label>Art</label>
        <a class="@( Model.Query == "picture" ? "active" : "" )" 
           href="@baseUrl?problem=art-direction&query=picture">Picture</a>
    </nav>

    if (Model.Query == "picture")
    {
        <div class="image-container">
            
            <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")
{
    <nav class="nav">
        <label>Lazy</label>
        <a class="@(Model.Query == "different-sizes" ? "active" : "")" 
           href="@baseUrl?problem=lazy-loading&query=different-sizes">Different Sizes</a>
        <a class="@(Model.Query == "same-size" ? "active" : "")"
           href="@baseUrl?problem=lazy-loading&query=same-size">Same Size</a>
        <a class="@(Model.Query == "picture" ? "active" : "")"
           href="@baseUrl?problem=lazy-loading&query=picture">Picture</a>
    </nav>

    if (Model.Query == "different-sizes")
    {
        for(var i = 1; i <= 10; i++)
        {
            <div class="image-container">
                
                <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.Query == "same-size")
    {
        for(var i = 1; i <= 10; i++)
        {
            <div class="image-container same-size">

                <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.Query == "picture")
    {
        for(var i = 1; i <= 10; i++)
        {
            <div class="image-container">
                
                <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>
        }
    }
}
(b) The scaffolding that we will use to display our image tag helper examples.
Advertisement

Importing our tag helpers

  • WebUi
    • Pages
      • _ViewImports.cshtml

In order to use our tag helpers within the pages of our project we first need to import them. To do this we just need to add one line of code to our view imports file (c).

_ViewImports.cshtml

...
@addTagHelper *, WebUi
(c) Importing our tag helpers so that we can use them in the pages of our project.

Creating our responsive image tag helper

  • WebUi
    • Features
      • Images
        • ResponsiveImageTagHelper.cs

First up we are going to create a tag helper that we can use to create an image tag, as shown in (d), that will be able to download one or more different images based on the size of the users display (e).

Example (no lazy loading)

<img alt="Woman sitting on a chair beside a balloon"
     sizes="(max-width: 320px) 320px,
            (max-width: 480px) 480px,
            800px"
     src="/images/responsive-images/woman-balloon-chair-dark-320w.jpg" 
     srcset="/images/responsive-images/woman-balloon-chair-dark-320w.jpg 320w,
             /images/responsive-images/woman-balloon-chair-dark-480w.jpg 480w,
             /images/responsive-images/woman-balloon-chair-dark-800w.jpg 800w">
(d) Example of the format of the img tag that we are tying to make using our tag helper.

ResponsiveImageTagHelper.cs

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebUi.Features.Images
{
    [HtmlTargetElement("responsive-image", Attributes = "alt,extension,src-fragment,sizes")]
    public class ResponsiveImageTagHelper : TagHelper
    {
        [HtmlAttributeName("alt")] public string Alt { get; set; }
        [HtmlAttributeName("extension")] public string Extension { get; set; }
        [HtmlAttributeName("src-fragment")] public string SrcFragment { get; set; }
        [HtmlAttributeName("sizes")] public string Sizes { get; set; }

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

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

            var sizeStrings = Sizes.Split(',');

            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));
            output.Attributes.Add("src", ImageSrc(sizeStrings[0]));
            output.Attributes.Add("srcset", string.Join(",", srcset));
        }

        private string ImageSrc(string size)
        {
            return $"{SrcFragment}-{size}w.{Extension}";
        }
    }
}
(e) The initial code for our responsive image tag helper.

Example usage

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

Now that we have created the tag helper it is time to see how to use it (f). In this example we have three different sizes of the image and our tag helper will automatically set it up so that all of the images will be shown on a device up to their specified width except for the last which will be shown at an width above the second to last specified width.

Image-Tag-Helper.cshtml

...
@if (Model.Problem == "resolution-switching")
{
    ...
    if (Model.Query == "different-sizes")
    {
        <div class="image-container">
            <responsive-image src-fragment="/images/responsive-images/woman-balloon-chair-dark"
                              sizes="320,480,800"
                              extension="jpg"
                              alt="Woman sitting on a chair beside a balloon"></responsive-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>
    }
    ...
}
...
(f) An example of how to use the tag helper

Default Lazy

  • WebUi
    • Features
      • Images
        • ResponsiveImageTagHelper.cs

The last thing we need to do is to set up the tag helper so that by default it should do everything required for the resulting image tag, example shown in (g), will be set up for lazy loading (h). We of course are also making it so we are able to disable the lazy loading features if we so desire.

Example (lazy loading)

<img alt="Woman sitting on a chair beside a balloon"
     class="lazyload"
     sizes="(max-width: 320px) 320px,
            (max-width: 480px) 480px,
            800px"
     data-src="/images/responsive-images/woman-balloon-chair-dark-320w.jpg" 
     data-srcset="/images/responsive-images/woman-balloon-chair-dark-320w.jpg 320w,
                  /images/responsive-images/woman-balloon-chair-dark-480w.jpg 480w,
                  /images/responsive-images/woman-balloon-chair-dark-800w.jpg 800w">
(g) Example of the format of the img tag that is setup for lazy loading.

ResponsiveImageTagHelper.cs

using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.TagHelpers;
...
namespace WebUi.Features.Images
{
    ...
    public class ResponsiveImageTagHelper : TagHelper
    {
        ...
        [HtmlAttributeName("lazy-load")] public bool? LazyLoad { get; set; }
        ...

        private bool IsLazyLoaded => LazyLoad == null || LazyLoad.Value;

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            ...
            string srcKey;
            string srcsetKey;
            if (IsLazyLoaded)
            {
                srcKey = "data-src";
                srcsetKey = "data-srcset";
                output.AddClass("lazyload", HtmlEncoder.Default);
            }
            else
            {
                srcKey = "src";
                srcsetKey = "srcset";
            }
            ...
            output.Attributes.Add(srcKey, ImageSrc(sizeStrings[0]));
            output.Attributes.Add(srcsetKey, string.Join(",", srcset));
        }
        ...
    }
}
(h) Adding code so that we can enable or disable lazy loading. The default will be to enable it.

Updating the examples

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

With that change made we will return to our first example and adjust it so that lazy loading is disable. We also need to add the example that will so lazy loading of our images in action (i).

Image-Tag-Helper.cshtml

...
@if (Model.Problem == "resolution-switching")
{
...
    if (Model.Query == "different-sizes")
    {
        <div class="image-container">
            <responsive-image src-fragment="/images/responsive-images/woman-balloon-chair-dark"
                              sizes="320,480,800"
                              extension="jpg"
                              alt="Woman sitting on a chair beside a balloon"
                              lazy-load="false"></responsive-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")
{
    ...
    if (Model.Query == "different-sizes")
    {
        for(var i = 1; i <= 10; i++)
        {
            <div class="image-container">
                <responsive-image src-fragment="/images/responsive-images/woman-balloon-chair-dark-Copy@(i)"
                              sizes="320,480,800"
                              extension="jpg"
                              alt="Woman sitting on a chair beside a balloon"></responsive-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>
        }
    }
    ...
}
...
(i) Updating the previous example to disable lazy loading and adding an example to show lazy loading in action.
Exciton Interactive LLC
Advertisement