Downloading Text Using Blobs, URL.createObjectURL(), And The Anchor Download Attribute In JavaScript
About a month ago, I discovered that you could use Plain Text in a Data URI when programmatically prompting a user to download content. Prior to that, I had assumed that all Data URIs had to be Base64-encoded. In response to that post, several of my co-workers (Adam DiCarlo and Dave Johnson) warned me that text-based URIs were constrained by size limitations. And, that I should look into using a Blob URI instead. Now, I have used Blobs in the past to render image previews (thank you Jonathan Rowny); but, I've never created a Blob myself. As such, I wanted to take my previous text-download demo and revamp it to use Blobs and the URL.createObjectURL() method.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
In the previous demo, I was setting an anchor tag's "href" attribute to point to a Data URI in the form of:
href="**data:**text/plain;charset=utf-8,........."
In this demo, we're going to be using a similar approach. Except, instead of a Data URI, we're going to point the "href" attribute to an in-memory object reference. This reference will be generated by the URL.createObjectURL() method. This method takes a Blob (or Blob-like object) and returns a String that can be used in any place that a URL might be used (such as an HREF attribute).
When you create a Blob URI, the browser holds onto the referenced memory until the document is unloaded; or, until you explicitly revoke the URL. Depending on what your application is doing, this may or may not be a concern. But, if you're generating a lot of these URLs, you can control the memory consumption more granularly by removing old Blob URIs with the URL.revokeObjectURL(blogURI) method.
Of course, in order to use the URL.createObjectURL() method, we need to have a Blob. Luckily, creating a Blob from a plain-text value is as simple as calling the Blob constructor and passing in the text value:
var blob = new Blob(
[ plainTextValue ],
{
type : "text/plain;charset=utf-8"
}
);
This returns a Blob instance, which we can then pass to URL.createObjectURL(). To see this in action, I've revamped my previous demo to convert the contents of a Textarea element into a Blob URI which is then made downloadable through the use of the "download" attribute on an anchor tag:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
Downloading Text Using Blobs, URL.createObjectURL(), And The Anchor Download Attribute In JavaScript
</title>
<link rel="stylesheet" type="text/css" href="./demo.css" />
</head>
<body>
<h1>
Downloading Text Using Blobs, URL.createObjectURL(), And The Anchor Download Attribute In JavaScript
</h1>
<form>
<textarea name="input">Snakes. Why'd it have to be snakes?</textarea>
<!-- NOTE: Download attribute not supported in IE (but is in Edge). -->
<a href="javascript:void(0)" download="data.txt">
Download Text
</a>
</form>
<script type="text/javascript">
// Gather our DOM references.
var input = document.querySelector( "textarea[ name = 'input' ]" );
var download = document.querySelector( "a[ download ]" );
// In order to facilitate the download, we're going to allocate Object URLs.
// We'll need to keep track of those so we can manage the browser memory.
var downloadUrl = null;
// Listen for relevant form changes so that we can dynamically update the HREF
// attribute of our download link to contain the proper Object URL.
input.addEventListener( "input", updateDownloadHref, false );
// Initialize the download link.
updateDownloadHref();
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I update the HREF of the download link to point to the textarea payload.
function updateDownloadHref() {
// Create a binary representation of the plain-text input.
var blob = new Blob(
[ input.value ], // Blob parts.
{
type : "text/plain;charset=utf-8"
}
);
// When we create Object URLs, the browser will keep them in memory until the
// document is unloaded or until the URL is explicitly released. Since we are
// going to create a new URL every time the user hits a key-stroke (in this
// particular demo), we need to be sure to release the previous Object URL
// before we create the new one.
if ( downloadUrl ) {
URL.revokeObjectURL( downloadUrl );
}
// Create an addressable version of the blob.
// --
// CAUTION: At this point, the URL has been allocated and the blob will be
// kept in the document memory space until the document is unloaded or the
// URL is explicitly released (see above).
downloadUrl = URL.createObjectURL( blob );
// Tie the addressable version of the blob to the download link.
download.setAttribute( "href", downloadUrl );
console.group( "Object URL" );
console.log( "Text:", input.value );
console.log( "URL:", downloadUrl );
console.groupEnd();
}
</script>
</body>
</html>
For the sake of this demo, I'm creating a new Blob URI after every input event. That Blob URI is then attached to the "download" link. Which, when clicked, will start downloading the textarea content:
In the past, this is the kind of workflow that I would need some sort of server-side component in order to fulfill. But now, the client-side functionality is getting so robust that more and more processing can be offloaded to the browser. What an exciting time to be alive!
Want to use code from this post? Check out the license.
Reader Comments
Hi, do you think you can also provide a tutorial on how a normal download link can be converted into a Blob Download Link? much like how Mega is doing it? i'd like to do this on my download link because, i'd like the URI to be protected and hidden from users while at the same time having them able to download it. thanks
@K,
I am not exactly sure what you mean (not sure what "Mega" is). Are you saying that when the user clicks on a link to a remote page or resource (like a file), you want the browser to prompt them to download it?
Like a "Click here to start your download" kind of link? Or where you get to a page, and then suddenly a download starts?
Hi, Would love a follow-up to this post.
In this above examples, the blobs are all in memory aren't they? So suppose if you are downloading a 500mb - 1gb file from a url, you may run in to memory limit issues on the client side.
In this case how would you stream the file from a remote url to the browser? The download attribute in html5 is kinda broken, not well supported and doesn't allow different origin downloads.
services like dropbox, and others allow the user to stream the file and you can see this by viewing the download progress in the browser. I am not sure how they do this?
P.S Mega i guess he was referring to the once popular file sharing site called megaupload