8x8 pixel painter
by Eugene Trounev
HTML
<link rel="stylesheet" href="https://unpkg.com/sanitize.css">
<link rel="stylesheet" href="https://unpkg.com/nes.css@latest/css/nes.min.css">
<main class="row">
<section class="sidebar column">
<div class="toolbar row">
<button type="button" class="nes-text" onclick="newFrame()">New</button>
<button type="button" class="nes-text" onclick="newFrame()">Preview</button>
</div>
<div class="nes-container with-title is-rounded is-dark grow">
<p class="title">Frames</p>
<div class="frames row">
</div>
</div>
<div class="nes-container with-title is-rounded is-dark grow">
<p class="title">Sprites</p>
<div class="sprites row">
</div>
</div>
</section>
<section class="preview grow column">
<div class="toolbar row">
<button type="button" class="nes-text" onclick="onSaveNewFrame()">Save as new</button>
<button type="button" class="nes-text is-warning" onclick="document.getElementById('confirm-save-over').showModal();">Save over</button>
<span class="grow"></span>
<button type="button" class="nes-text is-error" onclick="document.getElementById('confirm-clear-canvas').showModal();">Clear canvas</button>
</div>
<div class="nes-container is-rounded is-dark grow desktop">
<div class="canvas">
</div>
</div>
<div class="toolbar row">
<button type="button" class="nes-text">Edit color</button>
</div>
<div class="nes-container is-rounded is-dark palette">
</div>
</section>
</main>
<dialog class="nes-dialog is-rounded is-dark" id="confirm-clear-canvas">
<form method="dialog">
<p class="title">Clear canvas</p>
<p>Are yousure want to clear canvas?</p>
<menu class="dialog-menu">
<button class="nes-btn">Cancel</button>
<button class="nes-btn is-error" onclick="onClearCanvasClick()">Yes, clear</button>
</menu>
</form>
</dialog>
<dialog class="nes-dialog is-rounded is-dark" id="confirm-save-over">
...
CSS
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
html,
body {
margin: 0;
padding: 0;
color: #fff;
background-color: #212529;
}
html,
body,
pre,
code,
kbd,
samp {
font-family: 'Press Start 2P', cursive;
}
main,
section.sidebar,
section.preview {
height: 100vh;
}
.row {
display: flex;
flex-direction: row;
}
.column {
display: flex;
flex-direction: column;
}
.grow {
flex-grow: 1;
}
main,
section.sidebar,
section.preview,
.desktop,
.palette {
margin: 0 !important;
}
section.sidebar {
width: 272px;
}
.frames,
.sprites {
flex-wrap: wrap;
gap: 8px;
}
.frames .cell,
.sprites .cell {
pointer-events: none;
}
.frames .frame,
.sprites .sprite {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
gap: 0px 0px;
width: calc(8 * 8px);
height: calc(8 * 8px);
border: 2px dashed transparent;
}
.frame.selected,
.sprite.selected {
border-color: cyan;
}
section.preview {}
.toolbar {
column-gap: 1em;
padding: 0.5em 1em;
}
.toolbar button {
background: none;
border: none;
color: inherit;
}
.desktop {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
}
.canvas {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
gap: 0px 0px;
width: calc(8 * 64px);
height: calc(8 * 64px);
}
.canvas .cell {
border: 2px dashed transparent;
}
.canvas .cell:hover {
border-color: #fff;
}
.palette {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 0px 0px;
width: 100%;
height: calc(2 * 64px);
margin-top: 1em;
}
.palette .cell {
border: 2px dashed transparent;
}
.palette .cell.selected {
border-color: cyan;
}
JavaScript
var _frame = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
];
var palette = ["#000000", "#F4CCA8", "#FF87AF", "#877AA6", "#62A9D7", "#907038", "#582D04", "#006411", "#1FB914", "#00AAE8", "#0000D4", "#4700A4", "#F00884", "#DC0808", "#FCF404", "#FFFFFF"];
var palette_index = 0;
var frames = [];
var frame_index = 0;
var sprites = [];
var sprite_index = 0;
var canvas = [
..._frame
];
const updatePalette = () => {
const $palette = document.querySelector(".palette");
palette.forEach((c, i) => {
const $el = $palette.children[i];
$el.style.background = c;
});
}
const updateCanvas = () => {
const $canvas = document.querySelector(".canvas");
canvas.forEach((v, i) => {
const $el = $canvas.children[i];
$el.style.background = palette[v];
});
}
const newFrame = (data = _frame) => {
const $frames = document.querySelector(".frames");
frames.push([...data]);
const $frame = document.createElement("div");
$frame.addEventListener("click", onFrameClick(frames.length - 1));
$frame.classList.add('frame');
for (let i = 0; i < 64; i++) {
const $el = document.createElement("div");
$el.classList.add("cell");
$frame.appendChild($el);
}
$frames.appendChild($frame);
updateFrame(frames.length - 1);
}
const updateFrame = (index) => {
const $frames = document.querySelector(".frames");
const $frame = $frames.children[index];
const frameData = frames[index];
frameData.forEach((v, i) => {
const $el = $frame.children[i];
$el.style.background = palette[v];
});
}
const updateFrameSlection = () => {
const $frames = document.querySelector(".frames");
document.querySelector(".frames .selected").classList.remove("selected");
$frames.children[frame_index].classList.add("selected");
}
const newSprite = () => {
const $sprites =...