0
\$\begingroup\$

Just for fun I wrote some JavaScript that "converts" an image into pure HTML / CSS. The user is able to select a local image. This image is loaded into an invisible canvas in order to extract pixeldata from the image. Using the pixeldata, a structure of 1x1 <div/> elements is created with each element having its background color set to its corresponding pixel color of the image.

The script works perfectly fine on small (width / height) images, but kills the browser on any large image file. I was wondering if there are some things I can do within my code to solve this issue?

document.addEventListener("DOMContentLoaded", function(event) {
	const file = document.getElementById('file');

	file.addEventListener('change', handleFile);

	function handleFile(e){
		// Create canvas
		let canvas = document.createElement('canvas');
		let ctx = canvas.getContext('2d');
		
		// Load image
		let img = new Image;
		img.src = URL.createObjectURL(e.target.files[0]);
		img.onload = function() {
			// Draw image to canvas
			ctx.drawImage(img, 0, 0, img.width, img.height);
			
			let container = document.createElement('div');
			container.id = 'container';
			container.style.Width = img.width;
			container.style.Height = img.height;

			let pixelData = ctx.getImageData(0, 0, img.width, img.height).data;
			let pixel = 0;

			// Loop through each row
			for(let x = 0; x < img.height; x++){
				let row = document.createElement('div');
				row.className = 'row';

				// Loop through each column
				for(let y = 0; y < img.width; y++){
					let col = document.createElement('div');
					col.className = 'col';

					col.style.cssText = 'background: rgba('+pixelData[pixel]+','+pixelData[pixel+1]+','+pixelData[pixel+2]+','+pixelData[pixel+3]+');';
					row.appendChild(col);
			    	pixel = pixel + 4;
				}
				container.appendChild(row);
			}

			document.getElementById('body').appendChild(container);

			URL.revokeObjectURL(img.src);
		}
	}
});
#container {
	margin-left: 50px;
	margin-top: 50px;
}
.row {
	overflow: auto;
}
.row {
	height: 1px;
}
.col {
	width: 1px;
	height: 1px;
	float:left;
}
<body id='body'>
	<input type='file' id='file'/>
</body>

\$\endgroup\$
  • \$\begingroup\$ What do you mean by "pure HTML / CSS"? The Image object you create at the very beginning (the one whose src property you set to img.src = URL.createObjectURL(e.target.files[0]);) is a perfectly valid HTMLElement that you can insert into a HTML document. \$\endgroup\$ – Máté Safranka Oct 15 '18 at 14:01
  • \$\begingroup\$ @MátéSafranka For each pixel in the image, a <div/> element is added to the DOM with its background color set to the color of the pixel. So in the end it looks like the image in the browser, but in facts it's just a whole bunch of div elements. \$\endgroup\$ – icecub Oct 15 '18 at 15:10
0
\$\begingroup\$

After working on this for a while with other developers on Stack Overflow chat, the best solution I came up with (so far) was to use an asynchronous loop and adding nodes to a Document Fragment instead of adding them directly to the DOM.

The result:

document.addEventListener("DOMContentLoaded", function(event) {
	const file = document.getElementById('file');

	file.addEventListener('change', handleFile);

	function handleFile(e){
		document.getElementById('container').innerHTML = '';
		
		// Load image
		let img = new Image;
		img.src = URL.createObjectURL(e.target.files[0]);
		img.onload = function() {
			// Create canvas
			let canvas = document.createElement('canvas');
			let ctx = canvas.getContext('2d');
			canvas.width = img.width;
			canvas.height = img.height;

			// Draw image to canvas
			ctx.drawImage(img, 0, 0, img.width, img.height);
			
			let container = document.getElementById('container');
			container.style.width = img.width+'px';
			container.style.height = img.height+'px';

			let pixelData = ctx.getImageData(0, 0, img.width, img.height).data;
			let pixel = 0;
			let processedPixels = 0;
			let total = img.height*img.width;

			(function _asyncLoop(){
				let row = document.createElement('div');
				let fragment = document.createDocumentFragment();
				row.className = 'row';

				let rowPixels = 0;

				do {
					let col = document.createElement('div');
					col.className = 'col';

					col.style.cssText = 'background: rgba('+pixelData[pixel]+','+pixelData[pixel+1]+','+pixelData[pixel+2]+','+pixelData[pixel+3]+');';
					fragment.appendChild(col);
					processedPixels++;
					rowPixels++;
					pixel = pixel + 4;
				} while(rowPixels < img.width);

				row.appendChild(fragment);
				document.getElementById('container').appendChild(row);

				if( processedPixels < total ) setTimeout( _asyncLoop, 100 );
			}());

			URL.revokeObjectURL(img.src);
		}
	}
});
#container {
	margin-left: 50px;
	margin-top: 50px;
}
.row {
	overflow: auto;
}
.row {
	height: 1px;
}
.col {
	width: 1px;
	height: 1px;
	float:left;
}
<body>
	<p>Select an image from your computer and allow Javascript to draw it in the DOM using only DIV elements!</p>
	<input type='file' id='file'/>
	<div id='container'></div>
</body>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.