#22 Responsive Image Tag Helper
Saturday, March 16, 2019
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.
Parts
- Part 29: Offset Pager Urls
- Part 28: Offset Pager Start
- Part 27: Mock Context Builder
- Part 26: Mock Repository
- Part 25: Mock Async
- Part 24: Picture Tag Helper
- Part 23: Img DPR Tag Helper
- Part 22: Img Responsive Tag Helper
- Part 21: Img Optimized Display
- Part 20: Img Optimization
- Part 19: Img Lazy Loading
- Part 18: Img Responsive
- Part 17: Bottom Nav
- Part 16: Main Nav Cookie
- Part 15: Main Nav Mobile
- Part 14: Main Nav Search
- Part 13: Main Nav Auth
- Part 12: Main Nav Anchors
- Part 11: Main Nav Logo
- Part 10: Search Results
- Part 9: Search Manager
- Part 8: Search Start
- Part 7: Seeding the Database
- Part 6: Domain Database
- Part 5: Emailing Exceptions
- Part 4: Mailkit
- Part 3: View Renderer
- Part 2: Upgrade to 2.1
- Part 1: Quick Start
UI Code Behind
- WebUi
- Pages
- Image-Tag-Helper.cshtml.cs
- Pages
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()
{
}
}
}
Example page
- WebUi
- Pages
- Image-Tag-Helper.cshtml
- Pages
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>
}
}
}
Importing our tag helpers
- WebUi
- Pages
- _ViewImports.cshtml
- Pages
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
Creating our responsive image tag helper
- WebUi
- Features
- Images
- ResponsiveImageTagHelper.cs
- Images
- Features
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">
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}";
}
}
}
Example usage
- WebUi
- Pages
- Image-Tag-Helper.cshtml
- Pages
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>
}
...
}
...
Default Lazy
- WebUi
- Features
- Images
- ResponsiveImageTagHelper.cs
- Images
- Features
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">
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));
}
...
}
}
Updating the examples
- WebUi
- Pages
- Image-Tag-Helper.cshtml
- Pages
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>
}
}
...
}
...