Paint
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta author="MiduDev" /> <meta url="midudev" /> <meta author="ShunTrDev" /> <title>Paint.js</title> <style> *, *::before, *::after { box-sizing: border-box; }
body { font-family: 'Arial', sans-serif; background: #222; padding: 16px; max-width: 500px; margin: 0 auto; }
h1 { color: #fce300; font-size: 12px; font-weight: 600;
display: flex; justify-content: center; align-items: center; gap: 4px;
img { width: 16px; height: auto; } }
canvas { background: #fff; }
header { grid-area: header; background: silver; padding: 2px;
button { border: 0; background: transparent;
&:hover { box-shadow: 1px 1px black, inset -1px -1px gray, inset 1px 1px white; } } }
main { grid-area: main; padding: 16px; }
footer { grid-area: footer; background: silver; padding: 4px; }
#container { border: 1px solid #808080; display: grid; grid-template-areas: 'header header header' 'aside main main' 'footer footer footer'; }
aside { background: silver; grid-area: aside; width: 52px; padding-top: 2px;
nav { display: flex; flex-wrap: wrap; gap: 1px; justify-content: center; }
button { width: 24px; height: 24px;
background: #bdbdbd url('./icons/draw.png') no-repeat center;
border: 1.5px solid #eee; border-right-color: #000; border-bottom-color: #000;
image-rendering: pixelated;
&.active { background-color: #eee; border-color: #000; border-right-color: #eee; border-bottom-color: #eee; }
&[disabled] { pointer-events: none; opacity: 0.5; } } }
#erase-btn { background-image: url('./icons/erase.png'); }
#rectangle-btn { background-image: url('./icons/rectangle.png'); }
#ellipse-btn { background-image: url('./icons/ellipse.png'); }
#picker-btn { background-image: url('./icons/picker.png'); }
#clear-btn { background-image: url('./icons/trash.png'); background-size: 18px; } </style> <script type="module"> // CONSTANTS const MODES = { DRAW: 'draw', ERASE: 'erase', RECTANGLE: 'rectangle', ELLIPSE: 'ellipse', PICKER: 'picker', }
// UTILITIES const $ = (selector) => document.querySelector(selector) const $$ = (selector) => document.querySelectorAll(selector)
// ELEMENTS const $canvas = $('#canvas') const $colorPicker = $('#color-picker') const $clearBtn = $('#clear-btn') const $drawBtn = $('#draw-btn') const $eraseBtn = $('#erase-btn') const $rectangleBtn = $('#rectangle-btn') const $pickerBtn = $('#picker-btn')
const ctx = $canvas.getContext('2d')
// STATE let isDrawing = false let isShiftPressed = false let startX, startY let lastX = 0 let lastY = 0 let mode = MODES.DRAW let imageData
// EVENTS $canvas.addEventListener('mousedown', startDrawing) $canvas.addEventListener('mousemove', draw) $canvas.addEventListener('mouseup', stopDrawing) $canvas.addEventListener('mouseleave', stopDrawing)
$colorPicker.addEventListener('change', handleChangeColor) $clearBtn.addEventListener('click', clearCanvas)
document.addEventListener('keydown', handleKeyDown) document.addEventListener('keyup', handleKeyUp)
$pickerBtn.addEventListener('click', () => { setMode(MODES.PICKER) })
$eraseBtn.addEventListener('click', () => { setMode(MODES.ERASE) })
$rectangleBtn.addEventListener('click', () => { setMode(MODES.RECTANGLE) })
$drawBtn.addEventListener('click', () => { setMode(MODES.DRAW) })
// METHODS function startDrawing(event) { isDrawing = true
const { offsetX, offsetY } = event
// guardar las coordenadas iniciales ;[startX, startY] = [offsetX, offsetY] ;[lastX, lastY] = [offsetX, offsetY]
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) }
function draw(event) { if (!isDrawing) return
const { offsetX, offsetY } = event
if (mode === MODES.DRAW || mode === MODES.ERASE) { // comenzar un trazado ctx.beginPath()
// mover el trazado a las coordenadas actuales ctx.moveTo(lastX, lastY)
// dibujar una línea entre coordenadas actuales y las nuevas ctx.lineTo(offsetX, offsetY)
ctx.stroke()
// actualizar la última coordenada utilizada ;[lastX, lastY] = [offsetX, offsetY]
return }
if (mode === MODES.RECTANGLE) { ctx.putImageData(imageData, 0, 0)
// startX -> coordenada inicial del click let width = offsetX - startX let height = offsetY - startY
if (isShiftPressed) { const sideLength = Math.min(Math.abs(width), Math.abs(height))
width = width > 0 ? sideLength : -sideLength height = height > 0 ? sideLength : -sideLength }
ctx.beginPath() ctx.rect(startX, startY, width, height) ctx.stroke() return } }
function stopDrawing(event) { isDrawing = false }
function handleChangeColor() { const { value } = $colorPicker ctx.strokeStyle = value }
function clearCanvas() { // también os ayudaría a limpiar parte del canvas // con la herramienta de selección ctx.clearRect(0, 0, canvas.width, canvas.height) }
async function setMode(newMode) { let previousMode = mode mode = newMode // para limpiar el botón activo actual $('button.active')?.classList.remove('active')
if (mode === MODES.DRAW) { $drawBtn.classList.add('active') canvas.style.cursor = 'crosshair' ctx.globalCompositeOperation = 'source-over' ctx.lineWidth = 2 return }
if (mode === MODES.RECTANGLE) { $rectangleBtn.classList.add('active') canvas.style.cursor = 'nw-resize' ctx.globalCompositeOperation = 'source-over' ctx.lineWidth = 2 return }
if (mode === MODES.ERASE) { $eraseBtn.classList.add('active') canvas.style.cursor = 'url("./cursors/erase.png") 0 24, auto' ctx.globalCompositeOperation = 'destination-out' ctx.lineWidth = 20 return }
if (mode === MODES.PICKER) { $pickerBtn.classList.add('active') const eyeDropper = new window.EyeDropper()
try { const result = await eyeDropper.open() const { sRGBHex } = result ctx.strokeStyle = sRGBHex $colorPicker.value = sRGBHex setMode(previousMode) } catch (e) { // si ha habido un error o el usuario no ha recuperado ningún color }
return } }
function handleKeyDown({ key }) { isShiftPressed = key === 'Shift' }
function handleKeyUp({ key }) { if (key === 'Shift') isShiftPressed = false }
// INIT setMode(MODES.DRAW)
// Show Picker if browser has support if (typeof window.EyeDropper !== 'undefined') { $pickerBtn.removeAttribute('disabled') }
ctx.lineJoin = 'round' ctx.lineCap = 'round' </script> </head>
<body> <h1> <img src="./icon.png" alt="Paint.js" /> Paint.js </h1>
<div id="container"> <header> <button>File</button> <button>Edit</button> <button>View</button> <button>Image</button> <button>Options</button> <button>Help</button> </header>
<aside> <nav> <button id="draw-btn" title="Pincel"></button> <button id="erase-btn" title="Borrar"></button> <button id="rectangle-btn" title="Rectángulo"></button> <button id="ellipse-btn" title="Elipse"></button> <button disabled id="picker-btn" title="Capturar Color"></button> <button id="clear-btn" title="Limpiar dibujo"></button> </nav> </aside>
<main> <canvas id="canvas" width="300" height="200"></canvas> </main>
<footer> <input type="color" id="color-picker" value="#000000" /> </footer> </div> </body></html>