Generating Image Thumbnails in the Browser using JavaScript and FilePond
Rik Schennink
Posted on June 14, 2019
FilePond is a free JavaScript File Upload Library. In this article we'll explore the functionality of FilePond and how it can be extended with plugins. We'll combine a handful of these plugins to generate image thumbnails on the client.
If you want to code along, open an empty HTML file in your favourite text editor.
Let's get started.
In a hurry? View the end result here
Setting up FilePond
We'll start with a basic HTML outline and add an <input type="file"/>
.
Please note that you can also import
and use FilePond as an ES6 module, but for this tutorial we'll stick to plain HTML as it requires less project setup
<!doctype html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<input type="file">
</body>
</html>
Let's add the required FilePond scripts and styles.
<!doctype html>
<html>
<head>
<title>Hello World</title>
<!-- FilePond styles -->
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
</head>
<body>
<input type="file">
<!-- FilePond scripts -->
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
</body>
</html>
And now we extend it with the FilePond initialisation logic.
<!doctype html>
<html>
<head>
<title>Hello World</title>
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
</head>
<body>
<input type="file">
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<!-- FilePond initialisation logic -->
<script>
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement);
</script>
</body>
</html>
This will tell FilePond to create
a FilePond instance at the location of our file input.
If you run this in your browser the FilePond drop area will appear. It can handle a single file. We can add the multiple
attribute to the input
element to allow multiple files to be added.
<!doctype html>
<html>
<head>
<title>Hello World</title>
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
</head>
<body>
<!-- Add 'multiple' attribute -->
<input type="file" multiple>
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<script>
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement);
</script>
</body>
</html>
Okay, this is nice. Let's add some plugins.
Adding Plugins
Let's add the Image Preview, Image Resize, and Image Transform plugins.
The Image Preview plugin will show a preview of a dropped image. The Image Resize plugin will add resize information to the FileItem metadata, and lastly the Image Transform plugin, it will use the resize information to resize the actual image.
<!doctype html>
<html>
<head>
<title>Hello World</title>
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<!-- Add plugin styles -->
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">
</head>
<body>
<input type="file" multiple>
<!-- Add plugin scripts -->
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-resize/dist/filepond-plugin-image-resize.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-transform/dist/filepond-plugin-image-transform.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<script>
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement);
</script>
</body>
</html>
Refresh the page, nothing changed. Weird. For FilePond to use the plugins we need to register them with the library, this is not done automatically.
Let's extend our bottom initialisation <script>
like shown below.
// register the plugins with FilePond
FilePond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageResize,
FilePondPluginImageTransform
);
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement);
To see this working, drop an image on the FilePond drop area, it now shows a preview of the image.
Showing a Resized Preview
It's now time to tell FilePond of our intentions. We can do this by passing a configuration object to the FilePond.create
method.
const pond = FilePond.create(inputElement, {
imageResizeTargetWidth: 256
});
This will instruct the resize plugin to add a resize
entry to the FileItem metadata. We can view this metadata by adding the onaddfile
callback.
const pond = FilePond.create(inputElement, {
imageResizeTargetWidth: 256,
// add onaddfile callback
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
}
});
The following will be logged to the developer console.
null, { mode: "cover", upscale: true, size: { width: 256, height: 256 } }
null
means that there was no error when adding the file, and the rest of the data is related to the resize
information added by the Image Resize plugin.
Let's now show the output of the Image Transform plugin. We can do this by adding the onpreparefile
callback, it's called when the Image Transform plugin has "prepared" a file. It receives both the fileItem
and the output
Blob object of the Image Transform process.
const pond = FilePond.create(inputElement, {
imageResizeTargetWidth: 256,
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
},
// add onpreparefile callback
onpreparefile: (fileItem, output) => {
// create a new image object
const img = new Image();
// set the image source to the output of the Image Transform plugin
img.src = URL.createObjectURL(output);
// add it to the DOM so we can see the result
document.body.appendChild(img);
}
});
The resized image now appears on the page below the FilePond drop area.
It should be 256 pixels wide, and depending on the aspect ratio of the image its height might exceed 256 pixels. That's because imageResizeMode
is set to 'cover'
, setting it to 'contain'
will make sure that the output image is always contained inside the resize target dimensions.
Let's set imageResizeMode
to 'contain'
now.
const pond = FilePond.create(inputElement, {
imageResizeTargetWidth: 256,
// set contain resize mode
imageResizeMode: 'contain',
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
},
onpreparefile: (fileItem, output) => {
const img = new Image();
img.src = URL.createObjectURL(output);
document.body.appendChild(img);
}
});
Alright, we've accomplished generating a single thumbnail, now let's generate multiple.
Generating Multiple Thumbnails
The Image Transform plugin has a couple configuration values of its own.
By setting the imageTransformOutputQuality
property we can control the image output quality and we can convert images to JPEGs by setting the imageTransformOutputMimeType
to 'image/jpeg'
The property we need now is imageTransformVariants
, it's there to create additional versions of a file. We'll generate two additional versions of the image, one 512 pixels wide, and one 64 pixels wide.
const pond = FilePond.create(inputElement, {
imageResizeTargetWidth: 256,
imageResizeMode: 'contain',
// add imageTransformVariant settings
imageTransformVariants: {
thumb_medium_: transforms => {
transforms.resize.size.width = 512;
return transforms;
},
thumb_small_: transforms => {
transforms.resize.size.width = 64;
return transforms;
}
},
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
},
onpreparefile: (fileItem, output) => {
const img = new Image();
img.src = URL.createObjectURL(output);
document.body.appendChild(img);
}
});
Nuts! Our script throws an error.
Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.
It's telling us that whatever we're trying to create a URL to, it's not working.
This is because we're now generating multiple files. Our output
parameter in the onpreparefile
callback has changed to an array. Let's alter the code so it can deal with a list of files.
const pond = FilePond.create(inputElement, {
imageResizeTargetWidth: 256,
imageResizeMode: 'contain',
imageTransformVariants: {
thumb_medium_: transforms => {
transforms.resize.size.width = 512;
return transforms;
},
thumb_small_: transforms => {
transforms.resize.size.width = 64;
return transforms;
}
},
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
},
// alter the output property
onpreparefile: (fileItem, outputFiles) => {
// loop over the outputFiles array
outputFiles.forEach(output => {
const img = new Image();
// output now is an object containing a `name` and a `file` property, we only need the `file`
img.src = URL.createObjectURL(output.file);
document.body.appendChild(img);
})
}
});
Dropping a file now result in three images being added to the DOM, all matching the widths provided.
We can take this further by adding the Image Crop plugin, we can then tell FilePond to automatically crop the output images in certain aspect ratios.
Let's do this quickly and then call it a day.
<!doctype html>
<html>
<head>
<title>Hello World</title>
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<!-- Add plugin styles -->
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">
</head>
<body>
<input type="file" multiple>
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-resize/dist/filepond-plugin-image-resize.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-transform/dist/filepond-plugin-image-transform.js"></script>
<!-- add the Image Crop plugin script -->
<script src="https://unpkg.com/filepond-plugin-image-crop/dist/filepond-plugin-image-crop.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<script>
FilePond.registerPlugin(
// register the Image Crop plugin with FilePond
FilePondPluginImageCrop,
FilePondPluginImagePreview,
FilePondPluginImageResize,
FilePondPluginImageTransform
);
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement, {
// add the Image Crop default aspect ratio
imageCropAspectRatio: 1,
imageResizeTargetWidth: 256,
imageResizeMode: 'contain',
imageTransformVariants: {
thumb_medium_: transforms => {
transforms.resize.size.width = 512;
// this will be a landscape crop
transforms.crop.aspectRatio = .5;
return transforms;
},
thumb_small_: transforms => {
transforms.resize.size.width = 64;
return transforms;
}
},
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
},
onpreparefile: (fileItem, outputFiles) => {
outputFiles.forEach(output => {
const img = new Image();
img.src = URL.createObjectURL(output.file);
document.body.appendChild(img);
})
}
});
</script>
</body>
</html>
You can view a live demo below. Drop an image file and you'll see three different output files.
- A big square measuring 256 x 256 pixels.
- A landscape rectangle with a width of 512 pixels and a height of 256 pixels.
- A tiny 64x64 pixel square.
Images are resized on a separate thread so the image generation process is fairly quick and doesn't block the user interface.
We could now use the Image Filter plugin to also generate a grayscale version of the image by applying a CoorMatrix to each pixel, but I think we've got our bases covered for now. We've learned how to generate thumbnails on the client and how to leverage FilePond to do so.
I hope this has been useful to you, do let me know if you have any questions, I'm happy to answer them below.
Posted on June 14, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.