mirror of
https://github.com/milk-net/milk-net.github.io.git
synced 2025-04-18 17:23:40 -05:00
273 lines
8.1 KiB
HTML
273 lines
8.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MilkNet | Draw</title>
|
|
<link id="favicon" rel="icon" href="/assets/img/milk.png" type="image/x-icon">
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
height: 100vh;
|
|
background-color: #f5f5f5;
|
|
font-family: 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
#drawingCanvas {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
cursor: crosshair;
|
|
}
|
|
|
|
.controls {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background-color: #fff;
|
|
border: 2px solid #ccc;
|
|
padding: 20px;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
|
width: 250px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
height: calc(100vh - 40px);
|
|
font-family: 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
.controls label {
|
|
margin-top: 10px;
|
|
display: block;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.controls input[type="color"],
|
|
.controls input[type="range"],
|
|
.controls select {
|
|
width: 100%;
|
|
margin: 5px 0;
|
|
}
|
|
|
|
.controls button {
|
|
margin-top: 10px;
|
|
display: block;
|
|
width: 100%;
|
|
padding: 10px;
|
|
background-color: #000;
|
|
color: white;
|
|
font-size: 18px;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
.controls button:hover {
|
|
background-color: #333;
|
|
}
|
|
|
|
#shapeSelector {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<canvas id="drawingCanvas"></canvas>
|
|
|
|
<div class="controls">
|
|
<div>
|
|
<label for="toolSelector">Tool</label>
|
|
<select id="toolSelector">
|
|
<option value="brush">Brush</option>
|
|
<option value="rainbow">Rainbow Brush</option>
|
|
<option value="eraser">Eraser</option>
|
|
<option value="shape">Shape</option>
|
|
</select>
|
|
|
|
<label for="shapeSelector">Shape</label>
|
|
<select id="shapeSelector">
|
|
<option value="square">Square</option>
|
|
<option value="circle">Circle</option>
|
|
<option value="triangle">Triangle</option>
|
|
</select>
|
|
|
|
<label for="colorPicker">Color</label>
|
|
<input type="color" id="colorPicker" value="#000000">
|
|
|
|
<label for="brushSize">Thickness</label>
|
|
<input type="range" id="brushSize" min="1" max="50" value="5">
|
|
|
|
<button id="undoButton">Undo</button>
|
|
<button id="redoButton">Redo</button>
|
|
</div>
|
|
|
|
<button id="clearButton">Clear</button>
|
|
</div>
|
|
|
|
<script>
|
|
const canvas = document.getElementById('drawingCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const clearButton = document.getElementById('clearButton');
|
|
const colorPicker = document.getElementById('colorPicker');
|
|
const brushSize = document.getElementById('brushSize');
|
|
const toolSelector = document.getElementById('toolSelector');
|
|
const shapeSelector = document.getElementById('shapeSelector');
|
|
const undoButton = document.getElementById('undoButton');
|
|
const redoButton = document.getElementById('redoButton');
|
|
|
|
let isDrawing = false;
|
|
let lastX = 0, lastY = 0;
|
|
let startX = 0, startY = 0;
|
|
let previewCanvas, previewCtx;
|
|
let hue = 0;
|
|
let currentTool = 'brush';
|
|
let currentShape = 'square';
|
|
const undoStack = [];
|
|
const redoStack = [];
|
|
|
|
function saveState() {
|
|
undoStack.push(canvas.toDataURL());
|
|
redoStack.length = 0;
|
|
}
|
|
|
|
function restoreState(stackFrom, stackTo) {
|
|
if (stackFrom.length === 0) return;
|
|
stackTo.push(canvas.toDataURL());
|
|
const img = new Image();
|
|
img.src = stackFrom.pop();
|
|
img.onload = () => {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.drawImage(img, 0, 0);
|
|
};
|
|
}
|
|
|
|
function resizeCanvas() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
}
|
|
window.addEventListener('resize', resizeCanvas);
|
|
resizeCanvas();
|
|
|
|
ctx.lineCap = 'round';
|
|
ctx.lineJoin = 'round';
|
|
|
|
function getPosition(e) {
|
|
if (e.touches && e.touches.length > 0) {
|
|
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
} else {
|
|
return { x: e.clientX, y: e.clientY };
|
|
}
|
|
}
|
|
|
|
function startDrawing(e) {
|
|
const pos = getPosition(e);
|
|
isDrawing = true;
|
|
startX = lastX = pos.x;
|
|
startY = lastY = pos.y;
|
|
if (currentTool !== 'shape') saveState();
|
|
}
|
|
|
|
function stopDrawing(e) {
|
|
if (!isDrawing) return;
|
|
isDrawing = false;
|
|
if (currentTool === 'shape') {
|
|
const pos = getPosition(e);
|
|
drawShape(startX, startY, pos.x, pos.y, true);
|
|
saveState();
|
|
}
|
|
}
|
|
|
|
function draw(e) {
|
|
const pos = getPosition(e);
|
|
if (currentTool === 'shape' && isDrawing) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
restorePreview();
|
|
drawShape(startX, startY, pos.x, pos.y, false);
|
|
return;
|
|
}
|
|
|
|
if (!isDrawing) return;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(lastX, lastY);
|
|
ctx.lineTo(pos.x, pos.y);
|
|
|
|
if (currentTool === 'rainbow') {
|
|
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
|
|
hue = (hue + 1) % 360;
|
|
} else if (currentTool === 'eraser') {
|
|
ctx.strokeStyle = '#ffffff';
|
|
} else {
|
|
ctx.strokeStyle = colorPicker.value;
|
|
}
|
|
|
|
ctx.lineWidth = brushSize.value;
|
|
ctx.stroke();
|
|
lastX = pos.x;
|
|
lastY = pos.y;
|
|
}
|
|
|
|
function drawShape(x1, y1, x2, y2, finalDraw) {
|
|
if (finalDraw) restorePreview();
|
|
ctx.fillStyle = colorPicker.value;
|
|
ctx.lineWidth = brushSize.value;
|
|
ctx.beginPath();
|
|
|
|
if (currentShape === 'square') {
|
|
const width = x2 - x1;
|
|
const height = y2 - y1;
|
|
ctx.fillRect(x1, y1, width, height);
|
|
} else if (currentShape === 'circle') {
|
|
const radius = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / 2;
|
|
ctx.arc((x1 + x2) / 2, (y1 + y2) / 2, radius, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
} else if (currentShape === 'triangle') {
|
|
ctx.moveTo((x1 + x2) / 2, y1);
|
|
ctx.lineTo(x1, y2);
|
|
ctx.lineTo(x2, y2);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
function restorePreview() {
|
|
const img = new Image();
|
|
img.src = canvas.toDataURL();
|
|
img.onload = () => ctx.drawImage(img, 0, 0);
|
|
}
|
|
|
|
canvas.addEventListener('mousedown', startDrawing);
|
|
canvas.addEventListener('mousemove', draw);
|
|
canvas.addEventListener('mouseup', stopDrawing);
|
|
canvas.addEventListener('mouseout', stopDrawing);
|
|
|
|
canvas.addEventListener('touchstart', startDrawing);
|
|
canvas.addEventListener('touchmove', draw);
|
|
canvas.addEventListener('touchend', stopDrawing);
|
|
|
|
colorPicker.addEventListener('input', (e) => ctx.strokeStyle = e.target.value);
|
|
brushSize.addEventListener('input', (e) => ctx.lineWidth = e.target.value);
|
|
toolSelector.addEventListener('change', (e) => {
|
|
currentTool = e.target.value;
|
|
shapeSelector.style.display = currentTool === 'shape' ? 'block' : 'none';
|
|
});
|
|
shapeSelector.addEventListener('change', (e) => currentShape = e.target.value);
|
|
|
|
undoButton.addEventListener('click', () => restoreState(undoStack, redoStack));
|
|
redoButton.addEventListener('click', () => restoreState(redoStack, undoStack));
|
|
clearButton.addEventListener('click', () => {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
saveState();
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|