Chrome extension with SVG icons (chrome.browserAction.setIcon) - javascript

I am using scalable SVG icons in my Chrome extension.
chrome.browserAction.setIcon({
tabId: tabId,
path: '../icons/' + icon + '/scalable.svg'
});
I want to switch icons based on some parameters, so I have visual feedback.
What I have realized is that when I am switching icons very quickly, Chrome is messing up and often I end up seeing the wrong icon. I added console.log prints to code to ensure I am switching icons properly and I see that my code has no errors.
It looks like Chrome executes such change of icon requests asynchronously and conversion of SVG to pixels takes sometimes longer than usual. That leads to an execution in the wrong order.
So for example if I switch icons from A to B, then to C; then to D, ... at the end I may see C, although the last request for change was to switch it to D.
Any ideas on how to fix this annoying problem?

Chain the calls to the API using Promise
If you call setIcon often, create a cache of imageData yourself and use it instead of path because the API re-reads the source icon each time and re-creates imageData.
Here's a generic example, not tested:
const queue = {};
const cache = {};
// auto-clean the queue so it doesn't grow infinitely
chrome.tabs.onRemoved.addListener(tabId => delete queue[tabId]);
async function setIcon(tabId, icon) {
const url = '../icons/' + icon + '/scalable.svg';
const imageData = await (cache[url] || (cache[url] = loadImageData(url)));
queue[tabId] = (queue[tabId] || Promise.resolve()).then(() =>
new Promise(resolve =>
chrome.browserAction.setIcon({tabId, imageData}, resolve)));
}
function loadImageData(url) {
return new Promise((resolve, reject) => {
const data = {};
const img = new Image();
img.src = url;
img.onload = () => {
for (const size of [16, 32]) {
const canvas = document.createElement('canvas');
document.documentElement.appendChild(canvas);
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
data[size] = ctx.getImageData(0, 0, size, size);
canvas.remove();
}
resolve(data);
};
img.onerror = reject;
});
}

Related

Copying images and text from an Iframe. As if ctrl a + ctrl c

As the title says I am trying to copy elements from an Iframe programmatically as if I selected and copied them manually with my mouse.
Copying the text was simple enough but I am trying to feed an image into clipboard its coming up blank.
Also i want to append the image and text to have both the image and the text in the clipboard. The same as when you select all with your mouse and r-click, copy. But at the moment I can only seem to do one at a time using navigator.clipboard.writeText(textContents) / navigator.clipboard.write(img)
atm I got a function to select all elements in the iframe and use just needs to right click and copy, but Id like to make the copy function below to do the copying so use just needs to click the button.
the _downloadSigImage is just an alternative option I am working on.
my current code
// select the elements of the iframe so in worst
// case a useer just has to right click and copy
// their signature. Succesfulling reduing friction
// in the copy and pasting process
const _iframeSelectContents = () => {
let frameObj = document.getElementById('signature-preview');
var frameContent = frameObj.contentDocument; // get content of iframe
frameContent.execCommand('selectAll');
}
// an png generator which has some pros and some cons. It ports easily being an image
// and outlook editor offers some interesting image effects which allow a user to add
// some uniqueness and flare to their cards in a easy and fun way.
// con is it's a single point of failure if another email client disables images possibly ??
// cant copy and paste or click text fields.
const _downloadSigImage = () => {
let frameObj = document.getElementById('signature-preview');
let htmlContents = frameObj.contentWindow.document.body;
htmlContents.querySelector('img').setAttribute('crossOrigin','null');
html2canvas(htmlContents, { useCORS: true}).then( canvas => {
const base64image = canvas.toDataURL("img/png", 1);
let anchor = document.createElement('a');
anchor.setAttribute("href", base64image);
anchor.setAttribute("download", "SHAPE-E-Signature.png");
anchor.click();
anchor.remove();
});
}
// ideally we reduce all friction by removing the need for right clicking.
const _copyButton = () => {
let frameObj = document.getElementById('signature-preview');
let textContents = frameObj.contentWindow.document.body.innerText;
let htmlContents = frameObj.contentWindow.document.body;
htmlContents.querySelector('img').setAttribute('crossOrigin','null');
//console.log(frameObj.contentWindow.document.body.innerText)
// Copy image
// let img = new Image();
// img.src = 'http://127.0.0.1:5500/app/js/components/shapelogo.png';
// console.log(htmlContents)
// let canvas = document.createElement('canvas');
// canvas.width = 152;
// canvas.height = 31;
// let context = canvas.getContext('2d');
// context.drawImage(img, 0, 0);
// console.log(img)
// //Save to clipboard
// canvas.toBlob( blob => {
// let data = [new ClipboardItem({ "image/png": blob })]; //[blob.type]
// if (navigator.clipboard) {
// navigator.clipboard.write(data).then( () => {
// console.log('done')
// alert("frame content : " + data + textContents);
// }, err => {
// console.log('error: ' + err)
// });
// } else {
// console.log('Browser do not support Clipboard API')
// }
// }, 'image/png');
// copy text
navigator.clipboard.writeText(textContents);
}
current output for img + text data although image is coming up blank but appears to be right size and can only do either text or image at once. Not both in clipboard simultaneously.
[object HTMLImageElement]
, Fit Living
E: W: fitproperties.com 2020 Three Ventall Centre l 7005 Grifiths St.,
Box 206 Vancouver, BC, Canada V7X 1M6

Capture chrome browser engine output using javascript in order to capture rasterized frames of an SMIL SVG animation

My ultimate goal is that I'm trying to convert an animated SMIL SVG into an APNG file. I have found no easy way to do this, and so I'm doing something a roundabout: I've written a node.js + express.js app that hosts a simple backend to get svg images on my local filesystem, and I've written a vue.js app that will go and pull those images and render them on a google chrome browser. I then play the SVG and try to capture rendered "frames", and save those frames as static png files (about 30 static PNG files for each second of SVG animation). I then plan to take those static png files & convert them over to a single animated png / apng file using another program. The part that I'm stuck on: actually trying to capture a rasterized "frame" of the svg.
Here's a snippet of code from my vue.js app which requests an SVG file, and renders it to a div, and then it tries to call a function takeSnap().
const file = await RequestsService.getFile(i);
const div = document.getElementById("svgContainer");
div.innerHTML = file.svg;
const { width, height } = div.children[0].getBBox();
console.debug(`width: ${width}, height: ${height}`);
const svg = div.children[0];
await svg.pauseAnimations();
let time = 0.0;
const interval = 1.0 / numFrames; // interval in seconds.
let count = 0;
while (time < file.duration) {
console.log(`time=${time}`);
await svg.setCurrentTime(time);
await this.takeSnap(svg, width, height);
time += interval;
console.debug(`file: ${file.fileName}_${count}`);
}
await svg.setCurrentTime(file.duration);
await this.takeSnap(svg, width, height);
I haven't been able to make a proper implementation of takeSnap(). I know that there are a slew of tools such as Canvg or HTML2png that go and directly render a webpage from the DOM. I've tried many different libraries, but none of them seem to be able to correctly render the frame of the SVG that chrome is correctly rendering. I don't blame the libraries: going from animated SVG XML file to actually rasterized pixels is a very difficult problem I think. But Chrome can do it, and what I'm wondering is... can I capture the browser engine output of chrome somehow?
Is there a way that I can get the rasterized pixel data produced by the blink browser engine in chrome & then save that rasterized pixel data into a png file? I know that I'll lose the transparency data of the SVG, but that's okay, I'll work around that later.
OK, this got a bit complicated. The script can now take SMIL animations with both <animate> and <animateTransform>. Essentially I take a snap shot of the SVG using Window.getComputedStyle() (for <animate> elements) and the matrix value using SVGAnimatedString.animVal (for <animateTransform> elements). A copy of the SVG is turned into a data URL and inserted into a <canvas>. From here it is exported as a PNG image.
In this example I use a data URL in the fetch function, but this can be replaced by a URL. The script has been tested with the SVG that OP provided.
var svgcontainer, svg, canvas, ctx, output, interval;
var num = 101;
const nsResolver = prefix => {
var ns = {
'svg': 'http://www.w3.org/2000/svg',
'xlink': 'http://www.w3.org/1999/xlink'
};
return ns[prefix] || null;
};
const takeSnap = function() {
// get all animateTransform elements
let animateXPath = document.evaluate('//svg:*[svg:animateTransform]', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// store all animateTransform animVal.matrix in a dataset attribute
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
let mStr = [...node.transform.animVal].map(animVal => {
let m = animVal.matrix;
return `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m.f})`;
}).join(' ');
node.dataset.transform = mStr;
});
// get all animate elements
animateXPath = document.evaluate('//svg:animate', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// store all animate properties in a dataset attribute on the target for the animation
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
let propName = node.getAttribute('attributeName');
let target = node.targetElement;
let computedVal = getComputedStyle(target)[propName];
target.dataset[propName] = computedVal;
});
// create a copy of the SVG DOM
let parser = new DOMParser();
let svgcopy = parser.parseFromString(svg.outerHTML, "application/xml");
// find all elements with a dataset attribute
animateXPath = svgcopy.evaluate('//svg:*[#*[starts-with(name(), "data")]]', svgcopy, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// copy the animated property to a style or attribute on the same element
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
// for each data-
for (key in node.dataset) {
if (key == 'transform') {
node.setAttribute(key, node.dataset[key]);
} else {
node.style[key] = node.dataset[key];
}
}
});
// find all animate and animateTransform elements from the copy document
animateXPath = svgcopy.evaluate('//svg:*[starts-with(name(), "animate")]', svgcopy, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// remove all animate and animateTransform elements from the copy document
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
node.remove();
});
// create a File object
let file = new File([svgcopy.rootElement.outerHTML], 'svg.svg', {
type: "image/svg+xml"
});
// and a reader
let reader = new FileReader();
reader.addEventListener('load', e => {
/* create a new image assign the result of the filereader
to the image src */
let img = new Image();
// wait got load
img.addEventListener('load', e => {
// update canvas with new image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
// create PNG image based on canvas
let img = new Image();
img.src = canvas.toDataURL("image/png");
output.append(img);
//let a = document.createElement('A');
//a.textContent = `Image-${num}`;
//a.href = canvas.toDataURL("image/png");
//a.download = `Image-${num}`;
//num++;
//output.append(a);
});
img.src = e.target.result;
});
// read the file as a data URL
reader.readAsDataURL(file);
};
document.addEventListener('DOMContentLoaded', e => {
svgcontainer = document.getElementById('svgcontainer');
canvas = document.getElementById('canvas');
output = document.getElementById('output');
ctx = canvas.getContext('2d');
fetch('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMCAxMCI+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0ibmF2eSI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJmaWxsIiBkdXI9IjNzIiBrZXlUaW1lcz0iMDsuMTsuMjsxIiB2YWx1ZXM9Im5hdnk7bGlnaHRibHVlO2JsdWU7bmF2eSIvPgogICAgPGFuaW1hdGVUcmFuc2Zvcm0gYWRkaXRpdmU9InN1bSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBkdXI9IjNzIiBrZXlUaW1lcz0iMDsuNTsxIiB0eXBlPSJ0cmFuc2xhdGUiIHZhbHVlcz0iMCwwOzIsMjs0LDYiLz4KICAgIDxhbmltYXRlVHJhbnNmb3JtIGFkZGl0aXZlPSJzdW0iIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgZHVyPSIzcyIga2V5VGltZXM9IjA7LjU7MSIgdHlwZT0icm90YXRlIiB2YWx1ZXM9IjAsMiwyOzMwLDIsMjstMTAsMiwyOyIvPgogIDwvcmVjdD4KPC9zdmc+CgoK').then(res => res.text()).then(text => {
let parser = new DOMParser();
let svgdoc = parser.parseFromString(text, "application/xml");
canvas.width = svgdoc.rootElement.getAttribute('width');
canvas.height = svgdoc.rootElement.getAttribute('height');
svgcontainer.innerHTML = svgdoc.rootElement.outerHTML;
svg = svgcontainer.querySelector('svg');
// set interval
interval = setInterval(takeSnap, 50);
// get all
let animateXPath = document.evaluate('//svg:*[starts-with(name(), "animate")]', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
let animationArr = Object.keys([...Array(animateXPath.snapshotLength)]).map(i => {
let node = animateXPath.snapshotItem(i);
return new Promise((resolve, reject) => {
node.addEventListener('endEvent', e => {
resolve();
});
});
});
Promise.all(animationArr).then(value => {
clearInterval(interval);
});
});
});
<div style="display:flex">
<div id="svgcontainer"></div>
<canvas id="canvas" width="200" height="200"></canvas>
</div>
<p>Exported PNGs:</p>
<div id="output"></div>

SVG Image load event firing before image is actually loaded

I have an SVG image that contains <image> tags with hrefs to other external images. For an function on this SVG I need to convert these URLs to their dataURL equivalent before exporting. I have the conversion working am having some difficulties waiting for all the URLs to finish converting prior to calling the function.
My code looks something like the following:
// Convert a URL to a dataURL, returns Promise for when it's complete
const convertUrlToDataUrl = (url) => {
let resolvePromise, rejectPromise;
const promise = new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
const img = document.createElement('img');
img.crossOrigin = 'anonymous';
img.onload = (event) => {
const loadedImage = event.target;
const {width, height} = loadedImage;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.drawImage(loadedImage, 0, 0);
const dataUrl = canvas.toDataURL();
resolvePromise(dataUrl);
};
img.onerror = () => {
rejectPromise();
};
img.src = url;
return promise;
};
// Updates a SVG image tags href to a dataURL of the image
const updateImageToDataUrl = async (image) => {
const url = image.getAttribute('href') || image.getAttribute('xlink:href');
try {
dataUrl = await convertUrlToDataUrl(url);
} catch(e) {
dataUrl = 'some backup data URL';
}
// Update image tag image reference to data URL and create imageUrlUpdated promise to complete when image has loaded
let resolvePromise, rejectPromise;
const imageUrlUpdated = new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
image.addEventListener('load', e => resolvePromise(), {once: true});
image.addEventListener('error', () => rejectPromise(), {once: true});
// Update image (both xlink:href and href for backwards compatiablity)
if (image.hasAttribute('xlink:href')) { image.setAttribute('xlink:href', dataUrl); }
if (image.hasAttribute('href')) { image.setAttribute('href', dataUrl); }
// Wait for image to load after updating URL
try {
await imageUrlUpdated;
console.log('Image fully updated');
} catch (e) {
console.log('Something went wrong');
}
};
// Get all svg image elements to change
const svgImages = getSVGImageElements();
// Start URL conversion process for each image and store promise of each
const imageUpdatingPromises = [];
svgImages.forEach(image => {
imageUpdatingPromises.push(updateImageToDataUrl(image));
});
// Wait for all images to be loaded
await Promise.allSettled(imageUpdatingPromises);
console.log('Starting doStuffWithSVG');
doStuffWithSVG();
The intended behaviour of the above code is to wait until all the SVG image tags have been updated with data URLS before calling doStuffWithSVG();. Upon testing it appears that doStuffWithSVG() is running before all the images have updated (I say this as I am getting an error that occurs when using an external URL instead of a data URL for my particular use case).
While trying to debug this all the logs "Image fully updated" happen before the log "Starting doStuffWithSVG" and "Something went wrong" is never called which indcates that my promises all do seem to be running in the right order and waiting appropriately. I can confirm that the actual href and xlink:href attributes on the image tag are all data URLs by the time doStuffWithSVG is called but I am still getting an error. In addition when calling this code in succession it will work after the first time, my guess for why is that the images are cached so the actual images update faster the subsequent runs.
I should mention that I am only seeing this issue in Safari (I am still seeing all the same expected logging in Safari though) but I am not convinced it is a browser specific issue but that the safari javascript engine is just slower and is why I am seeing it here (Can confirm that compared to chrome or firefox, Safari takes much longer to convert the images to data URLs). The reason I think this is that I have not been able to find any other differences in what is getting called or in what order between the browsers besides speed (Safari still uses xlink:href instead of href but that doesn't seem to be making a difference here).
With everything above it seems to me like the load event is firing on the SVGImageElements before the actual image has loaded. That or I have made a mistake in setting up and awaiting my Promises somewhere. Any help on this would be great as I have run out of ideas of how else I can accomplish this or what else the problem could be.

FileSaver.js saveAs view returns 'undefined' in Chrome 65

I have implemented filesaver in my project before, but since I changed it to ES6 modules, saveAs won't run. From some investigation the view within the FileSaver function is returning undefined, which causes the function to return and not save my file. Below is how I am calling it. Rasterize and Serialize are functions that create a blob from an svg which is refactored from here.
Would the change in view be due to the rollup process?
var rasta = await rasterize(chart);
saveAs(rasta, `chart.png`);
}
function rasterize(svg) { // FROM BOSTOCK via Observable modified for use outside of Observable environment
let resolve, reject;
const promise = new Promise((y, n) => (resolve = y, reject = n));
const image = new Image;
image.onerror = reject;
image.onload = () => {
const rect = svg.getBoundingClientRect();
const context = d3.create("canvas").attr("height", rect.height).attr("width", rect.width).node().getContext("2d");
context.fillStyle = "white";
context.fillRect(0,0,rect.width,rect.height);
context.drawImage(image, 0, 0, rect.width, rect.height);
context.canvas.toBlob(resolve);
};
image.src = URL.createObjectURL(serialize(svg));
return promise;
}
function serialize(svg) { // FROM BOSTOCK via Observable modified for use outside of Observable environment
const xmlns = "http://www.w3.org/2000/xmlns/";
const xlinkns = "http://www.w3.org/1999/xlink";
const svgns = "http://www.w3.org/2000/svg";
svg = svg.cloneNode(true);
svg.setAttributeNS(xmlns, "xmlns", svgns);
svg.setAttributeNS(xmlns, "xmlns:xlink", xlinkns);
const serializer = new window.XMLSerializer;
const string = serializer.serializeToString(svg);
return new Blob([string], {type: "image/svg+xml"});
}
UPDATE
It seems that if I copy paste the original FileSaver file contents into the rollup generated one it works correctly, must be an issue with how the rollup process works.

How to convert multiple images to base64 with canvas

I am storing base64 encoded images, and at the moment I can only create one code (i'm attempting to create two, but it appears the second is being overwritten). I don't get the over-arching concept of canvas drawing, so I believe that is the root of my issue when trying to solve this problem.
current behavior: It stores the same DataUrl in local storage twice. It does log the correct info. the favicon-green is getting stored, just not red
How do I encode multiple base64 images with canvas?
html:
<head>
...
<link id="favicon" rel="icon" src="/assets/favicon-red-16x16.ico.png">
</head>
<body>
...
<!-- hidden images to store -->
<img id="favicon-green" rel="icon" src="/assets/favicon-green-16x16.ico.png" width="16" height="16" />
<img id="favicon-red" rel="icon" src="/assets/favicon-red-16x16.ico.png" width="16" height="16" />
...
</body>
js:
// cache images
function storeImages() {
// my sorry attempt to create two canvas elements for two image encodings
var canvasGreen = document.createElement('canvas');
var canvasRed = document.createElement('canvas');
// painting both images
var ctxGreen = canvasGreen.getContext('2d');
var ctxRed = canvasRed.getContext('2d');
// getting both images from DOM
var favGreen = document.getElementById('favicon-green');
var favRed = document.getElementById('favicon-red');
// checking if images are already stored
var base64Green = localStorage.getItem('greenFavicon');
var base64Red = localStorage.getItem('redFavicon');
console.log('storing...')
if (base64Green == null && window.navigator.onLine || base64Red == null && window.navigator.onLine) {
ctxGreen.drawImage(favGreen, 0, 0);
ctxRed.drawImage(favRed, 0, 0);
// getting images (the DataUrl is currently the same for both)
base64Green = canvasGreen.toDataURL();
base64Red = canvasRed.toDataURL();
localStorage.setItem('greenFavicon', base64Green);
localStorage.setItem('redFavicon', base64Red);
console.log("are they equal : ", base64Green == base64Red); // returns true
}
}
storeImages();
I don't see anything particularly wrong with your code. If the code isn't a direct copy and paste, I would look through it with a fine-tooth come to make sure you don't switch any red and green around.
There shouldn't be any surprising mechanisms when it comes to converting canvases to data URLs.
Here is a quick example of two:
const a = document.createElement('canvas');
const b = document.createElement('canvas');
const aCtx = a.getContext('2d');
const bCtx = b.getContext('2d');
aCtx.fillStyle = '#000';
aCtx.fillRect(0, 0, a.width, a.height);
const aUrl = a.toDataURL();
const bUrl = b.toDataURL();
console.log(aUrl == bUrl, aUrl, bUrl);
console.log('First difference index:', Array.prototype.findIndex.call(aUrl, (aChar, index) => aChar !== bUrl[index]));
Notice that they are different. However, notice that they also start out very similar, and you have to go quite a ways over to start seeing differences (in my example, character 70). I would double-check that they are actually the same (by comparing them like I did). It could be it just looks the same.
Another thing you might do, which is more of a code style thing, but could also help with accidentally green and red mixups, is make a function to save just one, then call it twice.
const saveImage = (imageId, key) => {
if (localStorage.getItem(key)) {
return; // already saved
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById(imageId);
ctx.drawImage(image, 0, 0);
if (window.navigator.onLine) {
localStorage.setItem(key, canvas.toDataURL());
}
}
saveImage('favicon-green', 'greenFavicon');
saveImage('favicon-red', 'redFavicon');
Not only does that clean up your code and keep it DRY, but it also helps avoid accidental mix-ups between red and green in your function.
After some comments back and forth, I realized another possibility is you are trying to draw the images to the canvas before the images are loaded. This will cause it to draw blank images, but otherwise act like it is working fine.
You can quickly test this by console logging this:
console.log(image.width === 0);
after setting the image variable. If the value is true, then the image isn't loaded yet (before loading, images will have a width and height of 0). You need to make sure to wait until the image is loaded before trying to save it.
The best way to do this is with an addEventListener():
document.getElementById('favicon-green').addEventListener('load', () => {
saveImage('favicon-green', 'greenFavicon');
});
There is one more catch with this, in that if the image is somehow already loaded by the time that code runs, it'll never trigger. You need to look at the width of the image as well. Here is a function that does this all for you, and returns a Promise so you know it's done:
const saveImage = (imageId, key) => new Promise(resolve => {
if (localStorage.getItem(key)) {
return resolve(); // already saved
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById(imageId);
const onImageLoaded = () => {
ctx.drawImage(image, 0, 0);
if (window.navigator.onLine) {
localStorage.setItem(key, canvas.toDataURL());
}
resolve();
}
if (image.width > 0) {
onImageLoaded();
} else {
image.addEventListener('load', onImageLoaded);
}
});
saveImage('favicon-green', 'greenFavicon').then(() => console.log('saved'));

Categories

Resources