How to download images without displaying them using Vue.js?

12,261

Solution 1

I created a solution based on blob. First, I created a blob using the image url. After the image blob is loaded I called the saveAs method extracted from FileSaver to have the blob quietly.

let vm = new Vue({
  el: '#vue-instance',
  data: {
    link: 'https://i.imgur.com/lF1GKDt.jpg',
  },
  created() {
    this.remoteURL();
  },
  methods: {
    downloadImg() {
      let url = this.remoteURL();
      fetch(url)
        .then((response) => response.blob())
        .then((blob) => {
          saveAs(blob, 'image_name.jpg');
        });
      console.log('downloading', url);
    },
    remoteURL() {
      //return this.BASE_URL + link //link is the image's path fetched from database
      return this.link;
    },
  }
});



var _global = typeof window === 'object' && window.window === window ?
  window : typeof self === 'object' && self.self === self ?
  self : typeof global === 'object' && global.global === global ?
  global :
  this

function bom(blob, opts) {
  if (typeof opts === 'undefined') opts = {
    autoBom: false
  }
  else if (typeof opts !== 'object') {
    console.warn('Deprecated: Expected third argument to be a object')
    opts = {
      autoBom: !opts
    }
  }

  // prepend BOM for UTF-8 XML and text/* types (including HTML)
  // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
  if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
    return new Blob([String.fromCharCode(0xFEFF), blob], {
      type: blob.type
    })
  }
  return blob
}

function download(url, name, opts) {
  var xhr = new XMLHttpRequest()
  xhr.open('GET', url)
  xhr.responseType = 'blob'
  xhr.onload = function() {
    saveAs(xhr.response, name, opts)
  }
  xhr.onerror = function() {
    console.error('could not download file')
  }
  xhr.send()
}

function corsEnabled(url) {
  var xhr = new XMLHttpRequest()
  // use sync to avoid popup blocker
  xhr.open('HEAD', url, false)
  xhr.send()
  return xhr.status >= 200 && xhr.status <= 299
}

// `a.click()` doesn't work for all browsers (#465)
function click(node) {
  try {
    node.dispatchEvent(new MouseEvent('click'))
  } catch (e) {
    var evt = document.createEvent('MouseEvents')
    evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
      20, false, false, false, false, 0, null)
    node.dispatchEvent(evt)
  }
}

var saveAs = _global.saveAs || (
  // probably in some web worker
  (typeof window !== 'object' || window !== _global) ?
  function saveAs() { /* noop */ }

  // Use download attribute first if possible (#193 Lumia mobile)
  :
  'download' in HTMLAnchorElement.prototype ?
  function saveAs(blob, name, opts) {
    var URL = _global.URL || _global.webkitURL
    var a = document.createElement('a')
    name = name || blob.name || 'download'

    a.download = name
    a.rel = 'noopener' // tabnabbing

    // TODO: detect chrome extensions & packaged apps
    // a.target = '_blank'

    if (typeof blob === 'string') {
      // Support regular links
      a.href = blob
      if (a.origin !== location.origin) {
        corsEnabled(a.href) ?
          download(blob, name, opts) :
          click(a, a.target = '_blank')
      } else {
        click(a)
      }
    } else {
      // Support blobs
      a.href = URL.createObjectURL(blob)
      setTimeout(function() {
        URL.revokeObjectURL(a.href)
      }, 4E4) // 40s
      setTimeout(function() {
        click(a)
      }, 0)
    }
  }

  // Use msSaveOrOpenBlob as a second approach
  :
  'msSaveOrOpenBlob' in navigator ?
  function saveAs(blob, name, opts) {
    name = name || blob.name || 'download'

    if (typeof blob === 'string') {
      if (corsEnabled(blob)) {
        download(blob, name, opts)
      } else {
        var a = document.createElement('a')
        a.href = blob
        a.target = '_blank'
        setTimeout(function() {
          click(a)
        })
      }
    } else {
      navigator.msSaveOrOpenBlob(bom(blob, opts), name)
    }
  }

  // Fallback to using FileReader and a popup
  :
  function saveAs(blob, name, opts, popup) {
    // Open a popup immediately do go around popup blocker
    // Mostly only available on user interaction and the fileReader is async so...
    popup = popup || open('', '_blank')
    if (popup) {
      popup.document.title =
        popup.document.body.innerText = 'downloading...'
    }

    if (typeof blob === 'string') return download(blob, name, opts)

    var force = blob.type === 'application/octet-stream'
    var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
    var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)

    if ((isChromeIOS || (force && isSafari)) && typeof FileReader === 'object') {
      // Safari doesn't allow downloading of blob URLs
      var reader = new FileReader()
      reader.onloadend = function() {
        var url = reader.result
        url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
        if (popup) popup.location.href = url
        else location = url
        popup = null // reverse-tabnabbing #460
      }
      reader.readAsDataURL(blob)
    } else {
      var URL = _global.URL || _global.webkitURL
      var url = URL.createObjectURL(blob)
      if (popup) popup.location = url
      else location.href = url
      popup = null // reverse-tabnabbing #460
      setTimeout(function() {
        URL.revokeObjectURL(url)
      }, 4E4) // 40s
    }
  }
)

_global.saveAs = saveAs.saveAs = saveAs

if (typeof module !== 'undefined') {
  module.exports = saveAs;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-instance">
  <span @click="downloadImg()">Download Image</span>
</div>

Update:

I removed the jQuery and FileSaver and adapted a vanilla version.

Solution 2

I used Axios

import axios from "axios";

async downloadImage(url) {
  var base64 = await axios
    .get(url, {
      responseType: "arraybuffer"
    })
    .then(response =>
      Buffer.from(response.data, "binary").toString("base64")
    );
  var img = new Image();
  img.src = "data:image/jpeg;base64, " + base64;
  return img;
},

var img = await this.downloadImage("https://upload.wikimedia.org/wikipedia/commons/d/d9/Test.png");
console.log(img.width);
Share:
12,261

Related videos on Youtube

qliq
Author by

qliq

Updated on September 28, 2022

Comments

  • qliq
    qliq 2 months

    In my Vue.js app, I'd like force browser to download image without displaying it when a button is clicked.

    Following this answer, I came up with this solution:

    <span @click="downloadImg(remoteURL)">
            Download Image                  
    </span>
    

    And the method:

    downloadImg(url) {
        console.log('downloading', url); 
        document.execCommand('SaveAs',true, url);
        },
    

    remoteURL is in fact a small function which generates url like this:

      remoteURL(link) {        
           return this.BASE_URL+ link  //link is the image's path fetched from database
      },
    

    But when I click Download Image nothing happens.

    This question is not relevant because I don't want to just Download image with JavaScript. What I want is to download image quietly, that is without displaying the image.

    How can I fix it?

    • Yom T.
      Yom T. almost 4 years
      This post might help. It's for jQuery, but the idea is you could use an anchor element as a workaround.
    • AndrewShmig
      AndrewShmig almost 4 years
      what about headers sent from server when requesting image - inline / attachment ?
  • qliq
    qliq almost 4 years
    Thanks but I'd like to do it in vanilla vue.js, without jQuery or external packages.
  • ebbishop
    ebbishop almost 4 years
    @qliq - Unless you want to go the completely vanilla js route (gomakethings.com/ajax-and-apis-with-vanilla-javascript) you must use some kind of library to make http requests to get the image for downloading. Because vue.js is a library written in javascript, most things that work in vanilla javascript (or any other js library) will work in a vue app. All you have to do (most of the time) is wrap them in a vue method and call as desired. If you want to use a library recommended specifically for vue, check out axios: vuejs.org/v2/cookbook/using-axios-to-consume-apis.html
  • Stephen Ostermiller
    Stephen Ostermiller about 1 year
    A code-only answer is not high quality. While this code may be useful, you can improve it by saying why it works, how it works, when it should be used, and what its limitations are. Please edit your answer to include explanation and link to relevant documentation.