I have been busy for the last few months working on a new SDL Web ‘product’ – the Tridion Standard Implementation, which is an out-of-the box Tridion CMS and web application framework to learn from/build on top of. One of the things we are focusing on is having a single site to serve all devices with optimizations driven by SDL Mobile. Part of this is ensuring that we are smart about serving images to a device, while still making life easy for editors and (MVC) view developers. There turned out to be quite a lot of factors to consider, but I think we came up with a pretty good solution.
Content Manager Implementation
The Standard Implementation is all about doing the basics simply but effectively. As such we expect editors simply to upload content images to the CMS in a single resolution – without needing to know about the sizes required in different layouts or for different devices. This resolution should be high enough to cater for a certain maximum display size, in our case at least 2048 pixels wide. When the content is published, this original image is the one that is published as a binary. There is no resizing, cropping or other manipulation done at upload or publish time – meaning this is a very simple solution (both from a developer and editorial point of view) on the CM side.
A Basic View
The web application is an ASP.NET MVC web app using the Razor view engine. We wanted to hide the complexity of device capabilities from view developers, so our initial pass was to create an Html Helper function Media, with a couple of optional parameters to control the sizing:
The rendered output would output something like the following:
Here you can see the Contextual Image Delivery style URLs containing the appropriate resize instructions. The dimensions are calculated as follows:
1. For percentage widths, use the minimum of our base design width (1024px) and the browser width (detected using the browser:displayWidth claim in SDL Mobile) and multiply it by the width factor. For fixed widths, use this fixed width
2. Take into account the pixel ratio (detected using the browser:pixelRatio claim in SDL Mobile) this means on a x2 ‘retina’ display, our width is doubled
3. Derive the height by dividing the width by the aspect ratio
This worked reasonably well for larger screens, but what we noticed for smaller screens, is that you quickly get a lot of different versions created (and thus cached) by Contextual Image Delivery – screen/browser size for mobile/tablet devices is not standardized. This leads of course to a rather inefficient cache. Our HTML design, which uses Bootstrap will ‘fit’ the image to the containing element, so Its not necessary to have exactly the right size image for a particular view.
As such, we decided to create a fixed set of possible image widths. This is configurable, but we chose a sensible default set of: 160, 320, 640, 1024, 2048*. Once we have done all our other calculations on image width, we pick the first one from this set which is the same size or bigger than our calculated width. Thus for a given view, we only serve only 5 possible variations of an image.
Devil in the detail – Modular Design
As mentioned before – we use Bootstrap, which is great, as it very modular (an element can be rendered with the same basic HTML independent of its container elements).
We wanted to allow view developers to develop in a modular fashion – create a views that could in theory be used anywhere, from a single column layout on a smart phone, to within a 3 column layout on a larger screen, with and without left navigation filling a portion of the screen and so on. This significantly reduces the amount of code duplication required and making views easy to understand and maintain, but our logic as it stands does not take the container size into account: Ïmage size would be the same in a 1 column and 3 column layout, despite a 3 column layout requiring a far smaller version.
Bootstrap works with a grid system for container widths (the default is a 12 unit grid). Whenever you want to render a container, you specify the number of grid units to use as a css class
Typically our container elements are rendered in Page or Region level views, which render child elements (a region or entity (component presentation)) by calling further controller actions. As such, we can pass in the container width to these sub-actions, and continue to pass and slice it until we end up in an Entity View (component presentation). At this point we know the number of grid units we are working with and dividing this by 12 gives us a “container factor” to combine in the width calculation. So long as the container views are correctly passing this container size through, the view developer does not need to know or care in which container elements his view will be rendered.
The World collapses in: Responsive layouts
Another good thing about bootstrap is that it provides a framework to have responsive layouts client side – so that the left navigation collapses into a sort of drop down, and number of columns reduces as the screen size gets smaller to enable everything to ‘fit’ on the screen. The bad thing about this is it totally screws our carefully crafted container grid unit measurements – a 3 column layout with a left navigation might have given us a container of 3 grid units per column on a large screen, but on a very small screen it all collapses to a single full width column (12 units).
Trying to come up with a generic solution for this is impossible, so we boiled it down to the specifics of our design. We work with 4 basic screen width ranges; Large, Medium, Small and Extra-Small** to drive the responsive layout. There is no collapse behaviour until we hit Small, where we show a max of 2 columns and collapse the left navigation. When we move to Extra-Small, we only show a single column.
Thus before we do our container width factoring, we check the screen width. If its Extra small we disregard container width (there is only one full width column) – our “container factor” is 1. If its Small, and the container width is less than or equal to half the full width (6 units) then we set the “container factor” to be 0.5. For other sizes we stick to the original “container factor” calculation.
Our solution is not perfect of course: There will always be scenarios where the images are not resized 100% optimally for the layout/screen width, however what we lose in precision we win in ease of use, and in cache optimization – at the end of the day we do not aim to satisfy all image resizing requirements, simply provide a decent, simple out of the box default.
So what are the alternatives?
1. Manual upload of resized images – the editor crops/resizes and uploads images of different proportions and resolutions. Here you have total control over the size and part of the image displayed. The downside is your editors require extra work and skills, and you are creating a dependency between your raw content and the design of the site and types of screen size. With our solution the editor uploads a single image, and you can change the design width or device screen width breakpoints without having to re-upload or even publish the images.
2. Resize on publish – the editor uploads a single image, but template logic will resize/crop it to different variants on publishing. You lose the total control offered by the previous approach, but save the editor a lot of work. You still have the dependency between content and design (although changing the design is reduced to a republish issue, rather than a re-authoring issue) however if you think about the number of variations of an image required, we are adding a significant overhead into the publishing process. If your site is using Experience Manager this could slow down actions such as the time to update session preview for pages with binaries published in many variations.
Show me the money!
So by now you are convinced. This is how it should be done. Where can I get my hands on this gold dust? you say… Well, be a little more patient, the project is going through stabilization and testing, but should be hitting the shelves towards the end of July. Watch this space!
* Why is this sensible? Well certain popular smart phones have a 320px screen width in portrait orientation, and pixel ratio of 2x, certain popular tablets have a screen width of 1024px in landscape orientation, and pixel ratio of 1x or 2x. Plus our HTML design is centered on a maximum viewport of 1024 pixels.
** What do these represent? Well they roughly correspond to different device types and orientation. The breakpoints between the widths are configurable but set by default to 480px, 940px, 1140px – making most smartphones in portrait mode come in at Extra Small, but in landscape mode at Small, along with tablets in portrait mode. Tablets in landscape mode are typically Medium, and Laptops, Desktops and TVs tend to be Large.