Elmar Hoefinghoff
Sitecore Developer, Software Engineer, Dipl.-Ing. (FH)

Responsive Bilder mit Kentico 10


Inhalt

  1. Beschreibung
  2. Vorgehensweise
  3. Filter bereitstellen
  4. Varianten definieren
  5. Bilder hochladen
  6. Typ „File“ für einzelne Bilder
  7. Typ „Attachments“ für mehrere Bilder
  8. Unterstützte Bildformate
  9. Verarbeitung
  10. Voraussetzungen
  11. Kentico Settings – URLs and SEO
  12. Web.config – runAllManagedModulesForAllRequests
  13. Macros erweitern
  14. Custom GetAttachments
  15. Custom GetAttachmentsWithVariants
  16. Custom GetAttachmentByGUID
  17. Custom GetSrcByGUID
  18. Custom GetSrcsetByGUID
  19. Beispiel Transformation für einen „File“ Feldtypen
  20. Beispiel Transformation für einen „Attachments“ Feldtypen
  21. Ergebnis
  22. 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 IEnumerable Filters { 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

Kentico 10 Typ File Kentico 10 Typ Attachments

 

Nach dem Upload sollte im Form-Reiter der Page zu sehen sein, dass entsprechende Varianten angelegt wurden:

 

Kentico 10 Formreiter Image Variants


Ü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.

Kentico 10 Edit Images 1Kentico 10 Edit Image 2


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.

Kentico 10 SEO-friendly URL


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();
    List images = 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
    List attachmentWithVariants = 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:

Kentico 10 Image Variant Original

Kentico 10 Image Variant Width 992

Kentico 10 Image Variant Width 480


Quellen

 

Verwandte Artikel

UnterKentico
Kommentare
Es wurden noch keine Kommentare zu diesem Eintrag geschrieben.
Kommentar hinzufügen
Vor und Zuname
E-Mail
E-Mail bei weiteren Kommentaren
Mein Kommentar