Search code examples
javascriptcanvassave-as

Force showing the "Save as" dialog box when downloading a file


Below code does save a file to the user's disk:

function handleSaveImg(event){
  const image = canvas.toDataURL();
  const saveImg = document.createElement('a');
  saveImg.href = image;
  saveImg.download= saveAs;
  saveImg.click();
}

if(saveMode){
  saveMode.addEventListener("click", handleSaveImg);
}

It uses an <a> tag to save some data (in my case, an image exported from a <canvas>).

But this saves directly to the disk, with no prompt asking where to save the file, nor under which name.

I want to force the displaying of the "Save as" dialog box, so that the user has to choose where they'll save that file.
Is there any way?


Solution

  • Yes, and it's called showSaveFilePicker().

    This is part of the File System Access API, which is still a draft, but is already exposed in all Chromium browsers.

    This API is quite powerful and will give your code direct access to the user's disk, so it is only available in secure contexts.

    Once the Promise returned by this method resolve, you'll get access to an handle where you'll be able to access a WritableStream to which you'll be able to write your data.

    It's a bit more complicated than download, but it's also a lot more powerful, since you can write as stream, not needing to have the whole data in memory (think recording a video).

    In your case this would give

    async function handleSaveImg(event){
      const image = await new Promise( (res) => canvas.toBlob( res ) );
      if( window.showSaveFilePicker ) {
        const handle = await showSaveFilePicker();
        const writable = await handle.createWritable();
        await writable.write( image );
        writable.close();
      }
      else {
        const saveImg = document.createElement( "a" );
        saveImg.href = URL.createObjectURL( image );
        saveImg.download= "image.png";
        saveImg.click();
        setTimeout(() => URL.revokeObjectURL( saveImg.href ), 60000 );
      }
    }
    

    Here is a live demo, (and the code).