DEV Community

Cover image for Generating Image Thumbnails in the Browser using JavaScript and FilePond
Rik Schennink for PQINA

Posted on

Generating Image Thumbnails in the Browser using JavaScript and FilePond

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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'));
  }

});
Enter fullscreen mode Exit fullscreen mode

The following will be logged to the developer console.

null, { mode: "cover", upscale: true, size: { width: 256, height: 256 } }
Enter fullscreen mode Exit fullscreen mode

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);
  }

});
Enter fullscreen mode Exit fullscreen mode

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);
  }

});
Enter fullscreen mode Exit fullscreen mode

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);
  }
});
Enter fullscreen mode Exit fullscreen mode

Nuts! Our script throws an error.

Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.
Enter fullscreen mode Exit fullscreen mode

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);
    })
  }

});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

Oldest comments (8)

Collapse
 
zymawy profile image
Ironside

Thank You For Such Article!! 💯

Collapse
 
dkohno profile image
DKohno

Rik, nice work..well done.

I notice that metadata tags are also supported, but I can't seem to find a live example. Is there an example available? My goal is to use FilePond, for uploading and tagging files to a document library.

Many thanks...Dave

Collapse
 
rikschennink profile image
Rik Schennink

Hi Dave! File metadata is sent along with the post and is available on the server under the same name as the file input field. So, for example, in PHP that would be something like this:

$_POST['field_name'] // file metadata
$_FILES['field_name'] // file object
Collapse
 
msoutopico profile image
Manuel Souto Pico

Thanks for this tutorial. Not really interested in uploading images, but it's useful for uploading files in general. This all works fine on my page, my problem is that I don't see where the uploaded files are stored in the server. Can you help with that?

Collapse
 
rikschennink profile image
Rik Schennink

The FilePond server property defines where the files are uploaded.

If you setup a server end point at the same location it’ll push the data to that server end point as multiparty FormData

Collapse
 
msoutopico profile image
Manuel Souto Pico

THanks Rik. Yes, I'm using setOptions({ server: './api' }) and the <file-pond/> component has attribute server="./api". I undertand that points to the "api" folder at the root of my vue application. That should be fine, so the error must be somewhere else. I'll keep investigating, thanks!

Collapse
 
alignsoft profile image
Stephen R. Smith

This looks absolutely fantastic! I've spent the last few hours trying to find some insight into how to use this with PreSigned URLs and S3, and while I have found other people looking for the same thing, I can't find any solutions.

It sounds like it should be possible, but I can't see a clear place to start.

Right now I have a set or array of files I iterate over, sending each to a Node backend to get a PreSigned S3 URL, then I POST the file to that URL directly from the front end. I don't see any way to hook into FilePond to leverage that mechanism.

Collapse
 
rikschennink profile image
Rik Schennink

Glad to hear that! You can probably use this implementation as a starting point: github.com/pqina/filepond/issues/58