A Blog by Hangindev

Recreating Instagram Filter Functionality with CSS and Canvas APIs

Instagram Filter Clone

Recreating UI components, especially those in native apps, has always led me to interesting unknown web APIs. It also helps me see things from the app developers' perspective and understand why a particular task is done in a particular way. Going forward, I will jot down the making-of of each clone and share the lessons I've learned. I hope you will also learn a thing or two and starting cloning your favorite components.

Today, I want to share my recent clone of the Instagram Filter page which turned out to be much simpler than I expected (if I ignore a browser).

Goal: Users can select a photo from their device, apply filters, see preview, and export(download) the result.

Live Demo

CodeSandbox

The Process

Each Instagram filter is made of a set of basic filter effects, e.g. brightness, contrast, saturate, etc, and some overlays. With the help of CSS filter and mix-blend-mode, stacking filters and overlays to recreate an Instagram filter is pretty much an eyeballing task. Thanks to this brilliant work by Una which did exactly that, 75% of my goal is completed. From there, I only have to figure out a way to export the result out since the CSS is changing the appearance but not the actual image. Luckily, I found there are two Canvas APIs that do very similar things and they are canvas filter and globalCompositeOperation. With them, I can perform the same operation to the image drawn on canvas and use the method toDataURL to export it out. 🍬

Unfortunately, the canvas filter API is experimental and is only supported in Chrome, Firefox, Edge, but not in Safari. So the clone is not fully functioning on the iPhone. augh, safari!

Some Details

You can see the full implementation in the CodeSandbox. Note that the exporting function does not work in CodeSandbox's iframe browser, open the app in a new window instead. Here are some implementation details:

I used an array to store the filter configurations:

// effects.js
const effects = [
{
name: "noraml",
filter: "none",
overlays: []
},
{
name: "clarendon",
filter: "contrast(1.2) saturate(1.35)",
overlays: [
{
backgroundColor: "rgba(127, 187, 227, 0.2)",
mixBlendMode: "overlay"
}
]
},
]

When the "clarendon" filter is turned into HTML and CSS:

<!--image with "Clarendon" filter applied -->
<figure style="filter: contrast(1.2) saturate(1.35);">
<img src="/plitvice-lakes.jpg">
<div style="background-color: rgba(127, 187, 227, 0.2); mix-blend-mode: overlay;"></div>
</figure>

This is how a Instagram filter(i called it effect in the code) is applied on canvas:

function applyEffect(name) {
// find effect by name
const effect = effects.find(eff => eff.name === name);
const { width, height } = previewCanvas;
// clear canvas
ctx.clearRect(0, 0, width, height);
// apply filter
ctx.filter = effect.filter;
// draw the image
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(previewImg, 0, 0, width, height);
// loop through overlays and fill with corresponding color and blend mode
effect.overlays.forEach(overlay => {
ctx.globalCompositeOperation = overlay.mixBlendMode;ctx.fillStyle = overlay.backgroundColor;
ctx.fillRect(0, 0, width, height);
});
}

Lessons Learned

  • Both CSS filter and mix-blend-mode are handy if you want to alter the look of your page without reaching for graphics editors. They can be applied to not only image but every element.
  • Canvas declarative filter API lower the entry barriers to image processing.
  • These APIs all make use of the GPU so they are performant.
  • The CSS properties have wide browser support but not the canvas filter API.
  • For production, use WebGL or a thrid-party library instead.
  • Safari is the new IE.

Afterthoughts

Since I had played with WebGL before, I am well aware these effects can be achieved using WebGL. (Take a look at gl-react if you are a React developer) But this time, I am experimenting with an even simpler solution. And thanks to the declarative APIs (and also CSSgram!), recreating those Instagram effects and the export function is not complicated at all. I am interested in how you are using these CSS properties so please let me know! 😉

Thank you for reading! Until next time! 👋