If I understand correctly URL.createObjectURL creates a URL that represents a file or a blob. Because the URL is just a string the browser has no way to know when you're finished with the resource that URL represents so there's a provided URL.revokeObjectURL function.
MDN shows this example:
var canvas = document.getElementById("canvas");
canvas.toBlob(function(blob) {
var newImg = document.createElement("img");
var url = URL.createObjectURL(blob);
newImg.onload = function() {
// no longer need to read the blob so it's revoked
URL.revokeObjectURL(url);
};
newImg.src = url;
document.body.appendChild(newImg);
});
So some questions
Would it be safe to change the code to revoke the URL immediately after assigning newImg.src?
var canvas = document.getElementById("canvas");
canvas.toBlob(function(blob) {
var newImg = document.createElement("img");
var url = URL.createObjectURL(blob);
newImg.src = url;
// no longer need to read the blob as it's assigned to newImg.src
URL.revokeObjectURL(url);
document.body.appendChild(newImg);
});
I'm guessing the answer is "no" because potentially nothing has started on newImg.src = url;. It's still just a string at that point and will remain so until the current JavaScript event exits. Or is it?
Would it be valid/legal/correct to revoke the URL but still use it knowing it's referenced by other objects?
var canvas = document.getElementById("canvas");
canvas.toBlob(function(blob) {
var newImg = document.createElement("img");
var url = URL.createObjectURL(blob);
newImg.onload = function() {
// no longer need to read the blob so it's revoked
URL.revokeObjectURL(url);
// Use the URL again even though it's revoked
var newImg2 = new Image():
newImg2.src = url;
};
newImg.src = url;
document.body.appendChild(newImg);
});
In this case I'm assigning newImg2.src = url even though I've already revoked the URL. The idea being that newImg is still referencing the blob URL so it would seem valid to be able to say
someImage.src = someOtherImage.src
at any time. Is it?
Okay, well, following #adeneo's advice I tested this
$('#test').on('change', function(e) {
var newImg = document.createElement("img");
var url = URL.createObjectURL( e.target.files[0] )
console.log(url);
newImg.src = url;
URL.revokeObjectURL(url);
document.body.appendChild(newImg);
console.log(url);
});
$('#test3').on('change', function(e) {
var newImg = document.createElement("img");
var url = URL.createObjectURL( e.target.files[0] )
console.log(url);
newImg.src = url;
newImg.onload = function() {
URL.revokeObjectURL(url);
document.body.appendChild(newImg);
var i = new Image();
i.src= newImg.src;
document.body.appendChild(i);
setTimeout(function() {
var g = new Image();
g.src = newImg.src;
document.body.appendChild(g);
}, 3000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>test revoke before use</p>
<input type="file" id="test"/>
<br />
<br />
<br />
<p>test use revoke use</p>
<input type="file" id="test3" />
At least in Firefox, once URL.revokeObjectURL is called the URL can no longer be used, even though other things are accessing it.
So both #1 and #2 in the question fail and, even though in #2 newImg.src still has the URL that URL won't work anywhere else once URL.revokeObjectURL has been called.
In React, there is a hook called useEffect, and one of the things that it does, is what action would you like to take right before you're leaving the component(function), and there you release the existing object URL which was previously created by calling URL.createObjectURL().
Releasing an existing object URL using URL.revokeObjectURL:
useEffect(() => () => URL.revokeObjectURL(file as string), [file])
const onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files[0]) {
setFile(URL.createObjectURL(e.target.files[0]))
}
}
Related
I have the following code:
let self = this;
this.chunks = [];
const canvas2 = document.getElementById("self-canvas");
let recordStream = canvas2.captureStream(1);
var options;
options = {mimeType: 'video/webm; codecs=vp9'};
this.recorder = new MediaRecorder(recordStream, options);
this.recorder.ondataavailable = function(evt) {
self.chunks.push(evt.data);
};
this.recorder.onstop = function(evt) {
console.log("recorder stopping");
const link = document.createElement('a');
const videoBlob = new Blob(self.chunks, { type: "video/webm" });
console.log("file size: " + videoBlob.size);
const url = URL.createObjectURL(videoBlob);
link.href = url;
link.download = "sample.webm";
document.body.append(link);
link.click(); //if I comment out here I can see the video
};
console.log("finished setting controller")
console.log("recorder starting");
this.recorder.start(10000);
// the recorder.stop is called somewhere else
What it is supposed to do is pretty simple:
I have the element with id "self-canvas" that is showing my camera.
Now I am trying to record the camera and download the video from the browser using MediaRecorder, but for some reason I am unable to download the file.
I am sure that the file is being recorded, and console.log("file size: " + videoBlob.size); does not return empty.
But when I let the code run, instead of downloading the file, it tries to open it on the same window, and I cannot even see the video because the previous window disappears with the data of the recording.
However if I comment out the link.click(); I am able to see the video by opening the link on a new page (without closing the previous one). But it still doesn't download...
I used this as example, what am I doing wrong?
For heaven's sake...
I just added target blank and it worked.
link.href = url;
link.download = "sample.webm";
link.target = '_blank';
Probably because the resources are lost if it tries to open on the same page, and because it doesn't actually download the file if it is not a link "click".
Still, I never saw anyone having to add target blank in their examples like this one.
So I wonder why this is the case only for me...
In my website, I am trying to download tainted canvases that I have created. I get the "Not allowed to navigate top frame to data URL:" (followed with a string of data) when I try to do this.
I have looked at other posts about this and they are generally trying to show their canvas or something else instead of saving the canvas.
Here is my code:
//it looks complicated, but the only important lines are the ones before the else statement,
function download_img(el) {
//the if statement is used to see if this is using the canvas or not
if(document.getElementById("canvasImage").style.display != "none"){
alert('canvas')
var canvImg = document.getElementById("canvasImage").toDataURL("image/jpg");
el.href = canvImg;
}else{
//again, this code is for the image side of the project, which works fine
alert('image')
var xhr = new XMLHttpRequest();
xhr.open("GET", document.getElementById("theImg").src, true);
xhr.responseType = "blob";
xhr.onload = function(){
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL(this.response);
var tag = document.createElement('a');
tag.href = imageUrl;
tag.download = "meme";
document.body.appendChild(tag);
tag.click();
document.body.removeChild(tag);
}
xhr.send();
}
}
My HTML:
<a style="float:left;display:inline;" href="" onclick="download_img(this)">Canvas Button</a>
What I want to happen is that the canvas is saved.
Add the download attribute to the <a> tag to force it to download instead of navigate.
I have found code for reading multiple images on the
internet.
Here is the code:
HTML
<input id="browse" type="file" onchange="previewFiles()" multiple>
JavaScript
function previewFiles() {
var preview = document.querySelector('#preview');
var files = document.querySelector('input[type=file]').files;
function readAndPreview(file) {
// Make sure `file.name` matches our extensions criteria
if ( /\.(jpe?g|png|gif)$/i.test(file.name) ) {
var reader = new FileReader();
reader.addEventListener("load", function () {
var image = new Image();
image.height = 100;
image.title = file.name;
image.src = this.result;
preview.appendChild( image );
}, false);
reader.readAsDataURL(file);
}
}
if (files) {
[].forEach.call(files, readAndPreview);
}
}
I have a problem with it, as I do not fully understand what is happening and why it does not preview/seems like it is storing multiple files.
The main problem in the included code is that there is no element with the id preview (ref: var preview = document.querySelector('#preview');)
Adding this and it will work. However, you can skip FileReader as it isn't needed. Instead treat the File as a Blob (they are essentially the same) and use createObjectURL() with it - the performance and the memory footprint are significant better in case you want to display a high amount of images.
document.querySelector('#browse').onchange = function() {
var preview = document.querySelector('#preview');
[].forEach.call(this.files, function(file) {
if (/image\/.*/.test(file.type)) { // use any image format the browser can read
var img = new Image;
img.onload = remURL; // to remove Object-URL after use
img.style.height = "100px"; // use style, "width" defaults to "auto"
img.src = (URL || webkitURL).createObjectURL(file);
preview.appendChild(img); // add image to preview container
}
});
function remURL() {(URL || webkitURL).revokeObjectURL(this.src)}
};
<input id="browse" type="file" multiple>
<div id=preview></div> <!-- an element with this ID was missing -->
Your final lines of code:
if (files) {
[].forEach.call(files, readAndPreview);
}
Should do nothing. It goes for each over an empty array.
I think you were trying to do something like:
files.forEach(readAndPreview)
Which would actually go over all of the files in the files array and call readAndPreview for them.
How do I convert items inside a DIV to base64? See examle FIddle
html:
<div id="img2b64"> <img src="http://indianapublicmedia.org/stateimpact/files/2013/01/apple-image-300x300.jpg"> <div><br />
jQuery:
$('#ss').click(function(event){
var imageUrl = $('#img2b64').find('input[name=url]').val();
console.log('imageUrl', imageUrl);
convertImgToBase64(imageUrl, function(base64Img){
$('.output')
.find('textarea')
.val(base64Img)
.end()
.find('a')
.attr('href', base64Img)
.text(base64Img)
.end()
.find('img')
.attr('src', base64Img);
});
event.preventDefault();
});
function convertImgToBase64(url, callback, outputFormat){
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function(){
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img,0,0);
var dataURL = canvas.toDataURL(outputFormat || 'image/png');
callback.call(this, dataURL);
// Clean up
canvas = null;
};
img.src = url;
}
var imageUrl = $('#img2b64').find('input[name=url]').val();
There is no <input name="url"> in your code so this is returning undefined. Perhaps you meant:
var imageUrl = $('#img2b64').find('img').prop("src");
However you'll find that this introduces its own error:
Image from origin 'http://indianapublicmedia.org' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
Meaning that you can't load that image into a Canvas because the target server does not support CORS. You'll either have to download that image to your own server first & load it into your canvas from there. Either that or enable CORS on indianapublicmedia.org
To problem that I see directly is in your jQuery where you do:
var imageUrl = $('#img2b64').find('input[name=url]').val();
Probably the console.log() below that line doesn't give you any valid URL, that should ring a bell. So the line above the console.log() doesn't do it's job.
That's because you are looking for any <input /> element (with name attribute = "url") inside the DIV with ID "img2b64". Also you are getting the value of the element with val(). You probably wanted to do something differently there.
If you want to get all images inside the DIV, then you need to simply look for img. To get the src attribute, do attr('src'):
var imageUrl = $('#img2b64').find('img').attr('src');
If you want to add an additional check for images with non-data src, do:
var imageUrl = $('#img2b64').find('img:not([src^="data"])').attr('src');
I did not check if the rest of the code (e.g. convertImgToBase64() function) is correctly working. You should always specify in your question what does work and what doesn't to get the best answers on SE.
I'm displaying a long list of images from a site on a page with the below code. Id like to be able to use the download HTML5 attribute so that click each image will download it.
Here's what I've tried:
for (var i = 0; i<arlnData.d.length;i++) {
var img = document.createElement('img');
img.src = "https://images.website.com/airvendors/airlines_"+arlnData.d[i].Code+".gif";
img.download ="my image";
//also tried:
//img.src = "https://images.website.com/airvendors/airlines_"+arlnData.d[i].Code+".gif";
document.body.appendChild(img);
var imageCellspace=document.createElement('br');
document.body.appendChild(imageCellspace);
}
Images are displayed fine but clicking to download doesnt work.
What is the proper syntax here?
Try with this:
for (var i = 0; i<arlnData.d.length;i++) {
var img = document.createElement('img');
img.src = "https://images.website.com/airvendors/airlines_"+arlnData.d[i].Code+".gif";
var a = document.createElement('a');
a.href = img.src;
a.download = 'image.gif';
a.appendChild(img);
document.body.appendChild(a);
var imageCellspace=document.createElement('br');
document.body.appendChild(imageCellspace);
}
The download attribute is for a, not for img. Check the documentation.
You have to wrap your <img> on a <a>, as the download attribute is only for anchors.
var img = document.createElement('img');
img.src = "https://images.website.com/airvendors/airlines_"+arlnData.d[i].Code+".gif";
var a = document.createElement('a');
a.href = img.src;
a.download = "My image name";
a.appendChild(img);
document.body.appendChild(a);
See MDN for reference !