Animation App — v1.4
by Sharxxy
HTML
<div class="app">
<div class="panel toolbar">
<h3 style="margin:0 0 8px 0">Animator v1.4 — brush + export</h3>
<div>
<label>Tool</label>
<div class="tools-row">
<button id="pencilBtn" class="small">Brush</button>
<button id="bucketBtn" class="small">Bucket</button>
<button id="eraserBtn" class="small">Eraser</button>
<button id="textBtn" class="small">Text</button>
<button id="undoBtn" class="small">Undo</button>
<button id="clearBtn" class="small">Clear</button>
</div>
</div>
<div>
<label>Brush Type</label>
<select id="brushType">
<option value="hard">Hard round</option>
<option value="ink">Ink</option>
<option value="air">Airbrush</option>
<option value="charcoal">Textured charcoal</option>
<option value="pattern">Textured pattern</option>
<option value="shadow">EFX — Shadow</option>
<option value="glow">EFX — Glow</option>
<option value="blur">EFX — Blur</option>
</select>
</div>
<div>
<label>Size & Opacity</label>
<input id="size" type="range" min="1" max="120" value="16">
<input id="opacity" type="range" min="0.05" max="1" step="0.05" value="0.95">
</div>
<div>
<label>Color</label>
<input id="color" class="color-input" type="color" value="#0b0b0b">
<label style="margin-top:6px">Pattern (for textured pattern brush)</label>
<input id="patternURL" placeholder="image URL (optional)" style="width:100%">
</div>
<div>
<label>Smoothing / Pressure sim</label>
<div class="tools-row">
<input id="pressure" type="checkbox"> <label style="display:inline-block; margin:0 8px 0 4px">Pressure sim</label>
<input id="smoothing" type="checkbox" checked> <label style="display:inline-block; margin:0 8px 0 4px">Smoothing</label>
</div>
</div>
<div>
...
CSS
:root { --bg:#0f1720; --panel:#0b1220; --muted:#9aa8b2; --accent:#7dd3fc; }
html,body{height:100%;margin:0;font-family:Inter,system-ui,Segoe UI,Roboto,'Helvetica Neue';background:var(--bg);color:#e6eef6}
.app {display:grid;grid-template-columns:320px 1fr; height:100vh; gap:12px;padding:12px;}
.panel {background:var(--panel);padding:12px;border-radius:12px;box-shadow:0 6px 18px rgba(0,0,0,.6);}
.toolbar {display:grid;gap:8px;}
button,input,select{font-size:13px;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.06);background:transparent;color:inherit}
.canvas-wrap{position:relative;border-radius:12px;overflow:hidden;display:flex;align-items:center;justify-content:center;}
canvas{background:#fff;width:100%;height:100%;display:block;touch-action:none;}
.overlay-text{position:absolute;left:0;top:0;pointer-events:none}
.tools-row{display:flex;gap:6px;flex-wrap:wrap;}
.small{font-size:12px;padding:6px}
label{display:block;color:var(--muted);font-size:12px;margin-bottom:6px}
.color-input{height:40px;width:60px;padding:3px}
.hide{display:none}
.footer{display:flex;gap:8px;align-items:center;justify-content:space-between;margin-top:8px;color:var(--muted);font-size:12px}
JavaScript
/* =========================
Animator v1.4 - single file
Features: brushes, efx, bucket, text, export (MediaRecorder & GIF), undo
Notes: This is a baseline implementation. Adapt into your app architecture.
========================= */
const canvas = document.getElementById('c');
const panel = document.getElementById('canvasPanel');
const overlayText = document.getElementById('overlayText');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
let state = {
tool: 'brush', // brush | bucket | eraser | text
brushType: 'hard',
size: 16,
opacity: 0.95,
color: '#0b0b0b',
smoothing: true,
pressureSim: false,
patternImg: null,
isDrawing: false,
lastPos: null,
dpr: window.devicePixelRatio || 1,
undoStack: [],
maxUndo: 20,
};
function fitCanvas() {
const r = panel.getBoundingClientRect();
const dpr = state.dpr;
canvas.width = Math.max(600, Math.floor(r.width * dpr));
canvas.height = Math.max(400, Math.floor((window.innerHeight - 60) * dpr));
canvas.style.width = r.width + 'px';
canvas.style.height = (window.innerHeight - 60) + 'px';
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.lineJoin = ctx.lineCap = 'round';
ctx.fillStyle = '#fff';
// only fill if empty:
if (!state.initialized) {
ctx.fillRect(0,0,canvas.width,canvas.height);
state.initialized = true;
}
}
fitCanvas();
window.addEventListener('resize', fitCanvas);
/* ---------- UI Hooks ---------- */
document.getElementById('brushType').addEventListener('change', e => {
state.brushType = e.target.value;
updateStatus();
});
document.getElementById('size').addEventListener('input', e => state.size = +e.target.value);
document.getElementById('opacity').addEventListener('input', e => state.opacity = +e.target.value);
document.getElementById('color').addEventListener('input', e => state.color = e.target.value);
document.getElementById('patternURL').addEventListener('change', async (e) =>...