Responsive Images sind in den letzten Jahren in aller Munde seit es Smartphones & Tablets gibt. Die Problematik besteht darin, dass es statt einem Bild für alle Gerätetypen, – welches meistens dazu führt, dass man unnötig große Dateien herunterladen muss – speziell für die entsprechenden Geräte bzw. Bildschirmgröße Bilder bereitstellt.
Seit geraumer Zeit nimmt die Unterstützung der Browser für Responsive Images zu.
Ein kleines Beispiel als Grundlagen anhand meines letzten Urlaubs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <style> img { max-width: 100%; height: auto; } </style> <h2>HtmlHelper für Responsive Grafiken in ASP.NET MVC</h2> <figure> <img srcset="~/Content/Images/Buyukada_Small.jpg 350w, ~/Content/Images/Buyukada_Medium.jpg 700w, ~/Content/Images/Buyukada_Large.jpg 1400w, ~/Content/Images/Buyukada_ExtraLarge.jpg 2800w" src="~/Content/Images/Buyukada_Small.jpg" sizes="(min-width:600px) 50vw, 100vw" alt="Büyükada in Istanbul" /> <figcaption>Büyükada in Istanbul</figcaption> </figure> |
Der Clou bestehet darin, dass das Img-Tag ein neues Attribut „srcset“ bekommt. Dies ist ein Satz von kommaseparierten Bildern. Jedes Bild bekommt einen sogenannten Descriptor, der die Breite (w) des Bildes definiert, damit der Browser die richtige auswählen kann. Im obigen Beispiel sind es bei „Buyukada_Small.jpg“ 350w etc.
Zusätzlich kann man optional ein „sizes“ Attribut definieren. Diese hat den Zweck dem Browser mitzuteilen wie Breit das Bild dargestellt werden soll im aktuellen Viewport. Die Einheit lautet „vw“ (viewport-width). 100vw wären demnach 100% der Viewportbreite. Der Browser geht implizit bei fehlendem sizes von 100vw aus.
Im Beispiel ist eine Kombination (auch optional) mit einem MediaQuery bei mindestens 600px Breite ein vw von 50 angegeben.
Das bedeutet, dass das Bild auf allen Geräten, die einen Viewport von mindestens 600px haben nur 50% breit dargestellt wird,
bei kleiner 600px in voller Viewportbreite.
Die Breite des Bildes beinhaltet auch schon die „Density“ also Dichte.
Bei hochauflösenden Bildschirmen wie z.B. dem Retina-Display ist dies auch nötig wegen sichtbaren Qualitätsverlusten. Die hohen Auflösungen erfordern ebenfalls höhere aufgelöste Bilder.
Die Syntax ist ähnlich. Das „sizes“ Attribut ist unnötig
1 2 3 4 5 6 7 8 | <figure> <img srcset="~/Content/Images/Buyukada_Small.jpg, ~/Content/Images/Buyukada_Medium.jpg 2x, ~/Content/Images/Buyukada_Large.jpg 3x" src="~/Content/Images/Buyukada_Small.jpg" alt="Büyükada in Istanbul" /> <figcaption>Büyükada in Istanbul</figcaption> </figure> |
Mein Ziel ist es einen Html Helper in ASP.NET MVC zu erstellen, der mir die Erstellung von Responsive Images erleichtert. Vorzugsweise als Fluent Api und für beide Descriptorarten, d.h Width und Density. Ein leeres MVC-Projekt reicht aus.
Es bietet sich an eine abstracte Basis-Klasse „BaseResponsiveImage“ zu erstellen mit den Eigenschaften für Bildpfad (ImageSrc),Bildtext (ImageAltText) und Srcset-Dictionary (ImageSrcset)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.Collections.Generic; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { public abstract class BaseResponsiveImage { #region members/variables protected string ImageSrc { get; set; } protected string ImageAltText { get; set; } protected IDictionary<int,string> ImageSrcset { get; set; } #endregion protected BaseResponsiveImage(string imageSrc, string imageAltText) { ImageSrc = imageSrc; ImageAltText = imageAltText; ImageSrcset = new Dictionary<int, string>(); } } } |
Danach wäre es angebracht ein generisches Interface mit einer Methode zum Hinzufügen von einem Bild zum Srcset („AddImageToSrcset“) zu erstellen. Zusätzlich wird IHtmlString implementiert für den späteren Html-Output
1 2 3 4 5 6 7 8 9 | using System.Web; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { public interface IResponsiveImage<T> : IHtmlString where T : class { T AddImageToSrcset(string imagePath, int imageDescriptor); } } |
Danach geht es an die konkrete Implementierung der Descriptor Klassen. Fangen wir mit dem Density an. Dafür eine Klasse „ResponsiveImageWithDensityDescriptor“ anlegen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | using System; using System.Linq; using System.Web; using System.Web.Mvc; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { /// <summary> /// Responsive Image with Density as Descriptor /// </summary> public class ResponsiveImageWithDensityDescriptor : BaseResponsiveImage, IResponsiveImage<ResponsiveImageWithDensityDescriptor> { #region constuctors public ResponsiveImageWithDensityDescriptor(string imageSrc, string imageAltText) : base(imageSrc,imageAltText) {} #endregion public ResponsiveImageWithDensityDescriptor AddImageToSrcset(string imagePath, int imageDescriptor) { ImageSrcset.Add(imageDescriptor, imagePath); return this; } public string ToHtmlString() { var imageTag = new TagBuilder("img"); imageTag.MergeAttribute("src", VirtualPathUtility.ToAbsolute(ImageSrc)); imageTag.MergeAttribute("alt", ImageAltText); if (ImageSrcset.Any()) { AddSrcsetAttribute(imageTag); } return imageTag.ToString(TagRenderMode.SelfClosing); } #region private methods private void AddSrcsetAttribute(TagBuilder imageTag) { imageTag.MergeAttribute("srcset", string.Join(",", ImageSrcset.Select(c => VirtualPathUtility.ToAbsolute(c.Value) + " " + c.Key + "x"))); } #endregion } } |
Da die Klasse „IResponsiveImage“ implementiert muss sie „ToHtmlString()“ mit Leben füllen. Es wird ein image-Tag mit Hilfe des TagBuilders erstellt. VirtualPathUtility sorgt dafür dass der Bildpfad korrekt aufgelöst wird (bei „~/Content/…). Ansonsten kann man die Tilde nicht benutzen. Daraufhin werden eventuell vorhandene Srcset-Einträge kommasepariert zusammengebaut und dem „srcset“-Attribut zugewiesen.
In ähnlicher Weise wird die Klasse für den Width-Descriptor gebaut. Mit dem Unterschied das die Klasse eine „sizes„-Eigenschaft braucht.
Hierfür kann man eine separate Klasse anlegen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { public class ResponsiveSizesAttribute { public ResponsiveImageEnums.MediaQueryWidthType MediaQueryWidthType { get; set; } public int? Width { get; set; } public int ViewPortWidth { get; set; } public ResponsiveImageEnums.MediaQueryWidthUnit MediaQueryWidthUnit { get; set; } } } |
Die Enumerationen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { public class ResponsiveImageEnums { public enum MediaQueryWidthType { Min, Max } public enum MediaQueryWidthUnit { Px, Em, Rem } } } |
Welche somit in der Klasse „ResponsiveImageWithWidthDescriptor“ eingesetzt werden
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Web; using System.Web.Mvc; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { /// <summary> /// Responsive Image with Width as Descriptor /// </summary> public class ResponsiveImageWithWidthDescriptor : BaseResponsiveImage, IResponsiveImage<ResponsiveImageWithWidthDescriptor> { #region members/variables //Default Viewport Width private readonly IList<ResponsiveSizesAttribute> _sizes; #endregion #region constructors public ResponsiveImageWithWidthDescriptor(string imageSrc, string imageAltText) : base(imageSrc, imageAltText) { _sizes = new List<ResponsiveSizesAttribute>(); } #endregion public ResponsiveImageWithWidthDescriptor AddSizes(ResponsiveSizesAttribute size) { _sizes.Add(size); return this; } public ResponsiveImageWithWidthDescriptor AddImageToSrcset(string imagePath, int imageDescriptor) { ImageSrcset.Add(imageDescriptor, imagePath); return this; } public string ToHtmlString() { var imageTag = new TagBuilder("img"); imageTag.MergeAttribute("src", VirtualPathUtility.ToAbsolute(ImageSrc)); imageTag.MergeAttribute("alt", ImageAltText); if (ImageSrcset.Any()) { AddSrcsetAttribute(imageTag); if(_sizes.Any()) AddSizesAttribute(imageTag); } return imageTag.ToString(TagRenderMode.SelfClosing); } #region private methods private void AddSrcsetAttribute(TagBuilder imageTag) { imageTag.MergeAttribute("srcset", string.Join(",", ImageSrcset.Select(c => VirtualPathUtility.ToAbsolute(c.Value) + " " + c.Key + "w"))); } private void AddSizesAttribute(TagBuilder imageTag) { var sizesWithMediaQuery = _sizes.Where(c => c.Width.HasValue) // Werte nehmen,die eine Width haben .Select(c => "(" + c.MediaQueryWidthType.ToString().ToLower() + "-width: " + c.Width + c.MediaQueryWidthUnit.ToString().ToLower() + ") " + c.ViewPortWidth + "vw"); var sizesWithoutMediaQuery = _sizes.Where(c => c.Width.HasValue == false) .Select(c => c.ViewPortWidth + "vw"); imageTag.MergeAttribute("sizes",string.Join(",",sizesWithMediaQuery.Concat(sizesWithoutMediaQuery))); } #endregion } } |
Die AddSizesAttribute-Methode fängt die optionalen Fälle der Viewport-Breite mit und ohne MediaQuerys ab.
Unserem Ziel näherkommend noch eine HtmlHelper
1 2 3 4 5 6 7 8 9 10 11 12 13 | using System; using System.Web.Mvc; namespace MVC_Responsive_Images.Helpers.ResponsiveImage { public static class ResponsiveImageHelper { public static T ResponsiveImage<T>(this HtmlHelper helper, string imageSrc, string imageAltText = "") where T : class { return (T) Activator.CreateInstance(typeof(T),imageSrc,imageAltText); } } } |
Die generische Methode deckt beide Fälle ab (auf Validierung der Parameter habe ich verzichtet) und erstellt je nach Bedarf eine Instanz der gewünschten Descriptor-Klasse.
Somit kommt schlussendlich die Anwendung
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <h3>Html Helper mit Width Descriptor</h3> <figure> @(Html.ResponsiveImage<ResponsiveImageWithWidthDescriptor>("~/Content/Images/Buyukada_Small.jpg", "Büyükada in Istanbul") .AddImageToSrcset("~/Content/Images/Buyukada_Small.jpg", 350) .AddImageToSrcset("~/Content/Images/Buyukada_Medium.jpg", 700) .AddImageToSrcset("~/Content/Images/Buyukada_Large.jpg", 1400) .AddSizes(new ResponsiveSizesAttribute() { MediaQueryWidthType = ResponsiveImageEnums.MediaQueryWidthType.Min, Width = 600, MediaQueryWidthUnit = ResponsiveImageEnums.MediaQueryWidthUnit.Px, ViewPortWidth = 50 }) .AddSizes(new ResponsiveSizesAttribute() { MediaQueryWidthType = ResponsiveImageEnums.MediaQueryWidthType.Max, Width = 900, MediaQueryWidthUnit = ResponsiveImageEnums.MediaQueryWidthUnit.Px, ViewPortWidth = 70 }) .AddSizes(new ResponsiveSizesAttribute() { ViewPortWidth = 35 }) ) </figure> <h3>Html Helper mit Density Descriptor</h3> <figure> @(Html.ResponsiveImage<ResponsiveImageWithDensityDescriptor>("~/Content/Images/Buyukada_Small.jpg", "Büyükada in Istanbul") .AddImageToSrcset("~/Content/Images/Buyukada_Medium.jpg", 2) .AddImageToSrcset("~/Content/Images/Buyukada_Large.jpg", 3) ) </figure> |
Der Vorteil der Fluent Api besteht darin, dass man mit der Methoden-Verkettung sehr flexibel ist. Übrigens die Klammern beim Html-Helper braucht man, weil ansonsten Razor den generischen Typ als Html interpretiert und somit eine Fehlermeldung geworfen wird.
Alternativ hätte man auch zwei HtmlHelper erstellen können um dies zu verhindern.
Das Projekt ist hier in Github verfügbar
Hinterlassen Sie einen Kommentar