Skip to content

Instantly share code, notes, and snippets.

@dcousineau
Last active August 29, 2015 14:18
Show Gist options
  • Select an option

  • Save dcousineau/ecad177f95c6fd1f4acf to your computer and use it in GitHub Desktop.

Select an option

Save dcousineau/ecad177f95c6fd1f4acf to your computer and use it in GitHub Desktop.
Super safe CORS image loader that bypasses browser caches
import image from 'image';
image
.load("/my/image/url")
.then(img => {
let canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0);
//...
})
;
let registry = {};
let pending = {};
/**
* IE10 does not support CORS properly with canvases, we must use an alternative image loading mechanism to work around
* this fact
*/
let isIE = (() => {
let version = new Function("/*@cc_on return @_jscript_version; @*/")(); // jshint ignore:line
return !!(parseInt(version) && parseInt(version) < 11);
})();
/**
* Returns true if src url is a data uri
*
* @param src
* @returns {bool}
*/
function isDataUri(src) {
return src.match(/^data:/);
}
/**
* Accepts an image object and returns data url string (in PNG format)
*
* @param image
* @returns {string}
*/
function generateDataUri(image) {
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas.toDataURL("image/png");
}
/**
* Clones image. Returns promise that resolves when browser promises the image is available.
*
* @param image
* @param newImage image object to clone into
* @returns {Promise}
*/
function clone(image, newImage = null) {
return new Promise((resolve, reject) => {
let cloned = newImage || new Image();
cloned.onload = () => {
resolve(cloned);
};
cloned.onerror = () => reject();
cloned.src = image.src;
});
}
/**
* Stores image (and src key) in the image registry, also clears any pending requests for the image
*
* @param src
* @param image
*/
function store(src, image) {
registry[src] = {
image: image,
created: new Date(),
refs: 1
};
//We successfully resolved, delete the pending request
delete pending[src];
}
/**
* Loads an image from a URL and returns a promise that resolves when the image is fully loaded.
*
* If a dataurl is provided, load acts as a pass-through and automatically resolves a promise.
*
* The image object provided by the resolved promise is always a clone of the requested asset.
*
* Returns a Promise that resolves with the image object ready to use
*
* @param src
* @param newImage
* @returns Promise
*/
function load(src, newImage = null) {
if (src in pending) {
//There is a pending request for this resource, return this unresolved promise instead
return pending[src];
}
if (isDataUri(src)) {
//DataURI images are not actually available immediately, we must wait on the browser to
//trigger the onload event as well
return new Promise((resolve, reject) => {
let img = newImage || new Image();
img.onload = () => {
resolve(img);
};
img.onerror = () => reject();
img.src = src;
});
}
if (src in registry) {
return clone(registry[src].image, newImage);
}
pending[src] = new Promise((resolve, reject) => {
let img = new Image();
let isObjectUrl = false;
img.onload = () => {
let cacheImage = new Promise((resolve, reject) => {
let final = new Image();
final.onload = () => {
resolve(final);
};
final.onerror = () => reject();
final.src = generateDataUri(img);
});
cacheImage.then(cached => {
img = null; //Unset image, we're done
store(src, cached);
clone(cached, newImage).then(cloned => {
resolve(cloned);
});
});
if (isObjectUrl) URL.revokeObjectURL(img.src);
};
img.onerror = () => {
//We successfully resolved, delete the pending request
delete pending[src];
reject();
};
if (isIE) {
isObjectUrl = true;
let xhr = new XMLHttpRequest();
xhr.onload = function() {
img.src = URL.createObjectURL(this.response);
};
xhr.open('GET', src, true);
xhr.responseType = 'blob';
xhr.send();
} else {
img.crossOrigin = 'Anonymous';
img.src = src;
}
});
return pending[src];
}
/**
* Purges registry of an image (src). If no src is provided, purges the entire registry
*
* @param src
*/
function purge(src = null) {
if (src === null) {
registry = {};
} else {
delete registry[src];
}
}
export default {isDataUri, load, purge};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment