Generating colour palettes from images in Gatsby

Maciej Baron
Good Praxis
Published in
5 min readJul 30, 2021
An article on Skin Deep’s new website

Good Praxis has recently been tasked with redesigning and rebuilding Skin Deep Magazine’s website. The website was built using GatsbyJS and (headless) WordPress. One of the design choices was to have subtle gradients in the background of the page that are generated from the colour palette of the hero image.

There are several different JavaScript libraries which allow us to generate palettes based on image data, but we decided to go with Vibrant. Our first technical question was whether to generate the gradients in the browser on load, or pre-generate them during the build stage.

Theoretically, doing this in-browser has a couple advantages: it’s easier to setup and it doesn’t slow down the build of the site. There are however several drawbacks of this approach:

  • Gatsby sites pride themselves in near instantaneous page loads. Generating the colours in browser would introduce a visible delay.
  • Since the image has to be loaded first, this increases the delay.
  • In theory we could use the in-lined fall-back image data generated by Gatsby Image, however that may not return accurate results.
  • If the user is one of the few people who disable JavaScript, they won’t see the result at all.

We therefore decided to implement things during the build stage.

Using gatsby-node.js

If you’re familiar with Gatsby, you probably know that we can use gatsby-node.js to procedurally generate pages based on GraphQL data. Normally when you call the createPage function, you pass the path, component (template) and context, which most often contains the ID of the item you want to render in the template:

return gatsbyUtilities.actions.createPage({
path: `articles/${post.slug}`,
component: path.resolve('./src/templates/post.tsx'),
context: {
id: post.id,
},
});

In the above piece of code, we generate an article using the post.tsx template, and pass the post ID within the context object, which is then in turn used in the template component to query the actual post data. This ID is most frequently used in your template’s GraphQL query:

query BlogPostById(
$id: String!
) {
# Select the post by id
post: wpPost(id: { eq: $id }) {

...

However, this data is also passed as a prop called pageContext to your component, meaning we can use it to pass additional data to the component that isn’t necessarily retrieved in the GraphQL query.

const Post = ({ data, pageContext: { id } }) => {
// We can now use the page ID provided in the context
...

Generating colors

Vibrant makes the colour generation a super simple process. We call:

Vibrant.from(IMAGE_PATH).getPalette()

…and this returns a Promise containing the palette data. We can then pass this data in the context object to the template component. Here’s how this would look like in a WordPress based setup:

let palette = {};
if (post.featuredImage?.node?.localFile) {
const imagePath = post.featuredImage.node.localFile.publicURL;
palette = await Vibrant.from(`./public/${imagePath}`)
.getPalette();
}
return gatsbyUtilities.actions.createPage({
path: `articles/${post.slug}`,
component: path.resolve('./src/templates/post.tsx'),
context: {
id: post.id,
palette,
},
});

So far so good. We generate the necessary data if an image exists, and then pass it on to the template.

Using Vibrant data

Let’s have a quick look at what sort of data we receive from Vibrant and how we can use it. It typically looks something like this:

{
Vibrant: {
rgb: [200, 10, 30]
},
LightVibrant: {
rgb: [230, 100, 50]
},
Muted: {
rgb: [159, 159, 170]
}
...
}

Not every image will result in a LightVibrant color, or a DarkMuted colour etc., therefore we need to create a fall-back chain with a fall-back colour:

const DEFAULT_COLOR = [50, 50, 50];
const { Vibrant: vibrant, LightVibrant: lightVibrant } = palette;
const topColor = vibrant || lightVibrant || { rgb: DEFAULT_COLOR };

We then just need a little helper function to create the CSS gradient:

const generateGradient = ({ rgb }) => `linear-gradient(
to bottom, rgba(${rgb.join(',')},.3), #fff)`;

…and we’re all set! We can now use the data, and the helper function to set an inline style for our container:

<Layout style={{ backgroundImage: generateGradient(topColor) }}>
...
The yellow from the featured image gets pulled out and used as part of the gradient
Both red and yellow dominate in the picture, but in this case the red is more prominent

The build process now takes about a minute or two longer, however considering it is processing hundreds of articles, that’s pretty fast.

Additional considerations

If you’re also thinking about using a gradient as a background, remember that the size of the background image is by default the same as the size of the container. This means that, for instance, if you have a long article, the background will stretch across the whole text area.

A good solution for this is to limit the size to the height of the viewport, and set it so it does not repeat itself vertically:

background-size: 100vh;
background-repeat-y: no-repeat;

If you want, you can fix the position of the background so that the gradient is always visible:

background-attachment: fixed;

If you’re using styled components, you might consider integrating the colour that way. It might seem like a cleaner solution, however your CSS might grow considerably if a lot of different gradients are generated.

Conclusion

Gatsby is a powerful framework that allows to build websites based on many different sources of data. It allows us to build sophisticated build pipelines, aggregate data, automate tasks and take on challenges that might not seem feasible to do in browser.

Using a decoupled approach in development gives us a lot of flexibility and prevents us from being limited by specific platforms. It also allows us to be a lot more creative and push the boundaries of what’s possible.

--

--