aero and wave
This commit is contained in:
@@ -210,6 +210,26 @@
|
||||
|
||||
.brush-btn:hover { color: #ccc; border-color: #555; }
|
||||
.brush-btn.active { color: #fff; border-color: #4a4a8a; background: #1a1a2e; }
|
||||
.brush-btn.eraser-active { color: #f66; border-color: #a44; background: #2e1a1a; }
|
||||
|
||||
.recent-row {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.recent-swatch {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.recent-swatch:hover { border-color: #888; }
|
||||
|
||||
.settings-btn {
|
||||
width: 100%;
|
||||
@@ -257,6 +277,8 @@
|
||||
|
||||
<div class="color-preview" id="colorPreview"></div>
|
||||
|
||||
<div class="recent-row" id="recentColors"></div>
|
||||
|
||||
<div class="slider-group">
|
||||
<label>Lightness <span id="lightVal">50%</span></label>
|
||||
<input type="range" id="lightSlider" min="0" max="100" step="1" value="50">
|
||||
@@ -277,6 +299,7 @@
|
||||
<button class="brush-btn" data-brush="soft">Soft</button>
|
||||
<button class="brush-btn" data-brush="square">Square</button>
|
||||
<button class="brush-btn" data-brush="spray">Spray</button>
|
||||
<button class="brush-btn" data-brush="eraser">Eraser</button>
|
||||
</div>
|
||||
|
||||
<button class="settings-btn" id="clearCanvas">Clear Canvas</button>
|
||||
@@ -460,32 +483,93 @@
|
||||
opacityVal.textContent = opacitySlider.value + '%';
|
||||
});
|
||||
|
||||
// ─── recent colors ───
|
||||
const recentColors = [];
|
||||
|
||||
function addRecentColor(color) {
|
||||
const idx = recentColors.indexOf(color);
|
||||
if (idx >= 0) recentColors.splice(idx, 1);
|
||||
recentColors.unshift(color);
|
||||
if (recentColors.length > 8) recentColors.pop();
|
||||
renderRecentColors();
|
||||
}
|
||||
|
||||
function renderRecentColors() {
|
||||
const container = document.getElementById('recentColors');
|
||||
container.innerHTML = '';
|
||||
recentColors.forEach(c => {
|
||||
const swatch = document.createElement('div');
|
||||
swatch.className = 'recent-swatch';
|
||||
swatch.style.background = c;
|
||||
swatch.addEventListener('click', () => {
|
||||
isEraser = false;
|
||||
document.querySelectorAll('#brushTypes .brush-btn').forEach(b => {
|
||||
b.classList.toggle('active', b.dataset.brush === brushType);
|
||||
b.classList.remove('eraser-active');
|
||||
});
|
||||
currentColor = c;
|
||||
document.getElementById('colorPreview').style.background = c;
|
||||
});
|
||||
container.appendChild(swatch);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── brush type ───
|
||||
let brushType = 'round';
|
||||
let isEraser = false;
|
||||
const brushBtns = document.querySelectorAll('#brushTypes .brush-btn');
|
||||
|
||||
brushBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
brushBtns.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
brushType = btn.dataset.brush;
|
||||
brushBtns.forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.classList.remove('eraser-active');
|
||||
});
|
||||
if (btn.dataset.brush === 'eraser') {
|
||||
isEraser = true;
|
||||
btn.classList.add('eraser-active');
|
||||
} else {
|
||||
isEraser = false;
|
||||
brushType = btn.dataset.brush;
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── undo ───
|
||||
const undoStack = [];
|
||||
const MAX_UNDO = 50;
|
||||
|
||||
function saveState() {
|
||||
if (canvas.width === 0 || canvas.height === 0) return;
|
||||
undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
|
||||
if (undoStack.length > MAX_UNDO) undoStack.shift();
|
||||
}
|
||||
|
||||
function undo() {
|
||||
if (undoStack.length === 0) return;
|
||||
const state = undoStack.pop();
|
||||
ctx.putImageData(state, 0, 0);
|
||||
}
|
||||
|
||||
// ─── drawing ───
|
||||
let isDown = false;
|
||||
let lastX = -1, lastY = -1;
|
||||
|
||||
function drawBrush(x, y, size) {
|
||||
const opacity = parseFloat(opacitySlider.value) / 100;
|
||||
ctx.globalAlpha = opacity;
|
||||
ctx.fillStyle = currentColor;
|
||||
|
||||
if (brushType === 'round') {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
} else if (brushType === 'soft') {
|
||||
if (isEraser) {
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
ctx.globalAlpha = opacity;
|
||||
} else {
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.globalAlpha = opacity;
|
||||
ctx.fillStyle = currentColor;
|
||||
addRecentColor(currentColor);
|
||||
}
|
||||
|
||||
if (brushType === 'soft' && !isEraser) {
|
||||
const grad = ctx.createRadialGradient(x, y, 0, x, y, size / 2);
|
||||
grad.addColorStop(0, currentColor);
|
||||
grad.addColorStop(1, 'transparent');
|
||||
@@ -496,7 +580,7 @@
|
||||
} else if (brushType === 'square') {
|
||||
ctx.fillRect(x - size / 2, y - size / 2, size, size);
|
||||
} else if (brushType === 'spray') {
|
||||
const count = Math.max(5, size * 0.8);
|
||||
const count = Math.max(5, size * 0.4);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const radius = Math.random() * size / 2;
|
||||
@@ -507,7 +591,13 @@
|
||||
dotSize, dotSize
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
@@ -548,6 +638,7 @@
|
||||
}
|
||||
|
||||
function endDraw() {
|
||||
if (isDown) saveState();
|
||||
isDown = false;
|
||||
lastX = -1; lastY = -1;
|
||||
}
|
||||
@@ -575,13 +666,23 @@
|
||||
|
||||
// ─── clear ───
|
||||
document.getElementById('clearCanvas').addEventListener('click', () => {
|
||||
saveState();
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
});
|
||||
|
||||
// ─── keyboard ───
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
|
||||
e.preventDefault();
|
||||
undo();
|
||||
}
|
||||
});
|
||||
|
||||
// ─── init ───
|
||||
drawColorWheel();
|
||||
updateCurrentColor();
|
||||
resizeCanvas();
|
||||
saveState();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user