How to Use HTML5 File Drag and Drop

Tweet

Dragging and dropping files from your desktop to a browser is one of the ultimate goals for web application integration. This is the first in a four-part series of posts which describes how to:

  1. enable file dragging and dropping onto a web page element
  2. analyze dropped files in JavaScript
  3. load and parse files on the client
  4. asynchronously upload files to the server using XMLHttpRequest2
  5. show a graphical progress bar while the upload occurs
  6. use progressive enhancement to ensure your file upload form works in any browser (good news for all you IE6 fans!)
  7. code it in plain ol’ JavaScript without a library.

Phew.

Big, Bad Browser Support

Before we begin, this tutorial refers to several cutting-edge HTML5 techniques so expect support to be patchy. The code works today, but it’s possible the APIs will change and browsers will evolve.

  • Recent versions of Firefox and Chrome support all features and work perfectly.
  • Opera can parse files in JavaScript, but file dropping and XMLHttpRequest2 uploading is not implemented.
  • IE and the desktop editions of Safari do not support any of the APIs.
  • Apple has disabled HTML file upload forms on the iPhone and iPad editions of Safari. Anyone know why?

Finally, note that my code shows the fundamental concepts. There’s little error checking and you would need to adapt it for a production system.

The HTML and CSS

Here’s our standard form with a file input type. The only HTML5 feature is the “multiple” attribute which allows the user to select any number of files.

We’ll be uploading files to a server running PHP but the code is much the same no matter what technology you’re using. The hidden MAX_FILE_SIZE value specifies 300,000 bytes — this is used by PHP but we’ll also check it client-side to prevent huge file uploads.


<form id="upload" action="upload.php" method="POST" enctype="multipart/form-data">

<fieldset>
<legend>HTML File Upload</legend>

<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="300000" />

<div>
	<label for="fileselect">Files to upload:</label>
	<input type="file" id="fileselect" name="fileselect[]" multiple="multiple" />
	<div id="filedrag">or drop files here</div>
</div>

<div id="submitbutton">
	<button type="submit">Upload Files</button>
</div>

</fieldset>

</form>

<div id="messages">
<p>Status Messages</p>
</div>

The #filedrag element will be used as our file drag and drop location. The element is hidden in CSS but it will be enabled in JavaScript if drag and drop is supported:


#filedrag
{
	display: none;
	font-weight: bold;
	text-align: center;
	padding: 1em 0;
	margin: 1em 0;
	color: #555;
	border: 2px dashed #555;
	border-radius: 7px;
	cursor: default;
}

#filedrag.hover
{
	color: #f00;
	border-color: #f00;
	border-style: solid;
	box-shadow: inset 0 3px 4px #888;
}

We’ve also defined a .hover class which changes the style when the user has dragged a file on to the element. Browsers don’t apply a :hover style in that situation, but we can add the class with JavaScript when the event occurs.

The File API

The W3C File API provides several objects. We’ll be using:

  • FileList: represents an array of selected files.
  • File: represents an individual file.
  • FileReader: an interface which allows us to read file data on the client and use it within JavaScript.

Attaching JavaScript Events

Time to get our hands dirty with some JavaScript. We’re not using a JavaScript library so, to save our typing fingers, we’ll create a couple of helper functions to return an element by ID and output status messages:


// getElementById
function $id(id) {
	return document.getElementById(id);
}

//
// output information
function Output(msg) {
	var m = $id("messages");
	m.innerHTML = msg + m.innerHTML;
}

We’ll now check if the File API is available and call an Init() function:


// call initialization file
if (window.File && window.FileList && window.FileReader) {
	Init();
}

//
// initialize
function Init() {

	var fileselect = $id("fileselect"),
		filedrag = $id("filedrag"),
		submitbutton = $id("submitbutton");

	// file select
	fileselect.addEventListener("change", FileSelectHandler, false);

	// is XHR2 available?
	var xhr = new XMLHttpRequest();
	if (xhr.upload) {
	
		// file drop
		filedrag.addEventListener("dragover", FileDragHover, false);
		filedrag.addEventListener("dragleave", FileDragHover, false);
		filedrag.addEventListener("drop", FileSelectHandler, false);
		filedrag.style.display = "block";
		
		// remove submit button
		submitbutton.style.display = "none";
	}

}

The Init() function:

  1. Sets a “change” event listener to the file input element.
  2. Displays the #filedrag element.
  3. Sets “dragover” and “dragleave” event listeners to change the style of the #filedrag element.
  4. Sets a “drop” event listener for the #filedrag element.
  5. Hides the form submit button — it’s not required since we’ll be analyzing and uploading files as they’re chosen.

Optionally, you could hide the file input element when file dragging is supported. Personally, I prefer to offer both options since dragging and dropping incurs a number of usability issues.

The XMLHttpRequest.upload method check prevents problems in Opera. The browser supports File, FileList and FileReader, but not drag and drop events or XMLHttpRequest2. It can therefore display file information but we don’t want to show the #filedrag element or remove the submit button.

File Drop Style Change

Few people have experienced file drag and drop in a web browser. In fact, experienced web users may not consider it to be impossible. Therefore, we’ve used an element which states “drop files here”. We also want to indicate when a file has been dragged onto the #filedrag location by changing it’s styling:


// file drag hover
function FileDragHover(e) {
	e.stopPropagation();
	e.preventDefault();
	e.target.className = (e.type == "dragover" ? "hover" : "");
}

Analyzing Dropped or Selected Files

We’re using the same FileSelectHandler() function regardless of whether one or more files was selected using “Browse” or dragged onto the #filedrag location:


// file selection
function FileSelectHandler(e) {

	// cancel event and hover styling
	FileDragHover(e);

	// fetch FileList object
	var files = e.target.files || e.dataTransfer.files;

	// process all File objects
	for (var i = 0, f; f = files[i]; i++) {
		ParseFile(f);
	}

}

The function:

  1. Calls FileDragHover() to remove hover styling and cancel browser events. This is essential otherwise the browser may attempt to display the file.
  2. Fetches a FileList object. This will either be from the file input box (e.target.files) or #filedrag element (e.dataTransfer.files).
  3. Finally, the function loops through all File objects in the FileList and passes it as an argument to the ParseFile() function…

function ParseFile(file) {

	Output(
		"<p>File information: <strong>" + file.name +
		"</strong> type: <strong>" + file.type +
		"</strong> size: <strong>" + file.size +
		"</strong> bytes</p>"
	);
	
}

The function outputs information using the three main read-only properties provided by the File object:

  • .name: the file name (it does not include path information)
  • .type: the MIME type, e.g. image/jpeg, text/plain, etc.
  • .size: the file size in bytes.

Please view the demonstration page in Firefox, Chrome, or Opera (no drag & drop support). You can also download the files to examine the code.

We’ve covered a lot of ground. In my next article, we’ll discover How to Open Dropped Files Using HTML5 and JavaScript

This article has also been translated into Armenian

If you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like HTML5 & CSS3 For the Real World.

Comments on this article are closed. Have a question about HTML5? Why not ask it on our forums?

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://twitter.com/jokeyrhyme Ron

    I understand why Apple made this decision. Oddly enough, a solution that fits is already implemented in Android. In Gingerbread and up (possibly eariler), when you press a button, then the user is given a choice between all the appropriate applications that can select files / images. Usually this will just be the camera and gallery applications.

    Apple is notorious for not allowing third-party interference in various matters, but it would still make perfect sense for the user to be presented with a picker for official Apple applications at least. Most of the time, people will want to submit images from their camera or gallery anyway.

    If this isn’t in iOS 5, then surely it will be in 6. It’s such a no-brainer.

  • http://www.facebook.com/cemerson Chris Emerson

    Great post, but why can’t these things be posted as an article rather than yet another 4 part blog post?

    • http://twitter.com/craigbuckler Craig Buckler

      Primarily because it’s very, very long and introduces a lot of concepts. It’s easier to digest in bite-size chunks.

      In addition, you may require some of the technologies but not others, e.g. server uploading with progress bars, but no JavaScript file analysis or client-side previews.

      • http://www.facebook.com/cemerson Chris Emerson

        I’m pretty sure people here will be capable of splitting out the bits they need though. If someone wanted all of it, they have to wait until all 4 are out, then go back through the blog posts and fish out the previous parts then put it all together. There are plenty of long articles on this site, I don’t see the need to dumb things down by assuming people don’t have a long attention span :/

        • http://twitter.com/craigbuckler Craig Buckler

          Fair enough – we can never please everyone.

          Perhaps I’ll post a summary page or an article with all the content once every section’s been published?

  • http://www.internationalpatentservice.com Patent Attorney

    TaskInsight 3.0 Task Manager Updates Timeline Graphics and Functionality
    Imaja upgrades TaskInsight 3.0 for productivity and business task management with additional graphic timeline settings, background images, many new commands for working with the task list and timeline view, import and export to TSV and XML data files, export of the timeline view to HTML5 web page files, AppleScript scriptability, and support for new features in Mac OS X Lion (10.7): Full Screen, Versions, Automatic Termination and Sudden Termination and Resume.

  • http://www.anthonydillon.com Anthony Dillon

    Great post thanks, I had to add an load function to wait for the page elements to be created before trying to access them.
    to
    and
    if (window.File && window.FileList && window.FileReader) {
    Init();
    }
    to
    function load(){
    if (window.File && window.FileList && window.FileReader) {
    Init();
    }
    }

    Thanks again for the post!

  • http://twitter.com/stevieg_83 Steven Grant

    Is there a way to set the location of the uploaded files?

  • Avn Rocky

    nice post – thanks

  • Mahn Mahn

    Great post, thank you for this. Which element would I attack in order to strip the whitespaces from filenames?

  • http://magazine.nuefolio.com/ Ramakanth Ramaraju

    Very helpful. Thanks.

  • nick

    how can we display uploaded files links?

  • http://www.gilvanritter.blogspot.com Gilvan Ritter

    Thanks! did help-me alot! awesome article, and flexible script.

  • Charles

    Hello Craig, I am really having fun in testing the implementation of this interactivity, thank you very much, I learn a lot!

    I have two simple questions for you:

    1) The display: none style of the Submit button doesn’t allow me to submit the files because the button remains hidden. I changed it from none to inherit. Am I right to do so? Why? (Is there a way that the button appears only if one or more files are ready to be uploaded?)

    2) Once I click on submit button, I get the confirmation that the files were uploaded successfully on the next page (which is upload.php), I am wondering if it is possible to receive the confirmation message in the instead? If it is possible, can you tell what code I should modify or integrate?

    Thank you very much and have a great day!
    Charles

    • Charles

      [...] in div with the class ‘messages’