Create a custom colour replace component

This post is 4 years old. (Or older!) Code samples may not work, screenshots may be missing and links could be broken. Although some of the content may be relevant please take it with a pinch of salt.

In a recent course that I have recorded for Jamstack.training, I put together an interesting React component which I'd like to share with you in this article. The course in question is Building an E-Commerce Application using Gatsby.

Jamstack.training is a portal dedicated to bringing quality video courses about the Jamstack to anyone wishing to learn about the various technologies.

Gatsby and React

For those who have not heard of Gatsby, it is a React-based static site generator. Static site generators became popular with the Jamstack. In essence, they provide developers with tooling that enables them to create static HTML pages based on a template written by a familiar framework/library. This means that if you're a React developer, you can quickly be up to speed with Gatsby because everything will already look very familiar.

The component that we'll discuss uses hooks and other familiar features from React.

The use-case

Often when browsing e-commerce stores for some piece of clothing to purchase, we regularly come across a t-shirt, for example, that is available in multiple colours. Creating photographs of the same t-shirt with different colours could be a daunting task. What if there'd be an automated way of applying a different colour - or rather - replacing the original colour, with a new one for products in an e-commerce store?

If we are using a service such as Cloudinary to store our images, we can very efficiently utilise transformations - including colour replacement. This particular transformation, offered by Cloudinary, allows us to grab a colour (either by the name or hex value) and replace it with another colour.

Here's an example image that we'll be using throughout this tutorial:

https://res.cloudinary.com/tamas-demo/image/upload/w_300/Jamstack-training/winter-jumper

The original colour of this jumper worn by the model is d79025 - a variation of orange/brown. Using this information, we can easily replace the hexadecimal colour value with red (ff0000) by adding the e_replace_color transformation effect which accepts three parameters: e_replace_color:new_colour_hex:tolerance:old_colour_to_replace:

https://res.cloudinary.com/tamas-demo/image/upload/w_300/e_replace_color:ff0000:10:d79025/Jamstack-training/winter-jumper

From the Cloudinary docs we can learn more about the tolerance: The tolerance threshold (a radius in the LAB colour space) from the input colour, representing the span of colours that should be replaced with a correspondingly adjusted version of the target output colour. Larger values result in replacing more colours within the image. The more saturated the original input colour, the more a change in value will impact the result. (default: 50).

Now that we have established that we have such fantastic functionality available to us, let's put together a React component that will list colour values, and on clicking the colour, will allow us to change the image itself.

Displaying the image

To display the image from Cloudinary, we can rely on their React SDK which exposes an Image component as well as a Transformation component which we will utilise later to add the colour replacement.

Before we can continue, let's install the SDK by executing npm i cloudinary-react.

There are several props that we need to supply, such as the cloudName and the publicId for the original image:

<image
cloudName="tamas-demo"
publicId="Jamstack-training/winter-jumper"
width="300"
crop="scale"
fetchFormat="auto"
quality="auto"
secure="true"
className="h-full w-300 md:mx-8 rounded-lg"
>

</image>

The rest of the props are applying some additional transformations to the image such as scaling it down to 300 pixels and reducing its quality automatically without affecting the human eye (quality="auto") and sending the most appropriate format to the browser (fetchFormat="auto") achieving significant savings in KBs transferred over the wire.

We now have a way to display the image directly from Cloudinary. How can we apply additional transformation to it? The answer to this is the aforementioned Transformation component, that the Cloudinary SDK is also exposing:

// added inside the <image /> component
<Transformation rawTransformation="{e_replace_color:ff0000:10:d79025}" />

Note that rawTransformation allows us to send a transformation directly, but we can also construct transformations using props. The reason behind using the former is because creating the colour replace effect will be easier this way for our use-case.

The colour replace component

Let's also take a look at how to create a separate component that will allow us to list colours and make them selectable so that selecting a colour will also update the image via the Cloudinary Image/Transform component. The code for this component is relatively straight forward:

// colour-component.js
import React from 'react';

function ColourSelect({ changeColour, getColourName, original }) {
const colours = [
{
name: 'yellow',
hex: 'ffff00',
},
{
name: 'indigo',
hex: '4b0082',
},
{
name: 'original',
hex: original,
},
];

function applyColour(e, colour, original) {
e.preventDefault();
changeColour(`e_replace_color:${colour}:10:${original}`);
}

function getColour(e, colour) {
return getColourName(colour.name);
}

return (
<div>
<p>Select a colour</p>
<ul>
{colours.map((colour) => {
return (
<li
key={colour.hex}
value={colour.hex}
onClick={(e) => {
applyColour(e, colour.hex, original);
getColour(e, colour);
}}
>
<span
className="cursor-pointer inline-block px-6 py-2 rounded-full m-1 text-black-800"
style=
>
&nbsp;
</span>{' '}
{colour.name}
</li>
);
})}
</ul>
</div>
);
}

export default ColourSelect;

The above component lists the colours and makes them clickable. Note that the tolerance value has been hardcoded to 10. Feel free to parameterise that or change it to a different value that suits your needs.

Let's take a look at how our newly created component could be implemented:

// App.js
import ColourSelect from './colour-select';

const [colourTransformation, setColour] = useState('');
let [colourName, getColour] = useState('');
let [imgsrc, setImgsrc] = useState('');
const imageRef = useRef(null);

useEffect(() => {
setImgsrc(() => imageRef.current.element.src);
if (imageRef.current && imageRef.current.element) {
const observer = new MutationObserver((muts) => {
muts.forEach((m) => {
if (m.type === 'attributes' && m.attributeName === 'src') {
setImgsrc(() => m.target.src);
}
});
});

observer.observe(imageRef.current.element, {
attributes: true,
});
}
}, [imgsrc]);

// NOTE: we also need to add ref={imageRef} to the Image component

The above code may look complicated at first, but it isn't! The reason why we need to have a reference to the image (well, the Cloudinary Image component really) is that Cloudinary doesn't provide a way to update the src attribute after applied changes. In other words, we need to update the image once we have applied a transformation programmatically and by using the MutationObserver we can do this, by watching for changes happening in the DOM.

The action we take above is to update the src attribute of the Image component if a new colour was selected from the colour-select component.

A working example

If you'd like to see thigs in action, go ahead and give it a try below.

You could add additional colours to the list in the component and test the result with additional colours.

Summary

Creating beautiful e-commerce experiences couldn't be simpler if we are using the right tools for the job. The approach in the Jamstack is to utilise third-party services and APIs to create an enhanced experience. In this article, we had a look at how Cloudinary's colour replace feature can be used in conjunction with React to create a customised, reusable colour selector component.