At the moment, you are doing the following for every pixel
Create canvas
get the context and draw to the image
get the context and get the pixel data for one pixel
create a DIV
add it to the DOM
Now, lets streamline this
The following can be done ONCE
create the canvas
get the context
use the context to draw the image
use the context to get the image data
create an empty string
Now, for each pixel
get the pixel data
add the html for the div to the string
And finally, just ONCE
Add the string with all the divs to the DOM
Something like:
const componentToHex = c => c.toString(16).padStart(2, '0');
const rgbToHex = (r, g, b) => `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
const img = document.getElementById('my-image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const w = canvas.width = img.width;
const h = canvas.height = img.height;
context.drawImage(img, 0, 0, img.width, img.height);
const imageData = context.getImageData(0, 0, w, h).data;
const pixel = (x, y) => {
const index = (w * y + x) * 4;
return rgbToHex(imageData[index], imageData[index + 1], imageData[index + 2]);
}
const df = document.createDocumentFragment();
for (let y = 0; y < img.height; y++) {
for (let x = 0; x < img.width; x++) {
const div = df.appendChild(document.createElement('div'));
div.style.top = y + "px";
div.style.left = x + "px";
div.style.backgroundColor = pixel(x, y);
}
}
document.body.appendChild(df);
Note: it may not be the case now, but such a loop may work faster inside a function – often loops in the global context are slower
So, you could wrap the whole code above in
(() => {
// the code from above
})();
And see significant improvement again – not sure, used to be the case in years gone by
changed to use document fragment for a further 25% speed improvement
Now takes 1.4 seconds in firefox for a 640×480 image, 2.3 seconds in chrome – which didn’t really see a big difference between using insertAdjacentHTML vs a document fragment
another thing to note. In Firefox the page becomes sluggish, in chrome, for 640×480, no such issue
Problem :
Before you go off saying I’m crazy, believe me, I know. I’m not going for a website that renders fast, loads fast, or gets a high lighthouse score. I just want it to work.
I have some javascript that picks up all the pixel colors of an image. With this function, I create a div element that is 1px by 1px and set the background color to the pixel color of those same coordinates. Then the coordinates are used to set the top and left values. My code does what it’s told.
Here’s my problem, my image is 700px by 387px. If you do the math, that works out to 270,900 html elements. Chrome, simply isn’t built for this madness. I want to see this work, I want to “manually” create an image with div elements, somehow. My cpu maxes out when I try to do so, and I’m sure I’d run out of ram eventually.
Everything works fine if I only try hundreds or a few thousand pixels, but any more, and chrome dies. I’m not sure if it’s calculating in the browser that may be my problem, or if chrome cant display this many elements, or both. I suppose I could do the same math on my server with python, and append it to html, but then chrome probably couldn’t display it.
Obviously, this isn’t super important, just fun. I think the community will enjoy the challenge as well.
Here’s calculating 100 pixels:
onload = e => {
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
function img(x, y) {
var img = document.getElementById('my-image');
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var pixelData = canvas.getContext('2d').getImageData(x, y, 1, 1).data;
return rgbToHex(pixelData[0], pixelData[1], pixelData[2]);
}
//x = 700 y = 387
for (var x = 0; x < 10; x++) {
for (var y = 0; y < 10; y++) {
document.body.insertAdjacentHTML("beforeend", "<div style='top:" + y + "px; left:" + x + "px;background:" + img(x, y) + ";' />");
}
}
};
div {
position: absolute;
width: 1px;
height: 1px;
}
Cross orgin policies in most browsers will prevent the image from being sampled in the code snippets. You can disable them or try a different image. The data url for said images were too long but I really wanted to keep them to show the scale of the problem.
Comment posted by JLRishe
You haven’t explained why you’re trying to do this. Is this just an experiment in using DIVs to display the individual pixels of an image, or is it perhaps an XY problem?
Comment posted by Jaromanda X
erm, you’re creating a new canvas and drawing the full image for every pixel? That’s wasteful – don’t do that
Comment posted by Onur Gelmez
You can use web workers for reduce the time
Comment posted by ilsloaoycd
@JaromandaX haha yeah it was late. Definitely wasn’t the smartest choice on my part.
Comment posted by Kaiido
That’s a very good point, unfortunately, even with this huge improvement, I fear there will still be only a few devices able to render that many elements in one go. (Currently waiting for my FF to unfreeze…)
using insertAdjacentHTML is slower than using document fragment … 1.8 as opposed to 1.4 seconds
Comment posted by jsfiddle.net/c8ownh1y
@Kaiido – using insertAdjacentHTML will probably do that, in fact even doing document fragment line by line is just as slow (if not slower, oddly enough) – doing it in one chunk seems to be fine –
Comment posted by Jaromanda X
by the way, the code always runs in about 1.2 to 1.4 seconds on my machine … but sometimes the image doesn’t display until about a second later … guess the rendering engine is working overtime :p