0 comments on “Responsive Images and Sitecore”

Responsive Images and Sitecore

responsive

By February 2018 Australians already spent more than double the amount of time on smartphones than on their desktop1. With the greater variety of devices consumers use to access sites, it’s important to serve images which appropriately cater to those devices.

“Responsive images” describes a technique where an image is served to the browser depending (usually) on the width of the browser window. Desktop browsers would generally receive a larger version of the image with a greater download size, and mobile devices a smaller version better suiting the smaller display size of the device and being quicker to download.

One of our clients has a Sitecore 7.2 site which uses both MVC and legacy code in ASP.NET WebForms. The legacy code had multiple ways of handling image resizing, mostly inline HTML hard coding of dimensions. My aim was to provide a single C# library function that could work both with legacy code and new development and allow consistent results.

imagetag

In the newer part of the website, the designer had used a JavaScript based image processor which would allow the browser to determine the optimal resolution of the image required and only request an appropriately sized image from the server.

As you can see from the above HTML sample each image tag has multiple URLs in the data-srcset attribute. From this list the JavaScript library determines the optimal size for the image, based on device and browser size, and only requests that version of the image from the server.

Sitecore provides a tool to provide the resized image files based on the request URL and also caches these resized images. We created a helper function that could take a Sitecore image as input and return all of the URLs representing valid image sizes as a delimited string. The valid sizes are determined by a setting in a config file. This return value is used to populate the data-srcset attribute of our img.

ImageCodeSample

Sitecore 7.5 and beyond require a hash code to be added to the URL2 to prevent Denial of Service attacks by making numerous requests for images of various sizes. Because our new helper function now provides a centralised and configurable way to deliver the required URL string, this will be quite easy to change after the upgrade.

References

  1. http://www.nielsen.com/au/en/press-room/2018/february-2018-digital-ratings.html
  2. http://www.seanholmesby.com/images-not-resizing-in-sitecore-7-5-sitecore-8-0/
0 comments on “Sitecore Page Speed: Part 2: Inlining CSS into Helix”

Sitecore Page Speed: Part 2: Inlining CSS into Helix

In part 1 of this Sitecore page speed blog, we covered off:

  • The Google Page Speed Insights tool.
  • We looked at a node tool called critical that could generate above the fold (critical viewport) CSS code that is minified.
  • We referenced the way in which Google recommends deferring CSS loading.

In this second part of the Sitecore Page Speed series, I am going to cover off how I would go about achieving this in my Sitecore layout.

Before we dive in I have committed the sample code for this blog into a fork of the helix habitat example project. You can find the sample here. For a direct comparison of changes made to achieve these page load enhancements, view a side by side comparison here.

Installation Steps:

MinimisedCode

  1. For each page that you want to render above the fold CSS we take the minimised code (we generated in the first blog) and put it on the page within the CMS.
  2. Inside the CMS we also create some new renderings in the common project.
    • These renderings are used in the default.cshtml layout.
    • They point to the CSS rendering code.
    • This wrapping technique provides the ability to cache the rendering so that the code in RenderAssetsService.cs does not need to be executed on every single page load.
    • Take note of the IDs of each rendering you will need to copy them over to the CachedRendering IDs shown in step 3 below.
  3. Update the default.cshtml layout with two key renderings.
    • One in the <head> tag that points to /Views/Common/Assets/InlineStyles.cshtml
    •  @*Inline Styles Rendering*@
       @Html.Sitecore().CachedRendering("{B14DA82E-F844-4945-8F31-4577A52861E1}", new RenderingCachingSettings { Cacheable = true, CacheKey = cacheKey + "_critical_styles" })
    • One just before the </body> closing tag that points to /Views/Common/Assets/StylesDeferred.cshtml
    •  @*Styles Rendering Deferred Styles *@
       @Html.Sitecore().CachedRendering("{F04C562A-CBF9-40CF-8CA9-8CE83FDF0BFA}", new RenderingCachingSettings { Cacheable = true, CacheKey = cacheKey + "_bottom_styles" })

StylesDeferred.cshtml  contains logic that will check for inline CSS on every page. If the page contains Inline CSS then the main CSS files will have their network download deferred until later. On the other hand, if the page does not contain any inline CSS then the main CSS files will be loaded as blocking assets. Doing so ensures that the page displays normally in both situations.

  • The cacheKey variable passed to our CachedRendering is simply something to identify the page as unique.  You could use the Sitecore context item ID or path for example.

If done correctly you should end up with pages that look normal even with the main CSS files deleted (only do this as a test). The CSS will no longer load via another network that blocks the page and your Google Page Speed rank should recieve a boost.

0 comments on “Sitecore Page Speed: Part 1 : Above The Fold Content”

Sitecore Page Speed: Part 1 : Above The Fold Content

In this series of blogs, I am going to run through ways in which you can increase your score on the Google Page Speed Insights tool.

If you’re not familiar with PageSpeed Insights head over to the page hosted by Google and enter the URL of a Sitecore project you have recently worked on.  It will give you a rank out of 100 and then advise on what is wrong with the way your page HTML, JavaScript and CSS assets are loaded. It will also provide feedback on the way your server is setup and how assets are cached.

If you’re getting a score below 50 on Desktop or Mobile I would suggest it’s time to look at ways you can improve your layouts and renderings.  A score above 80 and you’re really doing pretty well.

If you got a score of 100 … you should probably be writing this blog instead of me.  🙂

Topic 1: Above the fold content / Critical CSS

One of Google’s recommendations is to use a technique called:

  • Above the Fold
  • Critical CSS

As you can imagine this refers to the CSS required to render only the visible part of the page.

In order to do this, you render the critical CSS inline within the head tag. The rest of your CSS is then loaded using a deferred script block approach.  This is all demonstrated in a simple example on this page.

The way this works is the inline minified CSS is delivered over the network as part of the page payload. It is not an external asset and will not block the page load via another network request. The page can, therefore, display the visible section (above the fold) immediately after the page is downloaded.

The bulk of CSS required by the page can be loaded a few moments later via deferred network requests.

What is the best way to construct the minimised block of Inline CSS you ask?

Lucky for us a very handy node tool called “critical” is available for download.

You can really easily spin up a gulp script that will generate all the critical CSS for the main pages across your Sitecore website.

A full example of a gulp critical script can be found here.  Download it and simply run:

  • npm install
  • gulp

This will generate a series of CSS files that contain critical viewport CSS.


In the next blog post, we will cover off how to integrate this inline CSS (above the fold) into our Sitecore layouts.


References:

 

0 comments on “Resolve RTE responsive image issue”

Resolve RTE responsive image issue

PIC-Responsive_Web_DesignRecently I found an issue with the Sitecores rich text editor causing responsive images to not size correctly. Whenever an image is edited in Sitecores Rich Text Editor (Even if no height or width has been touched) then it adds:

style="height: 123px; width: 123px;"

Before edit:

<img alt="" height="123" width="123" src="~/media/123" />p>

After edit:

<img alt=""  
src="~/media/123;h=123&w=123" style="height: 123px; width: 123px;" />p>

The problem is that the style height and width added by Sitecore messes with the responsiveness of the image.

Solution:

The best way to remove those attributes in a consistent fashion is to strip the attributes on render of the image by overriding this default pipeline: Sitecore.Pipelines.RenderField.GetImageFieldValue.

Implementation:

First add the following class to your solution:

namespace Custom.Business.SC.Extensions.Pipelines.RenderField
{
    using System;

    using HtmlAgilityPack;

    using Sitecore;
    using Sitecore.Diagnostics;
    using Sitecore.Pipelines.RenderField;

    /// <summary>Class that renders a rich text field with removed image dimensions.</summary>
    public class GetFieldValue : Sitecore.Pipelines.RenderField.GetFieldValue
    {
        /// <summary>The process method.</summary>
        /// <param name="args">The render field arguments.</param>
        public new void Process(RenderFieldArgs args)
        {
            base.Process(args);

            // Do not modify output if the field is not a rich text field,
            // or if the page is in page editor mode
            if (args.FieldTypeKey != "rich text" || string.IsNullOrEmpty(args.FieldValue) || Context.PageMode.IsPageEditorEditing)
            {
                return;
            }

            // stripping dimension attributes from images
            Profiler.StartOperation("Stripping dimension attributes from image field: " + args.FieldName);
            args.Result.FirstPart = this.StripImageDimensions(args.Result.FirstPart);
            Profiler.EndOperation();
        }

        /// <summary>The strip image dimensions.</summary>
        /// <param name="text">The text.</param>
        /// <returns>The <see cref="string"/>.</returns>
        private string StripImageDimensions(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return text;
            }

            var outText = text;
            try
            {
                var doc = new HtmlDocument();
                doc.LoadHtml(outText);
                this.StripAttribute(doc, "width");
                this.StripAttribute(doc, "height");
                this.StripAttribute(doc, "style");
                outText = doc.DocumentNode.WriteContentTo();
            }
            catch (Exception)
            {
            }

            return outText;
        }

        /// <summary>The strip attribute.</summary>
        /// <param name="doc">The doc.</param>
        /// <param name="attribute">The attribute.</param>
        private void StripAttribute(HtmlDocument doc, string attribute)
        {
            // HtmlAgilityPack returns null instead of an empty collection when the query finds no results.  
            var nodes = doc.DocumentNode.SelectNodes(string.Format("//img[@{0}]", attribute));
            if (nodes == null || nodes.Count.Equals(0))
            {
                return;
            }

            foreach (var node in nodes)
            {
                node.Attributes[attribute].Remove();
            }
        }
    }
}

Patch in the following config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
            <sitecore>
              <pipelines>
                <renderField>
                  <processor type="Custom.Business.SC.Extensions.Pipelines.RenderField.GetFieldValue,     Custom.Business"
                    patch:instead="*[@type='Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel']" />
                </renderField>
              </pipelines>
            </sitecore>
    </configuration>