Search code examples
javascriptcanvasbase64blobsame-origin-policy

How do I store an image as base64 to be used from local data instead of having to store it on a server? Need to redraw a massive image, even offline


SOLUTION: simply store the base64 as the image source on an HTML image tag and then hide that image. Thanks, Kaiido!

I need to store an image as pixel data in the most performant way possible at it's highest possible resolution. To accomplish I have been fetching the image from imgur and converting to base64 like so:

async function toBase64(file) {
   return new Promise((resolve, reject) => {
   const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result);
      reader.onerror = error => reject(error);
   });
}

async function getBlob(){
   const blob = fetch("https://i.imgur.com/sOmEuRl.jpg")
      .then(function (response) {
         return response.blob();
      })
      .catch(function(err){
          console.log(err)
      })
          
   return blob
}

const blob = await getBlob()
const base64 = await toBase64(blob);
const src = base64

It works tremendously but I need the project I'm working on to be usable offline. Naturally I'm encountering CORS errors when trying to use a local file URL so I'm completely out of ideas at this point.

I've tried loading the image using an img tag in the html but that gives the same CORS error, of course.

Is there any way I can retrieve and store the data locally instead of retrieving it from a server? I've tried reading the pixel data and then writing it to a new file as an array using node, but the resulting file is so massive I can't do anything with it...

Thanks for your time.


Solution

  • Base64 in LocalStorage is definitely not "the most performant way" to store an image, nor will it allow the storage of big files.

    If I understand your case correctly (the need to be able to navigate your site even when offline), then the correct solution is to

    • register a ServiceWorker,
    • make this ServiceWorker store all the required resources, including your images, in the browser's cache through the Cache API,
    • make it retrieve the resources from the cache when your page fetches it again (e.g at next reload).

    More info available at https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps and particularily for this matter at https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Offline_Service_workers

    Now, you may be in a rush and not wanting to build a full PWA setup. In that case, the best solution to store binary data (image files included) in a semi-persistent way is to use the IndexedDB API (IDB). I invite you to read this previous answer of mine showing how to use Mozilla's library localforage which helps a lot dealing with IDB.



    And... turns out I didn't understand the case correctly. (I leave the first section anyway because who knows, it may help others).

    So what OP actually wants is a standalone page, and for this, there is no need to "store" the image anywhere, it suffices to make it part of the markup directly, and this is actually one of the only cases where a data: URL is useful.

    So you'd keep your code that generates the data: URL, but only as a tool for building your HTML file. The HTML file would not contain this code but only the produced data: URL:

    async function toBase64(file) {
       return new Promise((resolve, reject) => {
       const reader = new FileReader();
          reader.readAsDataURL(file);
          reader.onload = () => resolve(reader.result);
          reader.onerror = error => reject(error);
       });
    }
    
    function getBlob(){
       return fetch("https://i.imgur.com/sOmEuRl.jpg")
          .then(function (response) {
             return response.blob();
          })
          .catch(function(err){
              console.log(err);
              return Promise.reject(err);
          });
    }
    
    (async () => {
      const blob = await getBlob()
      const base64 = await toBase64(blob);
      const src = base64;
      const HTMLMarkup = `<!DOCTYPE HTML>
    <html>
      <body>
        <h1>Your HTML page</h1>
        <img src="${src}" alt="an image">
        <!-- your normal HTML content -->
      </body>
    </html>`;
      const HTMLFile = new Blob([HTMLMarkup], { type: "text/html" });
      const HTMLSrc = URL.createObjectURL(HTMLFile);
      document.querySelector("iframe").src = HTMLSrc;
      document.querySelector("pre").textContent = HTMLMarkup;
    })();
    <iframe></iframe>
    <pre></pre>


    But maybe, I'm still not understanding your situation completely and all you need is actually to save that file on your hard-drive, in the same directory as your file (or in a sub-folder of the one where your index.html file is). Even if served through file:// all modern browsers accept to load an <img> if it meets this "same/sub-folder" condition.

    Ps: If it's only for your usage, you may also consider starting a local server on your machine. If you continue doing some web-development you'll very soon find out that you need one.