Last active
August 29, 2015 14:18
-
-
Save dcousineau/ecad177f95c6fd1f4acf to your computer and use it in GitHub Desktop.
Super safe CORS image loader that bypasses browser caches
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| //... | |
| }) | |
| ; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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