DEV Community

Nam Phแบกm
Nam Phแบกm

Posted on • Updated on

Welcome to Vintagram

Ever wonder if you can build such an offline alternative to the gram from the meta to use the vintage filters you love while avoid all the metal health issues? Wonder no more, 'cause today, in this article, I'll show you how to build an offline one that should be called Vintagram ๐Ÿ˜€ Have fun using it then ๐Ÿ˜†

The Glitch, the Zuck and the Offline

I uploaded this one to glitch, so you can both use it online at and offline just by downloading files or copying the source codes I pasted here while avoid the Zuck completely, is it cool? ๐Ÿ˜€

Download the files for offline use

In order to use vintagram offline, you need to download the files at

and run a local webserver to serve them.

Some little intro

I write code mainly just for fun and I think it's necessary to have fun in life, 'cause it's too short ๐Ÿ˜“. My main interest now is to find some cool libraries, especially javascript and thinking how to have fun using them ๐Ÿ˜€. There are actually tons of libraries to have fun with and I hope I have time in the future to do so :D Vintagram is an idea I have when I found many cool effect libraries and there are not many people willing to turn them into something fun to use. So I decide to build one ๐Ÿ˜Š, and I'll show you how to do it too ๐Ÿ˜€

Technique details

I list the technique details here so you can easily follow the article

Technique Solution / Library used Note
Building a simple and clean interface The pico.css library pico.css provides classless version to build elegant interface
Font face for headers The Grand Hotel font Suitable for vintage theme
Drag drop files The file drag drop api Better UX
Filter processing The WebGL Image Filter library A cool library that provides the core functionality
Download image button The Download.js library For quick implementation of the download button
Copy Paste skill For better coding ๐Ÿ˜€

Understand how it works

Interface structure

The interface structure is quite clear. You can view the source code to see the html code and some css then. The pico.css and font can be served by the cdn. Just copying the code they give you and that is done.

App structure and flow

Vintagram is designed that at first, you load a photo to the page via drag drop (desktop / laptop usage) or the upload photo button (smartphones, ...). The photo then get displayed and clicking on the filter image will apply that effect. The download button is for saving image or use can use save image feature from the browser.

Dragdroping file

This is straightfoward as you'll just have to implement the listeners for the dragover and drop events. For quick building, I listen on the whole viewport, that is, the html element. The handleDrop function handles the droping while handleDragOver funtion is just for preventing the default action from the browser. Look at the code

function handleDrop(ev) {

    if (ev.dataTransfer.files && ev.dataTransfer.files.length == 1) {
        let [f] = ev.dataTransfer.files;

Enter fullscreen mode Exit fullscreen mode

So to extract the dropped file, you query the ev.dataTransfer.files property. The length is just for ensuring that user only drop 1 image at a time. Also remember to prevent the default action as well. After that the loadFile function will handle the file.

You can infer the code for the upload photo button

function handleFileChange(ev) {

    if ( && == 1) {
        let [f] =;

Enter fullscreen mode Exit fullscreen mode

to handle this situation. This time, to get the file, you query the property rather than ev.dataTransfer.files above and the event you need to listen to here is change event from the hidden file input of the page. Again, the loadFile function is called to do the job.

Loading the image

In order to load and display the image, I maintain 2 image variable: the display variable for displaying result and the inner variable for handling loading behind the scene. Also, inner is for checking if the given file is a valid image (also for keeping the original version of the image). That's why I listen to the onload and onerror event from inner. There are also two url variable, current and received to maintain the current url and the one being received.

If a valid image is loaded, discard the current url and replace it with the received one. If an invalid image is loaded then discard the received one. In the case of a valid image, we also display it by passing the url to the display image and from the usage of the webgl-image-filter, we have to create a new filter variable (this requires a canvas with the width and height of the image) and hence the code

inner.onload = function () {
    let canvas = document.createElement('canvas');

    canvas.width = inner.naturalWidth;
    canvas.height = inner.naturalHeight;

    filter = new WebGLImageFilter({ canvas });
    display.src = this.src;

    if (current)

    current = received;
    received = null;
Enter fullscreen mode Exit fullscreen mode

We are good to go with loading the image then.

Using WebGLImageFilter

The main point of the webgl-image-filter library is to create a filter, which we did in the onload listener, then call addFilter function for each filter you want to add. There is also the reset function in case you want to return to the beginning (will clear all the added filters) and finally, using apply to apply the filter to the image. The result will be drawn to the canvas given to the filter in the listener above. After that, you can use the data from the canvas. Here is the example of the filter variable usage


filter.addFilter('hue', 15);
filter.addFilter('saturation', 0.2);
filter.addFilter('brightness', 0.2);
filter.addFilter('contrast', 0.2);

filter.apply(theImage).toDataURL(); // get the data url from the drawn canvas
Enter fullscreen mode Exit fullscreen mode

I find this library kinda cool because the author provided some useful filters that you can use immediately. So rather than knowing how to use hue, saturation, brightness, contrast,... properly, you can use instantly use the polaroid effect with


filter.addFilter('polaroid'); // cool!!!

filter.apply(theImage).toDataURL(); // get the data url from the drawn canvas
Enter fullscreen mode Exit fullscreen mode

This article is mean to show you how to build an simple app like vintagram so I won't dig deeper into how to build filters, but rather focus on using the library and the presets. You will have to find more about filters in another articles or try building one.

Applying the filter

The idea here is simple: using a preset and transfer the data url to the display image. I build some filter with associated name in the presets variable so you can use them easily by calling the applyPreset function which will find and apply the filter as following code shown

function applyPreset(name) {
    if (!filter || !(name in presets))

    display.src = filter.apply(inner).toDataURL();
Enter fullscreen mode Exit fullscreen mode

Download the image

This is quite simple: use the download function provided by the download.js library through the downloadImage wrapper

function downloadImage() {
    if (!filter)

    download(display.src, "vintagram.png");
Enter fullscreen mode Exit fullscreen mode

Well, and that's vintagram. Hope you enjoy the app, the article and learn something new. Have a nice day then ๐Ÿ˜Š

Top comments (2)

jonrandy profile image
Jon Randy ๐ŸŽ–๏ธ • Edited

And here's me thinking this would be an Instagram clone for wine lovers ๐Ÿท

govindkumwat profile image

Nice concept but downloaded image format is not working. Take a look on this issue.