Building a Perfectly-Sized Dynamic Cover Image with Cloudinary

For those unfamiliar with Cloudinary, it is a cloud service that is focused on image serving, with unique features like transformations, responsive generation, and a speedy CDN for delivery. I have no affiliation with Cloudinary, other than being a fan of their service.

Introduction

One of the big changes in web development over the years has been a shift from “fixed” designs to “responsive”. For example, where we used to define layouts or the display of assets with fixed units, we now try to use percentages or other ratio based approaches to adapt to any display size. This is something to be celebrated, and Cloudinary has an entire feature set built around this, but today I want to talk about when the opposite approach is needed.

We often have “cover images”, “hero images”, and/or images that we need for social media sharing sites, and these frequently have specific desired pixel sizes or aspect ratios. For example:

  • Dev.to cover image: 1000 x 420 (see Editor Guide)
  • Twitter: (depends) 16:9 (1.77:1), 2:1, 1200×628 (1.91:1)
  • Facebook feed: 1.91:1

Finding images that perfectly fit these aspect ratios is often difficult and time-consuming, and manually cropping and resizing existing images to fit, equally so.

What if there was a better way to generate the perfectly sized hero image, with any image as the input?…

Our Goal

So, here is our example goal. We want to produce a 1000 x 420 pixel (2.38:1) cover image for Dev.to, but do so automatically, with a method that accepts any image as the input. If the input aspect ratio does not match the desired output, then the input will be centered, and a blurred background based on the same input shown behind it.

Sample Input:

Sample Input

Sample goal output:

Sample Output

The cover image for this very post was created with the method outlined below! But, you’ll see that later.

Building It With Cloudinary

I’m going to walk through how to build this with Cloudinary image transformations, using URL parameters alone. This makes it very easy to use anywhere, with any backend or frontend, since all we need is a URL.

Base Image: https://res.cloudinary.com/demo/image/upload/sample

When you see Bold and Italic text in the URL, that indicates what was added in the step.

Steps:

  1. Start with the “Base Image”: https://res.cloudinary.com/demo/image/upload/sample
    • Added: sample
  2. Use image to fill exact desired size
    • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420/sample
    • Added: /c_fill,w_1000,h_420/
  3. Since this is actually going to be the background, apply a blur effect and reduce opacity
    • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/sample
    • Added: e_blur:1500,o_75
    • Blur is on a scale from 1 to 2000
    • Opacity is on a scale from 0 to 100 (100 being completely opaque)
  4. Overlay the same image, but limit its size to the “canvas”
    • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420/sample
    • Added: /l_sample,c_limit,w_1000,h_420/
    • Notice how l_{uploadedId} is used to generate an image overlay layer.
  5. Add a nice blurred border to our overlay
    • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100/sample
    • Added: e_outline:outer:2:100
    • You might notice that the border now pushes the overlay outside the canvas
  6. Prevent “overflow” caused by new border
    • Adding a border to the overlay actually made it larger than the boundaries of the background (base image). We have two options to remedy this:
    • Option A: Re-crop: Since adding a border to the overlay made it larger than the boundaries of the background, we can simply re-crop the output
      • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100/c_crop,w_1000,h_420/sample
      • Added: /c_crop,w_1000,h_420/
      • If we wanted to re-crop just the overlay, we could use fl_layer_apply to prevent crop action from applying to the entire chain
    • Option B: Use the no_overflow_flag
      • This is a super helpful flag to remember! From the docs:
        • Prevents Cloudinary from extending the image canvas beyond the original dimensions when overlaying text and other images.

      • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100,fl_no_overflow/sample
      • Added: fl_no_overflow

Final output, using option B:

Extras:

There is still more we can do with this!

  • Add compression
    • To save some bytes, we can tell Cloudinary to serve as a compressed JPG with a quality of 80%
    • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100/c_crop,w_1000,h_420,q_80/sample
    • For our sample image, this gives us a saving of approximately 50%!!!
  • Add overlay text
    • We can add overlay text, such as the title of the post that our cover image is for
    • https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100,fl_no_overflow/l_text:Roboto_100_normal_stroke_:Hero%20Image,co_rgb:FFFFFF,bo_8px_solid_black,g_south,y_30/sample
    • stroke, and border (bo_) are used to provide an outline around the text

Wrap-Up

This might have seemed like a bunch of work, but don’t forget; now that we have the transformation chain built, we can reuse it across an unlimited number of images, simply replacing {uploadedId} with our desired input:

const myCloudId = 'acb123';

// pretend `myImages` is an array of hundreds of image IDs
myImages.forEach(uploadedId => {
    const heroImage = `https://res.cloudinary.com/${myCloudId}/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_${uploadedId},c_limit,w_1000,h_420,e_outline:outer:2:100,fl_no_overflow/${uploadedId}`;
    saveImage(heroImage);
});

In fact, here it is in action, working its magic across any input I give it (large GIF, give a second to load…):

Cloudinary Dynamic Cover Image - Demo With Multiple Inputs

Nice, right!?

We could also…

  • Save this as a Named Transformation
    • Once we do this, reusing it becomes as easy as /image/upload/{transformationName}/{uploadedId}
  • Implement this via Cloudinary API
  • Integrate into any backend (WordPress, Gatsby, etc.) for dynamic meta image tags to serve to Twitter, FB / OpenGraph, etc.
  • … and many more things!

If this was interesting to you, I have a few other Cloudinary related projects that might be worth checking out:


More About Me:

Leave a Reply

Your email address will not be published. Required fields are marked *