preface

In front site download file, this is a very common demand, long ago already has a variety of solutions, why so old articles, only recently in with a new person, he seems to be a lot of have a little knowledge, also encountered problems we must pass through “cannot download TXT, PNG file” typical problem, I give him to summarize several ways to download. By the way, maybe someone really needs it.

Form submission

This was the traditional approach used in the old days, when there weren’t so many new features to use.

Add a click event for a download button, dynamically generate a form when clicked, and use the form submission function to download the file (in fact, the form submission is to send a request).

Here’s how to generate a form and what it looks like:

/** * download file * @param {String} path - requested address * @param {String} fileName - fileName */
function downloadFile (downloadUrl, fileName) {
    // Create form
    const formObj = document.createElement('form');
    formObj.action = downloadUrl;
    formObj.method = 'get';
    formObj.style.display = 'none';
    // create input
    const formItem = document.createElement('input');
    formItem.value = fileName; // Pass the value of the parameter
    formItem.name = 'fileName'; // The field name of the parameter to be passed
    // Insert it into the page
    formObj.appendChild(formItem);
    document.body.appendChild(formObj);
    formObj.submit(); // Send the request
    document.body.removeChild(formObj); // Clear it after sending
}
Copy the code

advantages

  • Traditional method, good compatibility, will not appear URL length limit problem

disadvantages

  • There is no way to know the download progress
  • Unable to download files that can be previewed directly by the browser (TXT/PNG, etc.)

The open, or the location. The href

The simplest and most direct way is actually the same as a tag to access the download link

window.open('downloadFile.zip');

location.href = 'downloadFile.zip';
Copy the code

Of course, the address can also be the address of the interface API, not just a link address.

advantages

  • Simple, convenient and direct

disadvantages

  • URL length limits occur
  • Pay attention to URL encoding
  • The types of files that can be directly viewed by the browser are not available for download, such as TXT, PNG, JPG, and GIF
  • If you cannot add headers, authentication cannot be performed
  • There is no way to know the download progress

A Download of the tag

As we know, the A tag can access the address of the downloaded file, and the browser helps with the download. But for the browser support direct browsing TXT, PNG, JPG, GIF and other files, it does not provide direct download (can right click from the menu to save as).

To solve this problem of browsing without downloading, you can use the Download attribute.

Download attribute is a new attribute in HTML5, can I use Download

The overall compatibility is very good, basically can be divided into IE and other browsing. But some information to note:

  • Edge 13 crashes when trying to download a data URL link.
  • Chrome 65 and later only supports same-origin download links.
  • Firefox only supports same-origin download links.

Based on the above description, if you try to download a cross-domain link, the effect of Download will actually be lost, as if you did not set Download. What the browser previews is still previewed, not downloaded.

Simple usage:

<a href="example.jpg"</a>Copy the code

You can add property values to specify the file name of the download, that is, rename the downloaded file. If this parameter is not specified, the original file name is used by default.

<a href="example.jpg" download="test"> Click download </a>Copy the code

As above, an image named Test has been downloaded

Monitor whether Download is supported

To find out whether the browser supports the Download attribute, a simple line of code can tell the difference

const isSupport = 'download' in document.createElement('a');
Copy the code

For files that can not be downloaded and browseable under the cross-domain, in fact, you can negotiate with the back end, do one more layer of forwarding at the back end, and finally return the file link to the front end with the same domain as the download page.

advantages

  • Can solve the problem of directly downloading browserable files

disadvantages

  • You need to know where to download the file
  • Cannot download files that can be browsed by browsers across domains
  • Compatibility issues, especially IE
  • Authentication cannot be performed

Use Blob objects

The advantage of this method over the previous method of using a tag Download directly is that in addition to using the known file address path for download, it can also send ajax request API to get the file stream for download. After all, sometimes the backend will not provide you with a download address to access directly, but will call the API.

Blob objects are used to stream files into Blob binary objects. This object is compatible, but it needs to be noted

  • Internet Explorer 10 or later is not supported.
  • Access it in SafariBlob UrlorObject URLThe current is defective, as passed belowURL.createObjectURLGenerated links.caniuseThe website points out that

Safari has a serious issue with blobs that are of the type application/octet-stream

CreateObjectUrl to generate the URL address, assign the value to the href attribute of a tag, and download the binary data.

/** * download file * @param {String} path - Download address/download request address. * @param {String} name - The name/rename of the downloaded file (preferably with a suffix for compatibility reasons) */
downloadFile (path, name) {
    const xhr = new XMLHttpRequest();
    xhr.open('get', path);
    xhr.responseType = 'blob';
    xhr.send();
    xhr.onload = function () {
        if (this.status === 200 || this.status === 304) {
            // If Internet Explorer 10 or later does not support the Download attribute, use the msSaveOrOpenBlob method. However, msSaveOrOpenBlob is also not supported in Internet Explorer 10 or later
            if ('msSaveOrOpenBlob' in navigator) {
                navigator.msSaveOrOpenBlob(this.response, name);
                return;
            }
            // const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
            // const url = URL.createObjectURL(blob);
            const url = URL.createObjectURL(this.response);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = name;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a); URL.revokeObjectURL(url); }}; }Copy the code

This method cannot be without setting the download attribute of the A tag. Since the return data type is set to Blob (xhr.responseType = ‘Blob’) when the request is sent, target.response is a Blob object with two properties size and type printed out. Although the type attribute already specifies the type of the file, it is safe to specify the suffix in the Download attribute value. If Firefox does not specify the type of the downloaded file, it will not recognize the type.

You may notice that there are two comments in the code above, but there is another way to write it. If the request is sent without setting xhr.responseType = ‘blob’, the default Ajax request returns DOMString data, which is a string. This is where the code for the two comments is needed, converting the returned text into a Blob object and creating a Blob URL, which comments out the original const URL = url.createObjecturl (target.response).

advantages

  • Can solve the problem of directly downloading browserable files
  • You can set header to add authentication information

disadvantages

  • Internet Explorer 10 is not available due to compatibility problems. Safari can keep an eye on usage

Using base64

The only difference is that the Blob object is used to generate Blob urls, while the Data URL is generated in base64 encoded URL form.

/** * download file * @param {String} path - Download address/download request address. * @param {String} name - The name of the downloaded file (preferably with a suffix for compatibility reasons) */
downloadFile (path, name) {
    const xhr = new XMLHttpRequest();
    xhr.open('get', path);
    xhr.responseType = 'blob';
    xhr.send();
    xhr.onload = function () {
        if (this.status === 200 || this.status === 304) {
            const fileReader = new FileReader();
            fileReader.readAsDataURL(this.response);
            fileReader.onload = function () {
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = this.result;
                a.download = name;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a); }; }}; }Copy the code

advantages

  • Can solve the problem of directly downloading browserable files
  • You can set header to add authentication information

disadvantages

  • Internet Explorer 10 is unavailable due to compatibility issues

About File name

Sometimes we don’t know the file name before we send the download request, or the file name is provided by the back end and we have to find a way to get it.

Content-Disposition

When we return the file stream, we look at the message returned by the interface in the browser and we see a header: Content-disposition

Content-Disposition: attachment; filename=CMCoWork__________20200323151823_190342.xlsx; filename*=UTF-8' 'CMCoWork_%E4
Copy the code

The values above are examples.

It contains the file name, and we can try to get the file name. We can see that filename= and filename*=, the latter may not have the form, which is not supported in older browsers or in some browsers, filename* uses the encoding specified in RFC 5987.

So if you want to get the file name, you’re just going to intercept the values of these two fields in this string.

If you look at the example above, you might see how it’s weird. Yes, if the name is In English, that’s fine, but if it has Chinese or other special symbols, it needs to be handled well

  • filename, requires the backend to handle the encoding form, but even if it does, it will parse differently for each browser. It’s a tough guy to handle. That’s why there’s a backfilename*
  • filename*, is a modern browser support, to solvefilenameIs generallyUTF-8And we usedecodeURIComponentWe can decode it, restore it to its original form. Of course, you have to put the values in before decodingUTF-8''Let’s get rid of that.

So, before we do that, we need to understand that content-disposition is not 100 percent what you would expect unless your filename is all English numbers.

We extract the filename value:

XHR is an XMLHttpRequest object
const content = xhr.getResponseHeader('content-disposition'); // Note that the header is all lowercase, as is the custom header
if (content) {
    let name1 = content.match(/filename=(.*); /) [1]; // Get the value of filename
    let name2 = content.match(/filename\*=(.*)/) [1]; // Get the value of filename*
    name1 = decodeURIComponent(name1);
    name2 = decodeURIComponent(name2.substring(6)); // utf-8"
}
Copy the code

If name1 contains Chinese characters or special characters, there is a risk that it will not be able to restore the real filename.

defects

  • The file name is not in full numeric English, if the browser only supports itfilenameThe encoding of the file name obtained may be problematic.

The custom header

It’s essentially the same as content-disposition, except instead of using the default header, we’re going to create our own response header, which the back end decides to encode, and the front end takes the custom header and uses the corresponding decoding. Use decodeURIComponent.

But it’s important to remember that in cross-domain situations, the header gets only the default six base fields: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, and Pragma.

So if you want to get another header, you have to go back and set it

Access-Control-Expose-Headers: Content-Disposition, custom-header
Copy the code

In this way, the front end gets the corresponding exposed header field, and it should be noted that Content-Disposition also needs to be exposed.

rename

Here is an additional method, which is used when you know the full name of a file and want to rename it, but the file has the same suffix.

function findType (name) {
    const index = name.lastIndexOf('. ');
    return name.substring(index + 1);
}
Copy the code

Please do not reprint without permission

My wechat official account