Generating Image Thumbnails in the Browser using JavaScript and FilePond

rikschennink

Rik Schennink

Posted on June 14, 2019

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.

💖 💪 🙅 🚩
rikschennink
Rik Schennink

Posted on June 14, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related