Poor mobile performance after setting background image from file input - javascript

I am currently in the process of developing a webapp using react(gatsby) where users can make posts. Posts contain of an image and multiple text fields. I get the image from the user by using a file type input. I'm using FileReader to read the image and pass it to state. Because i want to show a preview to the author I set the background-image of a div to the in the state saved image. If i do so the application becomes really slow. There is a very noticable lag when typing in one of the text inputs. Does anyone know why this could be? I understand that depending on size the file reader might take a sec to load the image but why does performance after loading suffers that much?
This is my file input:
<input type="file" accept="image/*" capture="camera" ref={cameraInputRef}
onChange = {e => {
e.persist();
readFile(e.target.files[0])
}}
>
</input>
This is my FileReader:
function readFile(file){
// let fileReader = new FileReader();
fileReader.addEventListener('load', () => {
// puts image in state
setDishImage(fileReader.result);
})
fileReader.readAsDataURL(file)
}
This is the css on the div:
background-image: url(props.dishImage});
background-position-y: center;
background-size: cover;
height:100vw;
width:100%;
Any help or will be greatly appreciated.

Your current implementation is not ideal for users who select large images (which is often the case, when selecting an image from the cameraroll). You can address this by resizing the image in a canvas and generating a blob URL, e.g.:
function handleFileChange(event) {
if (event.target.files.length === 0) {
return;
}
const file = event.target.files[0];
const fileReader = new FileReader();
fileReader.onload = () => {
const src = URL.createObjectURL(new Blob([fileReader.result], { type: file.type }));
const img = new Image();
img.onload = () => createPreview(img, file.type).then((preview) => {
console.log(preview);
previewImage.src = preview;
previewImage.style.display = "block";
// Remove in-memory blob URLs
URL.revokeObjectURL(src);
// Clean up the preview blob URL when you are done with the preview
// URL.revokeObjectURL(preview);
});
img.src = src;
};
fileReader.readAsArrayBuffer(file);
}
function createPreview(img, type, constraints = { width: 640, height: 480 }) {
return new Promise((resolve) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = constraints.width;
canvas.height = constraints.height;
const ratioH = canvas.width / img.width;
const ratioV = canvas.height / img.height;
const ratio = Math.min(ratioH, ratioV);
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width * ratio, img.height * ratio);
canvas.toBlob((blob) => resolve(URL.createObjectURL(blob)), type);
});
}
const filePicker = document.querySelector("#filePicker");
const previewImage = document.querySelector("#previewImage");
filePicker.onchange = handleFileChange;
<input type="file" max="1" accept="image/*" id="filePicker"/>
<img src="" alt="Image Preivew" id="previewImage" style="display: none;" />
You can improve this furthur by using the new OffscreenCanvas API (if the user's device supports it).

Related

Changing img.src doesn't always load the new image immediately

I have a simple demo where users can select an image file, and I want to extract the RGB values of the image file as a ImageData. Here's my attempt:
<form>
<label for="img">Select image:</label>
<div>
<input type="file" id="img" name="img" accept="image/*" onchange="loadImage(this)">
</div>
</form>
<canvas id="origCanvas" .../>
function loadImage(input) {
if (input.files && input.files[0]) {
var imageFile = input.files[0]
var reader = new FileReader();
reader.onload = function() {
var img = new Image(CANVAS_WIDTH, CANVAS_HEIGHT);
img.src = reader.result;
readRGB(img);
}
reader.readAsDataURL(imageFile);
}
}
function readRGB(img) {
var c = document.getElementById("origCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// do something else
}
However, when I keep selecting different files, readRGB only extracts the correct imageData some of the times. Other times the imageData I extracted are the old imageData that's already there on the canvas.
My gut feeling says that img.src = reader.result is an async operation, and it's at the mercy of the browser when img will be loaded. Many times when I try to ctx.drawImage(img), I end up drawing an empty canvas because img hasn't loaded the actual image file yet.
Is my guess correct, and what should I do in this case?

Javascript canvas drawImage results in blank? -revised-

I'm working inside a lit-element component trying to draw an img element onto a canvas element:
return html`
<img></img>
<br>
<canvas width="1500" height="1500"></canvas>
<br>
<input type='file' accept='image/*' #change=${this.readImage}> </input>
`
readImage(event) {
// Get the image element
var img = this.shadowRoot.querySelector('img')
// Get the file
var file = event.target.files[0]
// File reader
var reader = new FileReader()
// Handle read
reader.onload = event=>{
img.src = event.target.result
// Get the canvas element
var canvas = this.shadowRoot.querySelector('canvas')
// Get the canvas context
var context = canvas.getContext('2d')
// Draw image on canvas
context.drawImage(img,0,0)
}
// Read the file
reader.readAsDataURL(file)
}
However for some reason the canvas element remains blank. I can't seam to find out where I'm going wrong or if I'm running into some kind of bug?
You need to use onload to make sure the image has finished loading. Is there a reason you aren't just using a new Image() object instead of using an embedded img element?
this.shadowRoot=document;
function
readImage(event) {
// Get the image element
var img = this.shadowRoot.querySelector('img')
// Get the file
var file = event.target.files[0]
// File reader
var reader = new FileReader()
// Handle read
reader.onload = event => {
img.onload = () => {
// Get the canvas element
var canvas = this.shadowRoot.querySelector('canvas')
// Get the canvas context
var context = canvas.getContext('2d')
// Draw image on canvas
context.drawImage(img, 0, 0)
}
img.src = event.target.result
}
// Read the file
reader.readAsDataURL(file)
}
<img></img>
<br>
<canvas width="1500" height="1500"></canvas>
<br>
<input type='file' accept='image/*' onchange=readImage(event)> </input>

How to wait for the multiple images to load in the canvas and convert to base64?

I'm trying to get a base64 version of a canvas in HTML5.
Currently, the base64 image that I get from the canvas is blank.
I have found similar questions for example this one:
HTML Canvas image to Base64 problem
However, the issue that I have is that because I am adding 2 images in the canvas, I cannot use the example provided in those answers as most of them are using single image.
I understand that I have to wait until the canvas image is loaded properly before trying to get the base64 image. But I don't know how in my case.
This is my code:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.canvas.width = 1000;
context.canvas.height = 1000;
var imageObj1 = new Image();
imageObj1.src = "http://www.offthegridnews.com/wp-content/uploads/2017/01/selfie-psuDOTedu.jpg";
imageObj1.onload = function() {
context.drawImage(imageObj1, 0, 180, canvas.width, canvas.height);
};
var imageObj2 = new Image();
imageObj2.src = "http://www.publicdomainpictures.net/pictures/150000/velka/banner-header-tapete-145002399028x.jpg"
imageObj2.onload = function() {
context.drawImage(imageObj2, 0, 0, canvas.width, 180);
};
// get png data url
//var pngUrl = canvas.toDataURL();
var pngUrl = canvas.toDataURL('image/png');
// get jpeg data url
var jpegUrl = canvas.toDataURL('image/jpeg');
$('#base').val(pngUrl);
<div class="contents" style="overflow-x:hidden; overflow-y:scroll;">
<div style="width:100%; height:90%;">
<canvas id="myCanvas" class="snap" style="width:100%; height:100%;" onclick="takephoto()"></canvas>
</div>
</div>
<p>
This is the base64 image
</p>
<textarea id="base">
</textarea>
and this is a working FIDDLE:
https://jsfiddle.net/3p3e6Ldu/1/
Can someone please advice on this issue?
Thanks in advance.
EDIT:
As suggested in the comments bellow, i tried to use a counter and when the counter reaches a specific number, I convert the canvas to base64.
Like so:https://jsfiddle.net/3p3e6Ldu/4/
In both your examples (the one from the question and the one from the comments), the order of commands does not really respect the async nature of the task at hand.
In your later example, you should put the if( count == 2 ) block inside the onload callbacks to make it work.
However, even then you will run into the next problem: You are loading the images from different domains. You can still draw them (either into the canvas or using an <img> tag), but you are not able to access their contents directly. Not even with the detour of using the <canvas> element.
I changed to code so it would work, if the images are hosted on the same domain. I also used a function to load the image and promises to handle the callbacks. The direct way of using callbacks and a counting variable, seem error-prone to me. If you check out the respective fiddle, you will notice the SecurityError shown. This is the result of the aforementioned problem with the Same-Origin-Policy I mentioned.
A previous question of mine in a similar direction was about how to detect, if I can still read the contents of a <canvas> after adding some images.
const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');
context.canvas.width = 1000;
context.canvas.height = 1000;
// function to retrieve an image
function loadImage(url) {
return new Promise((fulfill, reject) => {
let imageObj = new Image();
imageObj.onload = () => fulfill(imageObj);
imageObj.src = url;
});
}
// get images
Promise.all([
loadImage("http://www.offthegridnews.com/wp-content/uploads/2017/01/selfie-psuDOTedu.jpg"),
loadImage("http://www.publicdomainpictures.net/pictures/150000/velka/banner-header-tapete-145002399028x.jpg"),
])
.then((images) => {
// draw images to canvas
context.drawImage(images[0], 0, 180, canvas.width, canvas.height);
context.drawImage(images[1], 0, 0, canvas.width, 180);
// export to png/jpg
const pngUrl = canvas.toDataURL('image/png');
const jpegUrl = canvas.toDataURL('image/jpeg');
// show in textarea
$('#base').val(pngUrl);
})
.catch( (e) => alert(e) );

Javascript: Simulate cropping and get file image fro memory

I upload an image using a file input:
<input type="file" id="product_images-0"/>
Then I get the file with javascript:
var element = document.getElementById('product_images-1');
console.log(element.files[0]);
I want to get the image file from memory, show it to user, simulate an image crop to get the coordinates and show the "cropped-simulated" image to the user, by using css transform/translate.
After cropping if the user doesn't like the image can remove it.
It is possible. I don't want to use Ajax because the user can remove an image before submits, and deleting this "remove" image from backend is hard to do it.
Please see simple proof-of-concept below. Once file is submitted you need to create new HTMLImageElement and append it to your preview. Then you can draw cropped image on canvas using drawImage.
Remember to validate if file you are submitting is image type!
var fileInput = document.getElementById('product_images-0');
var preview = document.querySelector('div.preview');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
fileInput.onchange = function (event) {
var curFiles = fileInput.files;
if (curFiles.length > 0) {
while (preview.firstChild) {
preview.removeChild(preview.firstChild);
}
var image = document.createElement('img');
image.src = window.URL.createObjectURL(curFiles[0]);
preview.appendChild(image);
image.onload = function () {
ctx.drawImage(image, 0.2 * image.width, 0.2 * image.height, 0.5 * image.width, 0.5 * image.height, 0, 0, canvas.width, canvas.height);
};
}
};
<input type="file" id="product_images-0"/>
<div class="preview">
<p>No files currently selected for upload</p>
</div>
<canvas id="canvas" width="200" height="200"></canvas>

save and display image captured from input type=file

I have a webpage with capture image from the Available camera feature. For the web version it simply places the video capture onto a canvas. However for a phone, I am using <input class="btn btn-info" type="file" accept="image/*" id="cameraCapture" capture="camera"> to capture a picture. It asks the user to either capture the image using the phone's camera or upload from its filesystem/gallery etc. Once the image is clicked it simply places the image's name next to the button.
Is there a way to access this image and display it in the same page.
Thanks in advance
You can do that with JS using FileReader object.
Take a look at this answer: preview-an-image-before-it-is-uploaded
I hope it helps
var input = document.querySelector('input[type=file]');
input.onchange = function () {
var file = input.files[0];
drawOnCanvas(file);
};
function drawOnCanvas(file) {
var reader = new FileReader();
reader.onload = function (e) {
var dataURL = e.target.result,
c = document.querySelector('canvas'),
ctx = c.getContext('2d'),
img = new Image();
img.onload = function() {
c.width = img.width;
c.height = img.height;
ctx.drawImage(img, 0, 0);
};
img.src = dataURL;
};
reader.readAsDataURL(file);
}

Categories

Resources