CSS Pixel Art Generator
📣 Sponsor
Welcome to the CSS pixel art generator - written in Javascript, and inspired by this article. Draw your pixel creations below, and then click "Generate CSS" to get your pixel art in CSS, which you can copy into your web pages.
You can upload files by clicking "Select a File". The pixel grid is 40x40, so ensure your files are 40x40px or smaller.
Select Color:
Pixel Art Code
Copy the code below to use this on your webpage
How it works
As described in the article, we use scaled up box shadows to act as pixels on the screen. We can scale these up and down to make our pixel art bigger or smaller.
Since each box shadow is 1px by 1px, we can create a piece of pixel art where every "pixel" is 1x1. If we want each pixel to be 20x20, we would simply use transform
to scale it by 20x:
transform: scale(20);
To achieve the effect we are after, we then use Javascript to create a UI which lets us draw our pixel art creations. The code for the UI can be found on codepen here., or if you like, find it below:
See the Pen CSS Pixel Art Generator by smpnjn (@smpnjn) on CodePen.
Overview of Javascript
To get this all to work, we have to use Javascript. The first step was generating a grid of pixels using a simple loop:
let config = {
width: 40,
height: 40,
color: 'white',
drawing: true,
eraser: false
}
let events = {
mousedown: false
}
document.getElementById('pixel-art-area').style.width = `calc(${(0.825 * config.width)}rem + ${(config.height * 2)}px)`;
document.getElementById('pixel-art-area').style.height = `calc(${(0.825 * config.height)}rem + ${(config.width * 2)}px)`;
for(let i = 0; i < config.width; ++i) {
for(let j = 0; j < config.height; ++j) {
let createEl = document.createElement('div');
createEl.classList.add('pixel');
createEl.setAttribute('data-x-coordinate', j);
createEl.setAttribute('data-y-coordinate', i);
document.getElementById('pixel-art-area').appendChild(createEl);
}
}
This ultimately creates about 40x40 pixels, or 1600 new HTML elements. You can easily scale this up for bigger experiments, but 40x40 works fine.
Tracking a users mouse movements
We can then track a user's mouse movements with three events: pointerdown, pointermove and pointerup. Since we have to apply this to all pixels, we use a loop to loop over each pixel to add the event.
Then, if a user continues to hold down, we can track which pixel they are over using e.target, which returns the current HTML entity which is being hovered over on pointermove. If they are using the eraser, we can take that into consideration here.
document.querySelectorAll('.pixel').forEach(function(item) {
item.addEventListener('pointerdown', function(e) {
if(config.eraser === true) {
item.setAttribute('data-color', null);
item.style.background = `#191f2b`;
} else {
item.setAttribute('data-color', config.color);
item.style.background = `${config.color}`;
}
events.mousedown = true;
});
});
document.getElementById('pixel-art-area').addEventListener('pointermove', function(e) {
if(config.drawing === true && events.mousedown === true || config.eraser === true && events.mousedown === true) {
if(e.target.matches('.pixel')) {
if(config.eraser === true) {
e.target.setAttribute('data-color', null);
e.target.style.background = `#101532`;
} else {
e.target.setAttribute('data-color', config.color);
e.target.style.background = `${config.color}`;
}
}
}
});
document.body.addEventListener('pointerup', function(e) {
events.mousedown = false;
});
File Uploads
When you upload an image to the tool, we call an event attached to the file upload input and put the image contents on a canvas. The canvas we paint the image on is hidden, so you can't see it - but now we can do things with it in Javascript.
Once it's been added to that canvas, we can sample each pixel using getImageData()
. We run through every pixel (40x40 as our pixel art generator is 40x40), and sample the color at each point. If there are more than 40x40 pixels, they just get ignored - so ensure your files are less than 40x40 in size.
In the code below, pixel data is returned in in R, G, B, A format. We store it in the pixelData
variable. pixelData[3]
is "A", and pixelData[0]
to [2]
are R, G, and B. Using this information, we can figure out the RGB color of each pixel, and ignore transparent pixels where A = 0.
// Attach the event to our input
document.querySelector('.select-file').addEventListener('change', function(e) {
let files = e.target.files;
// Get the files from the input that have been uploaded
let f = files[0];
let reader = new FileReader();
// Hide any errors
document.querySelector('.error').classList.remove('active');
reader.onload = (async function(file) {
// We only accept images.. so check the image type
if(file.type == "image/png" || file.type == "image/jpg" || file.type == "image/gif" || file.type == "image/jpeg") {
// create an image from our file
const bitmap = await createImageBitmap(file);
const canvas = document.querySelector("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
const ctx = canvas.getContext("2d");
// Clear any previous image.. just in case
ctx.clearRect(0, 0, 9999, 9999);
// And paint the new element onto the canvas
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
let constructPixelData = []
// Then run through each of our pixels and get the color at each point
for(let i = 0; i < config.width; ++i) {
for(let j = 0; j < config.height; ++j) {
let pixelData = canvas.getContext('2d').getImageData(i, j, 1, 1).data;
// pixel data is returned in in R, G, B, A format. pixelData[3] is "A", and pixelData[0] to [2] are R, G, and B.
// If pixelData[3] is 0, then opacity at that pixel is 0. So we won't show it.
if(pixelData[3] !== 0) {
// Put it into an array containing x, y, and color
constructPixelData.push({ x: i, y: j, color: `rgb(${pixelData[0]} ${pixelData[1]} ${pixelData[2]})`});
}
}
}
// And then update the pixel art generator to display this information.
constructPixelData.forEach(function(i) {
let getPixel = document.querySelector(`.pixel[data-x-coordinate="${i.x}"][data-y-coordinate="${i.y}"]`);
if(getPixel !== null) {
getPixel.setAttribute('data-color', i.color);
getPixel.style.background = i.color;
}
});
}
else {
document.querySelector('.error').textContent = 'Please select a png, jpg or gif file to upload.';
document.querySelector('.error').classList.add('active');
}
})(f);
});
Finally, we set up a few events on the colors and eraser, so we can track which tool and color is being selected:
[ 'click', 'input' ].forEach(function(item) {
document.querySelector('.color-picker').addEventListener(item, function() {
config.color = this.value;
document.querySelectorAll('.colors > div').forEach(function(i) {
i.classList.remove('current');
});
this.classList.add('current');
config.eraser = false;
document.querySelector('.eraser-container').classList.remove('current');
});
});
document.querySelectorAll('.colors > div').forEach(function(item) {
item.addEventListener('click', function(e) {
document.querySelector('.color-picker').classList.remove('current');
document.querySelectorAll('.colors > div').forEach(function(i) {
i.classList.remove('current');
})
item.classList.add('current');
config.eraser = false;
config.color = `${item.getAttribute('data-color')}`;
document.querySelector('.eraser-container').classList.remove('current');
})
});
Conclusion
When I saw the original article, I thought it was really cool to create pixel art with just CSS - but it would be even cooler to create a way to export pixel art creations - and it wasn't so hard with just a little bit of Javascript. Here are some useful links to the source code:
More Tips and Tricks for CSS
- A Guide to CSS Selectors
- Parent Selection in CSS: how the CSS has selector works
- How to disable text selection in CSS
- CSS Individual Transform Properties
- A Complete Guide to How the CSS not Selector Works
- CSS Transformations
- Updating CSS Variables with Javascript
- CSS Transitions
- CSS Colors
- CSS Positioning