DEV Community

Cover image for CSS Pixel Art Generator
Johnny Simpson
Johnny Simpson

Posted on • Originally published at fjolt.com

CSS Pixel Art Generator

Recently, I read an article outlining how to create CSS pixel art with box shadows. I thought it was a really cool idea - and thought it'd be interesting to make a generator with Javascript to let you create exportable pixel art from the browser using this effect. In the Codepen demo (shown below), you can draw your pixel creations, and then click "Generate CSS" to get your pixel art in CSS, which you can copy into your web pages. 

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);
Enter fullscreen mode Exit fullscreen mode

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:

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)`;
document.getElementById('pixel-art-options').style.width = `calc(${(0.825 * config.width)}rem + ${(config.height * 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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 = `#101532`;
        } 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;
});
Enter fullscreen mode Exit fullscreen mode

Finally, we set up a few events on the colors and eraser, so we can track what 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').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').classList.remove('current');
    })
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. Article on CSS Pixel Art
  2. Source Code on Code Pen
  3. Source Code on Github
  4. Original Article

Top comments (7)

Collapse
 
noahcalvo profile image
noahcalvo

Hi Johnny. This is super awesome, good work on this project. Do you know of any easy way to add save functionality to the pixel canvas? To save it as an image, or something easier to save in a database than raw css?

Collapse
 
smpnjn profile image
Johnny Simpson

If you were going to do that, I think it'd be best to draw the output of the pixel generator onto a canvas, and then write that to a png file or something similar.

Collapse
 
noahcalvo profile image
noahcalvo

Yes that's a good tip I will try it out. Also I'd like to implement your component in a pixel art sharing site I'm creating for a college class. Do you mind that?

Collapse
 
mustapha profile image
Mustapha Aouas • Edited

Amazing 👍🏻
Do you mind if I take the idea and do the same using angular ?

Collapse
 
smpnjn profile image
Johnny Simpson

go for it

Collapse
 
mustapha profile image
Mustapha Aouas

Great !

Collapse
 
jh3y profile image
Jhey Tompkins • Edited

Always a fun project 👏
Here's one I made last year with React & Canvas: pxl.netlify.app/