
Responsive Bilder mit Kentico 10
Inhalt
- Beschreibung
- Vorgehensweise
- Filter bereitstellen
- Varianten definieren
- Bilder hochladen
- Typ „File“ für einzelne Bilder
- Typ „Attachments“ für mehrere Bilder
- Unterstützte Bildformate
- Verarbeitung
- Voraussetzungen
- Kentico Settings – URLs and SEO
- Web.config – runAllManagedModulesForAllRequests
- Macros erweitern
- Custom GetAttachments
- Custom GetAttachmentsWithVariants
- Custom GetAttachmentByGUID
- Custom GetSrcByGUID
- Custom GetSrcsetByGUID
- Beispiel Transformation für einen „File“ Feldtypen
- Beispiel Transformation für einen „Attachments“ Feldtypen
- Ergebnis
- Quellen
Kentico 10 Implementierung „Responsive Images“
Beschreibung
Kentico 10 bietet die Möglichkeit, verschiedene Filter auf Bilder anzuwenden, sobald diese hochgeladen werden. Zusammen mit „File“ oder „Attachments“ Feld-Typen können hier automatisch verschiedene Varianten von Bildern erstellt werden. Diese dienen dann als unterschiedliche Bildquelle abhängig vom Endgerät. Auf diese Weise können Redakteure einmal ein Bild hochladen und Kentico übernimmt automatisch den Rest, um möglichst optimale Responsivität zu gewährleisten, ohne dass die Redakteure hier mit in die Verantwortung kommen.
In diesem Beitrag beschränken wir uns auf die Breite der Bilder. Man kann daneben jeden erdenklichen Bildfilter selber mit einhängen, z. B. Filter für Farbe, Rotationen oder ähnliche Bildeffekte. Dies würde hier aber zu weit gehen. So verzichte ich hier z.B. bewusst auf die Möglichkeiten Bilder mit verschiedenen Pixel-Dichten für Endgeräte mit extrem hoher Auflösung bereitzustellen.
Vorgehensweise
Filter bereitstellen
Zuerst müssen entsprechende Filter codeseitig implementiert werden. In unserem Beispiel reicht uns ein Filter, der die Breite der Bilder ändert.
using CMS.Core; using CMS.Helpers; using CMS.ResponsiveImages; using System; using System.IO; public class ResizeImageFilter : IImageFilter { private int Width { get; set; } public ResizeImageFilter(int width) { this.Width = width; } public ImageContainer ApplyFilter(ImageContainer container) { switch (container.Metadata.Extension.ToLower()) { case ".bmp": case ".gif": case ".jpg": case ".jpeg": case ".png": try { using (Stream imageStream = container.OpenReadStream()) { ImageHelper imageHelper = new ImageHelper(BinaryData.GetByteArrayFromStream(imageStream)); int[] dimensions = imageHelper.EnsureImageDimensions(this.Width, 0, 0); int newWidth = dimensions[0]; int newHeight = dimensions[1]; byte[] newImageBytes = imageHelper.GetResizedImageData(newWidth, newHeight); using (MemoryStream resizedImageData = new MemoryStream(newImageBytes)) { ImageMetadata metadata = new ImageMetadata(newWidth, newHeight, container.Metadata.MimeType, container.Metadata.Extension); return new ImageContainer(resizedImageData, metadata); } } } catch (ArgumentException exc) { throw new ImageFilterException("Failed to resize the image.", exc); } default: throw new ImageFilterException(string.Format("The '{0}' extension is not supported by the resize image filter!", container.Metadata.Extension.ToLower())); } } }
Ich verwende hier den in Kentico integrierten Bildverkleinerer aus dem ImageHelper. Die Qualität scheint nach ersten Eindrücken deutlich verbessert worden zu sein.
Dieser Filter verkleinert alle von Kentico direkt unterstützten Bildformate in die angegebene Breite.
Varianten definieren
Als nächstes muss man Varianten definieren. Eine Variante definiert sich über die auf ebendiese angewendeten Filter. Dabei können auch mehrere Filter angewendet werden. In diesem Beispiel erstelle ich mehrere Varianten, die sich nur in der Breite unterscheiden.
using CMS.ResponsiveImages; using System.Collections.Generic; [assembly: RegisterImageVariantDefinition(typeof(nkResponsiveImages_Width768VariantDefinition))] class nkResponsiveImages_Width768VariantDefinition : ImageVariantDefinition { private string _identifier = "Width768"; public override IEnumerableFilters { get { return new IImageFilter[] { new ResizeImageFilter(768) }; } } public override string Identifier { get { return _identifier; } } }
Dies wiederhole ich für die Breiten 1200px, 992px, 768px, 480px und 320px.
Damit sind alle standard Bootstrap Viewport Sizes sowie zusätzlich die Breite von alten Smartphones abgedeckt.
Bilder hochladen
Nachdem die Filter angelegt und Varianten definiert wurden, erstellt Kentico automatisch entsprechende Varianten, sobald Page Attachments hochgeladen werden.
Leider eben nur Page Attachments. Eine entsprechend vergleichbare Funktion für Media Dateien, die in die Media Library hochgeladen werden, fehlt aktuell (noch?).
Der hier vorgestellte Breiten-Filter erstellt die entsprechende Variante nur dann, wenn die Breite des Originals größer ist als die zu erzeugende Variante.
Ich verwende hier für einzelne Bilder den Feldtyp „File“ und für mehrere Bilder, z. B. für Galerien, den Feldtyp „Attachments“ in eigenen Page Types. Die Redakteure können diese Felder, wie sie es gewöhnt sind, im „Form“-Reiter der entsprechenden Pages editieren. Ich erlaube in beiden Fällen nur Bildformate, die Kentico direkt unterstützt. Das erleichtert das Arbeiten mit Bildern im CMS, kann aber bei Bedarf erweitert werden.
Man kann natürlich ebenso den Kentico Standard Page Type „cms.file“ verwenden oder über den „Attachments“ Reiter in den Page Einstellungen Dateien hochladen. Das Resultat ist entsprechend identisch. Aus Usability Sicht sind diese aber ungünstiger. Die Erfahrung hat gezeigt, dass Redakteure es nicht so gerne mögen solche Dinge in verschiedenen Bereichen einstellen zu müssen.
Typ „File“ für einzelne Bilder |
Typ „Attachments“ für mehrere Bilder |
![]() |
![]() |
Nach dem Upload sollte im Form-Reiter der Page zu sehen sein, dass entsprechende Varianten angelegt wurden:
Über den „Edit“ Dialog der einzelnen Bilder kann jedes Bild noch vom Redakteur bearbeitet werden. Besonders nützlich ist hier der Bereich „Properties“, in dem man noch den Dateinamen, Titel und die Description der einzelnen Bilder ändern kann.
In Attachments Feldern kann zusätzlich auch die Reihenfolge der Bilder angepasst werden.
Unterstützte Bildformate
Unterstützt werden: BMP, GIF, PNG, JPG und JPEG.
Nativ fehlt die Möglichkeit die immer beliebteren SVGs zu bearbeiten. Daher sollte man überlegen, ob man es seinen Redakteuren zumuten möchte mit SVGs zu arbeiten. Ich würde fast behaupten wenn SVGs, dann grundsätzlich nur SVGs. Eine Implementierung des responsiven Verhaltens wäre dann vermutlich komplett anders und würde so wie hier vorgestellt wahrscheinlich überhaupt keinen Sinn mehr machen. Ich persönlich verzichte ungerne auf die integrierten Features der Bildbearbeitung zugunsten der redaktionellen Möglichkeiten.
Die Erfahrung zeigt immer wieder, das Bilder aus diversen Quellen direkt in das CMS hochgeladen werden. Die Erwartungshaltung geht hier deutlich in die Richtung, dass für Bildoptimierung das CMS Verantwortung übernehmen muss. Wenn man sich für SVGs als mögliches Bildformat entscheidet, muss man dies bedenken.
Ich würde über die neue Filterfunktionalität einen entsprechenden Bildumwandler integrieren, der SVGs in hochwertige, lossless PNGs umwandelt. Den dafür notwendigen Umwandler muss man dann entsprechend selber implementieren. Dies würde erlauben, auch SVGs hochzuladen ohne auf die Bildbearbeitungsfeatures von Kentico verzichten zu müssen.
Verarbeitung
Wie greife ich aber jetzt auf diese Varianten zu? Wie schreibe ich entsprechende Transformationen oder wie gelange ich über die API an die Bilddaten?
Die von Kentico bereitgestellten Macros und Methoden sind hier leider veraltet und noch stark ausbaufähig. Daher empfehle ich die bestehende Macro Engine zu erweitern.
Voraussetzungen
Damit die Bilder und deren URLs vernünftig funktionieren muss man in Kentico einige Einstellungen prüfen.
Kentico Settings – URLs and SEO
Die „Files friendly URL extension“ sollte entfernt werden, damit nicht jedes File, egal welchen Typs, generell die Endung „.aspx“ bekommt. Die SEO-Abteilung wird das sehr freuen.
Web.config – runAllManagedModulesForAllRequests
Damit die Bildtypen richtig erkannt und verarbeitet werden, muss in der web.config am <modules> Knoten das Attribut „runAllManagedForAllRequests“ hinzugefügt werden.
<system.webserver /><modules runAllManagedModulesForAllRequests="true" />
[...]
Danach sollten alle Bilder mit ihren „echten“ Dateiendungen fehlerfrei im CMS sowie auf den ausgelieferten Seiten funktionieren.
Leider kommt man nicht darum herum, die Bilder über den „GetAttachment“ Pfad (z.B. http://www.meine-seite.de/getattachment/810dc50e-d3a1-4674-9654-2235787e4251/mein-bild.jpg) abzurufen. Aber durch die „echten“ Dateiendungen ist das nicht mehr allzu übel für SEO. Es wäre jedoch sehr wünschenswert wenn Kentico hierfür vernünftige, menschenlesbare URLs ausliefern könnte. Aber leider geht das aktuell nicht ohne Weiteres.
Macros erweitern
Leider fehlen in Kentico hilfreiche Macros für den Umgang mit responsiven Varianten. Daher empfehle ich, einige Macros zu ergänzen.
Custom GetAttachments
Die Attachments aus einem „Attachments“ Feldtypen erhält man am einfachsten über den folgenden Weg:
public static DocumentAttachmentCollection GetAttachments(object[] parameters) { DocumentAttachmentCollection attachments = new DocumentAttachmentCollection(); //get attachements field name string fieldname = ValidationHelper.GetString(parameters[0], ""); if (string.IsNullOrEmpty(fieldname)) return attachments; //get page TreeNode page = DocumentContext.CurrentDocument; if (parameters.Length > 1) { TreeProvider tree = new TreeProvider(); int documentId = ValidationHelper.GetInteger(parameters[1], 0); page = DocumentHelper.GetDocument(documentId, tree); } if (page == null) return attachments; //get attachments attachments = page.GroupedAttachments .Where(group => group.Name.Equals(fieldname)) .FirstOrDefault(); return attachments; }
Als Parameter wird hier der Feldname des „Attachments“ Feldes sowie optional die DocumentID der Page übergeben, in der dieses Feld zu finden ist. Fehlt die DocumentID, wird die aktuelle Page genommen.
Zurück bekommt man eine Collection, die als Datasource für weitere Transformations, z. B. in Repeatern genutzt werden kann.
Hier sind jedoch noch keine Varianten enthalten, sondern nur die Original Attachment Files. In meinem Beispiel reicht dies jedoch aus, da die Varianten nur für das „srcset“ Attribut benötigt werden und nicht selber im Repeater durchlaufen müssen.
Custom GetAttachmentsWithVariants
Benötigt man dennoch die Varianten, dann empfiehlt sich diese Methode:
public static DocumentAttachmentCollection GetAttachmentsWithVariants(DocumentAttachmentCollection attachments) { //local variables DocumentAttachmentCollection collection = new DocumentAttachmentCollection(); Listimages = new List (); //get variants try { foreach (DocumentAttachment attachment in attachments) { AttachmentInfo image = AttachmentInfoProvider.GetAttachmentInfo(attachment.AttachmentGUID, SiteContext.CurrentSiteName); List variants = AttachmentInfoProvider.GetAttachments() .WhereEquals("AttachmentVariantParentID", attachment.AttachmentID) .ToList(); images.Add(image); images.AddRange(variants); } } catch { return collection; } //create collection collection = new DocumentAttachmentCollection(new InfoDataSet (images.ToArray())); return collection; }
Die Collection aus „GetAttachments“ wird hier entsprechend ergänzt.
Custom GetAttachmentByGUID
Ein einzelnes Attachment kann am einfachsten über die GUID abgerufen werden:
public static AttachmentInfo GetAttachmentByGUID(object[] parameters) { AttachmentInfo attachment = new AttachmentInfo(); //read parameters Guid attachmentGuid = ValidationHelper.GetGuid(parameters[0], Guid.Empty); if (attachmentGuid == Guid.Empty) return attachment; //get attachment attachment = AttachmentInfoProvider.GetAttachmentInfo(attachmentGuid, SiteContext.CurrentSiteName); return attachment; }
In dem zurückgelieferten Objekt sind alle Properties des Attachments enthalten.
Custom GetSrcByGUID
Um die URL des Bildes zu erhalten empfehle ich, ebenfalls ein eigenes Macro zu verwenden. Leider fehlt in Kentico ein Macro, welches einfach die AttachmentGUID nimmt und einem eine SEO-freundliche URL des Bildes zurück gibt.
In diesem Beispiel wird grundsätzlich immer die URL des Originalbildes zurück gegeben, selbst wenn die GUID zu einer Variante gehört. Hintergrund ist, dass die Varianten URLs nur in srcset Attributen sinnvoll sind. Das src Attribut sollte auch aufgrund von Browserkompatibilitäten immer zusätzlich dazu vorhanden sein und die URL des Originals enthalten.
public static string GetSrcByGUID(object[] parameters) { string src = ""; //read parameters Guid attachmentGuid = ValidationHelper.GetGuid(parameters[0], Guid.Empty); if (attachmentGuid == Guid.Empty) return src; bool includeSrc = parameters.Length > 1 ? ValidationHelper.GetBoolean(parameters[1], false) : false; //get attachment AttachmentInfo attachment = AttachmentInfoProvider.GetAttachmentInfo(attachmentGuid, SiteContext.CurrentSiteName); if (attachment.AttachmentVariantParentID > 0) attachment = AttachmentInfoProvider.GetAttachmentInfo(attachment.AttachmentVariantParentID, false); if (attachment == null) return src; //get src src = includeSrc ? "src=\"{0}\"" : "{0}"; src = string.Format(src, GetResponsiveImageUrlByGUID(attachment.AttachmentGUID, attachment.AttachmentVariantDefinitionIdentifier)); return src; }
Custom GetSrcsetByGUID
Diese Methode ist wohl die wichtigste Implementierung für unsere responsiven Bilder. Sie liefert alle URLs jeder enthaltenen Variante zurück.
public static string GetSrcsetByGuid(object[] parameters) { string srcset = ""; //read parameters Guid attachmentGuid = ValidationHelper.GetGuid(parameters[0], Guid.Empty); if (attachmentGuid == Guid.Empty) return srcset; bool includeSrcset = parameters.Length > 1 ? ValidationHelper.GetBoolean(parameters[1], false) : false; //get attachment AttachmentInfo attachment = AttachmentInfoProvider.GetAttachmentInfo(attachmentGuid, SiteContext.CurrentSiteName); if (attachment.AttachmentVariantParentID > 0) attachment = AttachmentInfoProvider.GetAttachmentInfo(attachment.AttachmentVariantParentID, false); if (attachment == null) return srcset; //get attachment and variants list ListattachmentWithVariants = new List (); attachmentWithVariants.Add(attachment); attachmentWithVariants.AddRange(AttachmentInfoProvider.GetAttachments() .Where(string.Concat("AttachmentVariantParentID = ", attachment.AttachmentID)) .ToList()); //get srcset List srcsetParts = new List (); foreach (AttachmentInfo image in attachmentWithVariants) { string url = GetResponsiveImageUrlByGUID(new object[] { image.AttachmentGUID, image.AttachmentVariantDefinitionIdentifier }).ToString(); string w = string.Format("{0}w", image.AttachmentImageWidth); srcsetParts.Add(string.Format("{0} {1}", url, w)); } srcset = includeSrcset ? "srcset=\"{0}\"" : "{0}"; srcset = string.Format(srcset, srcsetParts.Join(", ")); return srcset; }
Beispiel: Transformation für einen „File“ Feldtypen
Der Typ „File“ speichert die GUID des Attachments der Originaldatei. Mit unseren Macros können wir über diese GUID direkt alle Informationen bekommen die wir brauchen. Hier eine entsprechende Transformation:
<img title="{% NkResponsiveImages.GetAttachmentByGUID(Image).AttachmentDescription #%}" alt="{% NkResponsiveImages.GetAttachmentByGUID(Image).AttachmentTitle #%}" src="{% NkResponsiveImages.GetSrcByGUID(Image) #%}" srcset="{% NkResponsiveImages.GetSrcsetByGUID(Image) #%}" />Beispiel: Transformation für einen „Attachments“ Feldtypen
Bei mehreren Attachments im Feldtyp „Attachments“ müssen wir zuerst die Attachment Collection bekommen. Haben wir diese, können wir eine weitere Transformation nutzen um die einzelnen Varianten zu durchlaufen. In meinem Beispiel iteriere ich über Child-Pages eines entsprechenden Typs. Der Repeater übergibt den Feldnamen und die DocumentID des entsprechenden Kindes. Läge das Attachments Feld in der eigenen Page wäre es nicht nötig die DocumentID zu übergeben.
<div class="gallery">
{% NkResponsiveImages.GetAttachments("Images", DocumentID).ApplyTransformation("nkResponsiveImages.Transformations.ImageGalleryItems") #%}
</div>
Die „ImageGalleryItems“ Transformation:
<div class="item">
<a target="_blank" href="{% nkResponsiveImages.GetSrcByGUID(AttachmentGUID) #%}#%}">
<img alt="{% AttachmentTitle %}" title="{% AttachmentDescription %}" src="{% nkResponsiveImages.GetSrcByGUID(AttachmentGUID) #%}" srcset="{% nkResponsiveImages.GetSrcsetByGUID(AttachmentGUID) #%}" />
</a>
<div class="desc">{% AttachmentTitle %}</div>
</div>
Ergebnis
Als Ergebnis bekommen wir ein entsprechendes IMG Element geliefert, das alle responsiven Quellen über das srcset Attribut an den Browser übergibt. Dieser entscheidet final darüber welche Quelle er verwendet.
<img title="The design of Skyrims open world is really beautiful." alt="Skyrim open world" src="http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg" srcset="http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg 3840w, http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg?variant=Width1200 1200w, http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg?variant=Width320 320w, http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg?variant=Width480 480w, http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg?variant=Width768 768w, http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg?variant=Width992 992w" originalPath="http://local.k10mods.com/getattachment/e547d30f-6031-4f72-a825-c03ea9867a33/skyrim-2.jpg" originalAttribute="src" />Die Dateigrößen der verschiedenen Varianten sind signifikant kleiner als die Originaldatei. Dadurch wird besonders auf mobilen Geräten im Funknetz die Performance deutlich verbessert. Durch diese verbesserte Geschwindigkeit verbessert sich auch die Bewertung durch Suchmaschinen, ein Teilfaktor fürs Ranking.
Im Vergleich: