Responsive Images and TYPO3

As I already mentioned in my previous article Implementing Responsive Images you really don’t want to create multiple image sources and the markup for Responsive Images by hand. So I was happy when I saw that version 6.2 LTS of TYPO3 CMS introduced Responsive Images support for content images.

As you might guess the feature can be enabled and configured through some TypoScript settings, in this case in the IMAGE content object. This is how the configuration works:

Source Collection

Let’s start with the easy one: To provide multiple image sizes to the client, you first have to define which sizes should be provided. This is how it works:

myImage = IMAGE  
myImage {  
    file = path/to/my/file.jpg
    file.width = 960

    sourceCollection {
        small {
            width = 320
            srcsetCandidate = 320w
        }

        medium {
            width = 640
            srcsetCandidate = 640w
        }

        big {
            width = 1280
            srcsetCandidate = 1280w
        }
    }
}

With sourceCollection you can define additional image sizes that should be provided to the client. Each size should at least have a width value, which will be the width of the image TYPO3 generates.

One exception of this rule is a simple high dpi setup, which would look like this:

myImage = IMAGE  
myImage {  
    file = path/to/my/file.jpg

    sourceCollection {
        highDpi {
            pixelDensity = 2
            srcsetCandidate = 2x
        }
    }
}

This would generate a 2x equivalent of all you images, which would be used by all high dpi devices, regardless of their viewport size. That is certainly a valid option, but it doesn’t solve the Responsive Images problem I want to tackle with this article.


In the examples I define a second property: srcsetCandidate. This is the value that will appear in the srcset argument of the <img /> tag TYPO3 generates. For Responsive Images we use the w descriptor to tell the client how big the image is so that the appropriate image can be fetched from the server.

Layouts

If you check the TypoScript reference you might notice that there are a few other properties that can be set for source candidates, like dataKey or mediaQuery. This is where things get a bit complicated.

TYPO3 6.2 introduced a generic configuration to specify the way images will be rendered in HTML. Each configuration is called a layout. You can either specify your own layout or you can use those included in css_styled_content:

  • default: Stick to simple <img /> tag without any adjustments
  • srcset: Use <img /> with srcset attribute
  • picture: Use <picture> tag with several <source /> tags
  • data: Use a custom JavaScript implementation with data-* attributes

While these options made sense when TYPO3 6.2 was developed more than a year ago, I would recommend that you only use default (if you don’t want to use Responsive Images) or srcset today. Why is that?

For data I would argue that with excellent polyfills like Picturefill and Respimage the time for custom JavaScript solutions is over. It only makes sense to use the standards the W3C recommends and to polyfill them until they are available in all browsers we want (or need) to support.

With picture the situation isn’t as clear. As I said in my previous article you certainly can and should use the <picture> tag, but only if it’s really necessary. As TYPO3 doesn’t really provide a nice way to choose different aspect ratios for one image, and doesn’t support different file formats, such as webp, there is no reason to use <picture>.

This is why I would stick to the srcset layout for now:

myImage = IMAGE  
myImage {  
    file = path/to/my/file.jpg

    sourceCollection {
        # ...
    }

    layoutKey = srcset
}

So, to answer the question from above: Properties like dataKey or mediaQuery don’t have any effect if you use srcset as your layout. You only need to specify srcsetCandidate.

Tweaking the output

Now that we have a reasonable configuration, let’s test it. For that I borrow the default layouts from css_styled_content and append the image to my page:

myImage = IMAGE  
myImage {  
    file = path/to/my/file.jpg
    file.width = 960

    sourceCollection {
        small {
            width = 320
            srcsetCandidate = 320w
        }

        medium {
            width = 640
            srcsetCandidate = 640w
        }

        big {
            width = 1280
            srcsetCandidate = 1280w
        }
    }

    layoutKey = srcset

    # Borrow predefined layouts from css_styled_content
    layout < tt_content.image.20.1.layout
}

page.20 < myImage  

The output should look like this:

<img  
    src="fileadmin/_processed_/csm_p8070007_822b2a98af.jpg"
    srcset="
        fileadmin/_processed_/csm_p8070007_8946ab9750.jpg 320w,
        fileadmin/_processed_/csm_p8070007_4e94197b4f.jpg 640w,
        fileadmin/_processed_/csm_p8070007_889b354432.jpg 1280w
    "
    alt=""
>

While this looks promising, the code can be improved in some ways:

  • The fallback image should be included in our srcset definition, preferably in the first spot to circumvent the partial implementation in Safari 8.
  • Currently there is no sizes attribute, which means that the browser will load the image based on the viewport width and not the actual size of the displayed image.

The first one isn’t that hard to solve. We need to adjust the srcset layout:

# Borrow predefined layouts from css_styled_content
layout < tt_content.image.20.1.layout

# Adjust srcset layout
layout.srcset.element = <img src="###SRC###" srcset="###SRC### ###WIDTH###w,###SOURCECOLLECTION###"###PARAMS######ALTPARAMS######SELFCLOSINGTAGSLASH###>  

For the record, I added ###SRC### ###WIDTH###w, to the srcset attribute.1

With the second point, the missing sizes attribute, we have a bit of a problem: Depending on the layout of your website the value of the sizes attribute can vary for each image. To tackle this problem, we first need to setup Responsive Images for css_styled_content. Which of course is what you’re here for anyway.

Configuring css_styled_content

Because css_styled_content uses the IMAGE content object, the configuration is quite self-explanatory:

# Clear default source collection
tt_content.image.20.1.sourceCollection >  
# Define image sizes that should be provided for each content image
tt_content.image.20.1.sourceCollection {  
    # For high-column layouts on big screens
    tiny {
        width = 160
        maxW < .width
        srcsetCandidate = 160w
    }
    # For small low-dpi devices or column layouts
    extrasmall {
        width = 320
        maxW < .width
        srcsetCandidate = 320w
    }
    # For medium low-dpi devices (e. g. iPad)
    small {
        width = 460     
        maxW < .width
        srcsetCandidate = 460w
    }
    # For big low-dpi devices (e. g. desktop computers)
    normal {
        width = 600     
        maxW < .width
        srcsetCandidate = 600w
    }
    # For small high-dpi devices (e. g. iPhone 6)
    medium {
        width = 780
        maxW < .width
        srcsetCandidate = 780w
    }
    # For medium high-dpi devices (e. g. iPad Retina)
    large {
        width = 920
        maxW < .width
        srcsetCandidate = 920w
    }
    # For small high-dpi devices (e. g. iPhone 6+)
    extralarge {
        width = 1100
        maxW < .width
        srcsetCandidate = 1100w
    }
    # For big high-dpi devices (e. g. Mac Retina)
    huge {
        width = 1200
        maxW < .width
        srcsetCandidate = 1200w
    }
}

Except for that weird maxW statement, right? Well, TYPO3 lets you define a maximum width for content images with the following TypoScript constant:

styles.content.imgtext.maxW = 600  

Based on that value the width of multi-column images and textpic elements will be calculated. That’s fine as it provides us with sensible fallback images. However it also leads to a strange behavior for source candidates because the calculation is applied there as well. So, to be able to generate images bigger than the fallback image (e. g. for high dpi screens), we need to overwrite maxW.2

By the way, there is another constant which defines the default layout that should be used:

styles.content.imgtext.layoutKey = srcset  

That should give you a basic output in the frontend. But we aren’t happy yet, are we?

Back to sizes

Now that content images are rendered in different sizes, let’s get back to the sizes issue. First I would advise to limit some features in the backend via page TSconfig, mostly to make things easier:

TCEFORM.tt_content {  
    # Disable fields that specify/change image dimensions
    imagewidth.disabled = 1
    imageheight.disabled = 1
    section_frame.disabled = 1

    # Limit column selection
    imagecols.keepItems = 1, 2, 3, 4
    imagecols.types.textpic.keepItems = 1, 2
}

We …

  • disable manual input of image sizes, as they should be defined in CSS anyway.
  • disable indentation because it further complicates the width calculations.
  • only allow four columns for images and two columns for textpic images.

This leaves us with four cases to cover:

  • full-width images (100%)
  • images in two columns or textpic with one image column (50% 50%)
  • images in three columns (33% 33% 33%)
  • images in four columns (25% 25% 25% 25%) or textpic with two image columns (50% 25% 25%)

Once again we have to modify the layout for this. In addition to srcset we have to include three custom layouts to cover all cases:

# Define different responsive layout for each column layout
# (adjust "sizes" attribute to match image widths)
tt_content.image.20.1.layout {  
    # Default layout for 1-column images
    srcset {
        element.wrap = <img src="###SRC###" srcset="###SRC### ###WIDTH###w,###SOURCECOLLECTION###" sizes="|"###PARAMS######ALTPARAMS######SELFCLOSINGTAGSLASH###>
        element = (min-width: 1020px) 600px, (min-width: 740px) 460px, calc(100vw - 20px)
    }

    # Layout for 2-column images
    srcset2col < .srcset
    srcset2col.element = (min-width: 1020px) 288px, (min-width: 740px) 221px, (min-width: 480px) 50vw, calc(100vw - 20px)

    # Layout for 3-column images
    srcset3col < .srcset
    srcset3col.element = (min-width: 1020px) 186px, (min-width: 740px) 143px, (min-width: 480px) 33vw, calc(100vw - 20px)

    # Layout for 4-column images
    srcset4col < .srcset
    srcset4col.element = (min-width: 1020px) 132px, (min-width: 740px) 221px, (min-width: 480px) 50vw, calc(100vw - 20px)
}

The element property now contains the appropriate sizes media query, which will be wrapped by the template for the <img /> tag. Note that you most definitely have to adjust those media queries to match your media queries and css dimensions used in the frontend! My advise would be to move those media queries to TypoScript constants.

The last step is to tell TYPO3 which layout should be used in which case:

# Clear default layout key
tt_content.image.20.1.layoutKey >  
# Decide which responsive layout should be used in each case
tt_content.image.20.1.layoutKey {  
    cObject = CASE
    cObject {
        # First use content type to differentiate
        key.field = CType

        # Use specific srcset layout for each column layout
        default = CASE
        default {
            key.field = imagecols

            default = TEXT
            default.value = srcset
            2 = TEXT
            2.value = srcset2col
            3 = TEXT
            3.value = srcset3col
            4 = TEXT
            4.value = srcset4col
        }

        # For textpic content, images are already at 50% width, so
        # different layouts will be used
        textpic = CASE
        textpic {
            key.field = imagecols

            default = TEXT
            default.value = srcset2col
            2 = TEXT
            2.value = srcset4col
        }
    }
}

Once you included this configuration in your template, the layout will be set based on the type of the content element CType as well as the image column count imagecols. However, the constant we defined before is still necessary as it affects other parts of css_styled_content.

Bonus: Image Compression

If you are fortunate enough to be using TYPO3 CMS in version ≥ 7.5, there is one more detail that can be optimized: The download size of high dpi images. Since high dpi screens usually have tiny pixels, lossy image compression is less of a problem. This is why it makes sense to reduce the image quality of big image files that will only be used on high dpi screens. We can do that with the new quality parameter:

tt_content.image.20.1.sourceCollection {  
    # For big high-dpi devices (e. g. Mac Retina)
    huge {
        width = 1200
        maxW < .width
        srcsetCandidate = 1200w
        # Works since TYPO3 7.5
        quality = 60
    }
}

Result

Granted, this was a lot of text and code. But that only shows how complex a topic Responsive Images can be. Nevertheless I hope that it is helpful to know the considerations that lead to my solution.

To make things easier for you, here is my final configuration:


  1. Another possibility would be to include the default image in the sourceCollection shown above, but this (unfortunately) generates a different image file which can lead to duplicate downloads. I wouldn’t recommend that.

  2. For the sake of completeness there would be other options, such as pixelDensity, however I prefer the comprehensible maxW solution.