5 interesting technical challenges I faced when building FilePond

Rik Schennink
ITNEXT
Published in
8 min readJul 3, 2018

--

FilePond is an MIT licensed JavaScript file upload library released in March 2018. It’s available as a native JavaScript plugin but can also be used with Vue, React, Angular, and jQuery using special adapter components.

We’re going to take a look at the FilePond animation engine, the way it renders the drop area, how the image preview plugin leverages the new createImageBitmap API and lastly we’ll look into the limitations of our good old friend the file input.

Animation Engine

I wanted to make a central animation loop drive the entire FilePond interface as it can become very tricky to create high-performance animations with multiple loops while also reading information from the DOM.

The FilePond interface consists of a tree of views, each of these views has a root element and has a read and write function. For every tick of the core animation loop, FilePond calls the read method of the root view, which then calls the read methods of its child views, and so on. Then, when all views have been read, it calls the write method on the root view and this view calls the write methods on its child views, and so on till the end of the view tree is reached. So it basically does a read on the FilePond DOM subtree and then a write on that same subtree.

Chrome Layers inspector reveals FilePond views

The read method, reads layout information from the DOM. This contains data like the current element position, width, height and margins. The retrieved layout information is then stored in each view so it can be used in the write method. Because all reads are grouped the browser only has to do layout calculations once, subsequent requests for layout information are “free”.

The write method is in charge of updating the view. It will apply the layout information and update text elements and apply animations attached to the view. These animations can be tweens or spring based animations.

The core animation loop runs till no more actions are received, views have been updated and all animations have been completed and the interface reaches an idle state. FilePond then waits till the idle state is disturbed by either the user or an API call.

Because the DOM reads and writes have been grouped and we only animate using `transform` and `opacity` there’s no layout trashing (write methods can’t invalidate layout as reads have already finished), the browser only has to do compositing, resulting in very fast performance.

A typical FilePond frame at 6 times CPU slowdown: All logic runs, DOM is updated, browser recalculates styles, and finally handles compositing

Animating the File Drop Area

The FilePond drop area needed to grow vertically with each file dropped. I badly wanted to animate this as all other elements are animated as well and having the drop area size change instantly felt very out of place.

  • I could use scaleY to scale the drop area on the GPU but scaling a rounded rectangle will cause its rounded corners to stretch, which looks weird.
  • Or I could animate the height property but that would be very slow as it can’t be composited on the GPU. Also, it doesn’t do subpixels so as you can see in the animation below when the left square is near max height, the animation becomes less smooth.
Left animated height, Right animated transform scaleY (codepen)

To circumvent this we can use a technique called 9-Slice Scaling

Instead of one div, we use three separate divs for drop area.

  1. A static top div that renders the top-left and top-right rounded corner.
  2. A middle div that is scaled over the y-axis.
  3. A bottom div that is translated and renders the bottom-left and bottom-right corner.

To simulate the height animation we set the middle div height to 1px and use the scaleY() transform to change its height. Setting transform: scaleY(100) will result in a 100px high div. At the same time, we can move the bottom div to a 100 pixel vertical offset using transform: translateY(100px)

This is how it looks in the Chrome web inspector Layers view

By combining both transforms we can create the illusion of animating height, the animation performs well, our corners stay nice and sharp and we get the smoothness of subpixel positioning.

Fake Progress Indicator

On a fast connection, a tiny file can be uploaded to the server in the blink of an eye. This might cause your users to wonder if their files have actually been uploaded as the status almost immediately switches to“upload complete” skipping over the “busy uploading…” state.

To prevent this uncertainty, FilePond will show a fake progress indicator for the first second of each upload. If the upload takes longer the progress of the actual file upload takes over. This might give the user additional confidence that the file has actually been uploaded.

Try for yourself, which version feels better?

Left: with fake progress indicator, Right: without

What’s up with the File Input?

It’s very unfortunate but apart from serious style issues, our beloved file input has another more pressing limitation. It’s impossible to set a file input’s files property. I tried, vigorously.

I fully understand the reason why setting a path to the value attribute is all but secure (you could point it to a file on the user’s file system), but having the option to add a newly created File object to the files list would be very useful.

At the moment files that are drag-n-dropped have to be uploaded asynchronously. As the File objects are contained in the drop event they can’t be stored in the file input. They either have to be kept in memory or have to be uploaded using XMLHttpRequest (or fetch) at once.

The same goes for File objects created in the browser. For instance, files generated with a text editor, or images that have been modified on the client.

If we could set or add files to the files property of the file input.

  • It would be easier to set up form validation, as both dropped files and files selected on the file system via the browse window can be stored in the same list.
  • We would no longer have to modify the server to accept asynchronous uploads. Often we’ll want to know which files have been uploaded so we can reference them in the final form submit.
  • We could easier apply progressive enhancement techniques. The server will only receive a list of files, no matter where they originated from. If a certain CMS exposes a file input in its form module, we could do whatever we want on the front-end, as long as we set the files list.

To circumvent this problem the FilePond file encode plugin can encode files as base64 strings. To do this without stalling animations files are sent to a Worker thread for encoding. When the worker is done encoding, a base64 encoded string is sent back and stored in a hidden input field.

Once uploaded the base64 string can then be turned back into an actual file on the server.

While this creates the opportunity to send files synchronous along with the parent form post, this creates some other problems. One has already been stated, the data needs to be turned back into a file on the server. Another one has to do with memory usage. When submitted, strings take up a lot more memory than file objects. This causes some browsers to bug out when a form with lots of data is being submitted. Another thing to keep in mind is that some server security software will mark the form data as suspicious (cause of the length of the values).

Image Loading

When dropping an image on FilePond I wanted to show a preview, this adds a bit of color to an otherwise monotone experience. As previewing images should be optional I moved this functionality to a separate plugin. The plugin would then render previews of dropped images and adjusts these previews based on the crop information supplied by the image crop plugin.

Problem is, if you want to render a preview of a big JPEG encoded image, it’ll take the browser some time to decode the image and render the resulting Bitmap data.

Decoding time for a 6MB JPEG measured with 6x CPU slow down

While the browser is decoding, the main thread stalls and everything freezes. That’s bad news for any running animation.

To work around this problem, FilePond internally has an option to queue CPU heavy operations. It’ll await running these operations till it’s in idle state.This allows timing of heavy operations and prevents accidental freezing of animations.

I imagined it would still be nice to show some sort of busy state once the browser goes in lockdown. As it turns out, I was in luck, WebKit based browsers can run CSS animations even while the main thread is frozen. Before handling the heavy load queue FilePond will switch to a busy state and the file progress indicator will start its infinite spin animation (unfazed by the main thread that is about to be blocked).

Further speed improvements to the image preview plugin could be made by decoding images on a separate thread. We can do this with the recently added createImageBitmap API (at the time of this writing supported on Chrome and Firefox). The File object is sent to a Web Worker which uses createImageBitmap to decode the file and then returns a BitmapImage object which can be rendered to a <canvas/> using thedrawImage method.

That’s it for now!

Let me know if this was interesting! I’m happy to write some more about other challenges like rendering smooth gradients, making FilePond accessible for screen readers, handling and reading JPEG EXIF orientation and writing the adapter components for React, Vue, and Angular.

If you have any questions, find me on Twitter

--

--

An Indie Product Developer living on this 2D plane people call The Netherlands. Loves experimenting with HTML, CSS and JS, game development as a side dish.