In one of our Sitecore-based projects, we faced the issue of rendering SVG images.
The thing is that Sitecore JSS doesn’t support the rendering of SVG images as <svg> </svg> elements.
Working with Sitecore 8, we had to extend the config file so that Media Library could support the SVG format.
With Sitecore 9, we get the support of an SVG format out of the box: you can add an SVG image to the Medial Library as an image template, and, consequently, a content author can use it anywhere as a regular image.
Sitecore 9.2 Sample:
As a result of such rendering, an SVG file will be embedded via an <img> element like a reference in the src attribute:
Still, this way of rendering has some disadvantages (from the Mozilla site):
- You cannot manipulate the image with JavaScript
- If you want to control the SVG content with CSS, you must include inline CSS styles in your SVG code. (External stylesheets invoked from the SVG file take no effect.)
- You cannot restyle the image with CSS pseudoclasses (like :focus).
So We Developed Another Solution
The way of rendering SVG images within the <img> element didn’t match the requirements of our project. We needed to have SVG images rendered as <svg> </svg> elements.
That’s why we decided to implement the following behavior:
- If the content author adds a non-SVG image (e.g. png, jpg, etc.) to the data source, the result will be a regular image
- If the content author applies SVG in the data source, the result will be like this:
Following are the instructions on how to implement #2 by extending some places in Sitecore:
1. Extend renderField pipeline by creating a new custom ImageRenderer processor inherited from Sitecore.Xml.Xsl.ImageRenderer (Sitecore.Kernel.dll) and override Render() method.
public override RenderFieldResult Render()
{
if (Item == null || Parameters == null)
{
return RenderFieldResult.Empty;
}
var field = Item.Fields[FieldName];
if (field != null)
{
_imageField = new ImageField(field, FieldValue);
if (_imageField.MediaItem != null)
{
var imageMediaItem = new MediaItem(_imageField.MediaItem);
if (imageMediaItem.MimeType.Equals("image/svg+xml", StringComparison.Ordinal))
{
ParseNodeForSvg(Parameters);
return new RenderFieldResult(RenderSvgImage(imageMediaItem));
}
}
}
return base.Render();
}
This method checks if the MimeType type equals “image/svg+xml” and returns RenderFieldResult as a streamed media item string in the SVG XML format; otherwise it calls to the base rendering method.
2. Create a new GetImageFieldValue type inherited from Sitecore.Pipelines.RenderField.GetImageFieldValue (Sitecore.Kernel.dll) and override CreateRenderer method. This method creates a new instance of the CustomImageRenderer processor created in step #1.
public class CustomGetImageFieldValue : GetImageFieldValue
{
protected override ImageRenderer CreateRenderer()
{
return new CustomImageRenderer();
}
}
3. Extend getFieldSerializer pipeline by creating new custom GetFieldSerializer (Sitecore.LayoutService.dll) processor and override SetResult method. This method sets the result of serialization into GetFieldSerializerPipelineArgs.Result:
public class CustomGetImageFieldSerializer : GetImageFieldSerializer
{
public CustomGetImageFieldSerializer(IFieldRenderer fieldRenderer) : base(fieldRenderer)
{
}
protected override void SetResult(GetFieldSerializerPipelineArgs args)
{
Assert.ArgumentNotNull(args, nameof(args));
args.Result = new CustomImageFieldSerializer(FieldRenderer);
}
}
4. Create a new custom ImageFieldSerializer type inherited from Sitecore.LayoutService.Serialization.FieldSerializers.ImageFieldSerializer (Sitecore.LayoutService.dll) and override ParseRenderedImage() method. This method parses the rendered field to Dictionary. If the rendered field has the SVG code, it will put this code into the dictionary with the “svgCode” key. Otherwise, this method will call to the base method:
class CustomImageFieldSerializer : LayoutService.Serialization.FieldSerializers.ImageFieldSerializer
{
public CustomImageFieldSerializer(IFieldRenderer fieldRenderer) : base(fieldRenderer)
{
}
protected override IDictionary<string, string> ParseRenderedImage(string renderedField)
{
var dictionary = new Dictionary<string, string>();
var startSvgTag = "<svg";
var endSvgTag = "/svg>";
var startIndex = renderedField.IndexOf(startSvgTag, StringComparison.Ordinal);
if (startIndex != -1)
{
var lastIndex = renderedField.LastIndexOf(endSvgTag, StringComparison.Ordinal);
var result = renderedField.Substring(startIndex, lastIndex - startIndex + endSvgTag.Length);
dictionary.Add("svgCode", result);
return dictionary;
}
return base.ParseRenderedImage(renderedField);
}
}
5. Create a configuration patch to apply the custom stuff implemented above:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/">
<sitecore>
<pipelines>
<renderField>
<processor
patch:instead="processor[@type='Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel']"
type="Sitecore.Foundation.Extensions.JSS.SVG.Pipelines.RenderField.CustomGetImageFieldValue, Sitecore.Foundation.Extensions.JSS.SVG"
/>
</renderField>
<group>
<pipelines>
<getFieldSerializer>
<processor patch:before="*[@type='Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer.GetImageFieldSerializer, Sitecore.LayoutService']"
type="Sitecore.Foundation.Extensions.JSS.SVG.Pipelines.ImageFieldSerializer.CustomGetImageFieldSerializer, Sitecore.Foundation.Extensions.JSS.SVG"
resolve="true" >
<FieldTypes hint="list">
<fieldType id="1">image</fieldType>
</FieldTypes>
</processor>
</getFieldSerializer>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>
6. In JSS, you can use the following logic:
That is all! Happy coding, Sitecorians! 😊
P.S. Many thanks to a colleague of mine who helped with investigating this topic.
All the source code you need you can find in GitHub.