hand-written / index.html
Mustafa7assan's picture
Add 2 files
cb10101 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Handwriting Canvas</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400;700&display=swap');
.handwriting {
font-family: 'Caveat', cursive;
}
canvas {
touch-action: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.tool-btn {
transition: all 0.2s ease;
}
.tool-btn.active {
transform: scale(1.1);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
}
#drawingCanvas {
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTSAwIDEwIEwgNDAgMTAgTSAxMCAwIEwgMTAgNDAgTSAwIDIwIEwgNDAgMjAgTSAyMCAwIEwgMjAgNDAgTSAwIDMwIEwgNDAgMzAgTSAzMCAwIEwgMzAgNDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2QwZDBkMCIgb3BhY2l0eT0iMC4yIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48L3N2Zz4=');
}
.ink-effect {
position: relative;
}
.ink-effect::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle, transparent 0%, rgba(0,0,0,0.05) 100%);
pointer-events: none;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-indigo-700 handwriting mb-2">Handwriting Canvas</h1>
<p class="text-gray-600">Draw or write naturally on images or blank paper</p>
</header>
<div class="flex flex-col lg:flex-row gap-6">
<!-- Tools Panel -->
<div class="bg-white rounded-lg shadow-md p-4 lg:w-1/4">
<h2 class="text-xl font-semibold mb-4 text-gray-800 handwriting">Tools</h2>
<div class="space-y-4">
<!-- Color Picker -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1 handwriting">Pen Color</label>
<div class="flex flex-wrap gap-2">
<button data-color="#000000" class="tool-btn w-8 h-8 rounded-full bg-black border-2 border-gray-300"></button>
<button data-color="#e53e3e" class="tool-btn w-8 h-8 rounded-full bg-red-600 border-2 border-gray-300"></button>
<button data-color="#3182ce" class="tool-btn w-8 h-8 rounded-full bg-blue-600 border-2 border-gray-300"></button>
<button data-color="#38a169" class="tool-btn w-8 h-8 rounded-full bg-green-600 border-2 border-gray-300"></button>
<button data-color="#805ad5" class="tool-btn w-8 h-8 rounded-full bg-purple-600 border-2 border-gray-300"></button>
</div>
</div>
<!-- Brush Size -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1 handwriting">Brush Size</label>
<input type="range" id="brushSize" min="1" max="20" value="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>Thin</span>
<span>Thick</span>
</div>
</div>
<!-- Background Options -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1 handwriting">Background</label>
<div class="flex flex-wrap gap-2">
<button data-bg="blank" class="tool-btn px-3 py-1 bg-white border border-gray-300 rounded-md handwriting">White Paper</button>
<button data-bg="grid" class="tool-btn px-3 py-1 bg-white border border-gray-300 rounded-md handwriting">Grid Paper</button>
<button id="uploadImageBtn" class="tool-btn px-3 py-1 bg-indigo-100 text-indigo-700 border border-indigo-200 rounded-md handwriting">
<i class="fas fa-image mr-1"></i> Upload Image
</button>
<input type="file" id="imageUpload" accept="image/*" class="hidden">
</div>
</div>
<!-- Actions -->
<div class="pt-4 border-t border-gray-200">
<button id="clearCanvas" class="w-full bg-red-100 text-red-700 py-2 rounded-md hover:bg-red-200 transition handwriting">
<i class="fas fa-trash-alt mr-2"></i> Clear Canvas
</button>
<button id="downloadCanvas" class="w-full mt-2 bg-indigo-100 text-indigo-700 py-2 rounded-md hover:bg-indigo-200 transition handwriting">
<i class="fas fa-download mr-2"></i> Download
</button>
</div>
<!-- Handwriting Toggle -->
<div class="pt-4 border-t border-gray-200">
<label class="inline-flex items-center cursor-pointer">
<input type="checkbox" id="handwritingEffect" class="sr-only peer" checked>
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
<span class="ml-3 text-sm font-medium text-gray-700 handwriting">Handwriting Effect</span>
</label>
</div>
</div>
</div>
<!-- Canvas Area -->
<div class="flex-1">
<div class="bg-white rounded-lg shadow-md p-4">
<div class="relative overflow-hidden rounded-md ink-effect">
<canvas id="drawingCanvas" class="bg-white w-full h-96 lg:h-[500px] border border-gray-200"></canvas>
</div>
<div class="mt-4 flex justify-center space-x-4">
<button id="undoBtn" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition handwriting">
<i class="fas fa-undo mr-2"></i> Undo
</button>
<button id="redoBtn" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition handwriting">
<i class="fas fa-redo mr-2"></i> Redo
</button>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('drawingCanvas');
const ctx = canvas.getContext('2d');
const colorButtons = document.querySelectorAll('[data-color]');
const bgButtons = document.querySelectorAll('[data-bg]');
const brushSizeInput = document.getElementById('brushSize');
const clearCanvasBtn = document.getElementById('clearCanvas');
const downloadCanvasBtn = document.getElementById('downloadCanvas');
const uploadImageBtn = document.getElementById('uploadImageBtn');
const imageUpload = document.getElementById('imageUpload');
const handwritingEffect = document.getElementById('handwritingEffect');
const undoBtn = document.getElementById('undoBtn');
const redoBtn = document.getElementById('redoBtn');
let isDrawing = false;
let currentColor = '#000000';
let currentBrushSize = 3;
let handwritingEnabled = true;
let canvasHistory = [];
let historyIndex = -1;
// Set canvas size
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
redrawCanvas();
}
// Initialize canvas
function initCanvas() {
resizeCanvas();
setBackground('blank');
// Add event listeners for window resize
window.addEventListener('resize', () => {
resizeCanvas();
});
// Save initial state
saveCanvasState();
}
// Set background
function setBackground(type) {
if (type === 'blank') {
canvas.style.backgroundImage = 'none';
canvas.style.backgroundColor = '#ffffff';
} else if (type === 'grid') {
canvas.style.backgroundImage = 'url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTSAwIDEwIEwgNDAgMTAgTSAxMCAwIEwgMTAgNDAgTSAwIDIwIEwgNDAgMjAgTSAyMCAwIEwgMjAgNDAgTSAwIDMwIEwgNDAgMzAgTSAzMCAwIEwgMzAgNDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2QwZDBkMCIgb3BhY2l0eT0iMC4yIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48L3N2Zz4=")';
}
}
// Save canvas state to history
function saveCanvasState() {
// Remove any states after current index (for redo)
if (historyIndex < canvasHistory.length - 1) {
canvasHistory = canvasHistory.slice(0, historyIndex + 1);
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
canvasHistory.push(imageData);
historyIndex = canvasHistory.length - 1;
updateUndoRedoButtons();
}
// Update undo/redo button states
function updateUndoRedoButtons() {
undoBtn.disabled = historyIndex <= 0;
redoBtn.disabled = historyIndex >= canvasHistory.length - 1;
}
// Undo action
function undo() {
if (historyIndex > 0) {
historyIndex--;
redrawCanvas();
}
}
// Redo action
function redo() {
if (historyIndex < canvasHistory.length - 1) {
historyIndex++;
redrawCanvas();
}
}
// Redraw canvas from history
function redrawCanvas() {
if (canvasHistory.length > 0) {
ctx.putImageData(canvasHistory[historyIndex], 0, 0);
}
}
// Start drawing
function startDrawing(e) {
isDrawing = true;
draw(e);
}
// Stop drawing
function stopDrawing() {
isDrawing = false;
ctx.beginPath();
saveCanvasState();
}
// Draw on canvas
function draw(e) {
if (!isDrawing) return;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = currentColor;
ctx.lineWidth = currentBrushSize;
// Get position (handling both mouse and touch events)
let x, y;
if (e.type.includes('touch')) {
const rect = canvas.getBoundingClientRect();
x = e.touches[0].clientX - rect.left;
y = e.touches[0].clientY - rect.top;
} else {
x = e.offsetX;
y = e.offsetY;
}
// Apply handwriting effect by adding slight randomness
if (handwritingEnabled) {
ctx.lineTo(
x + (Math.random() - 0.5) * currentBrushSize * 0.5,
y + (Math.random() - 0.5) * currentBrushSize * 0.5
);
} else {
ctx.lineTo(x, y);
}
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
}
// Clear canvas
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
saveCanvasState();
}
// Download canvas as image
function downloadCanvas() {
const link = document.createElement('a');
link.download = 'handwriting-' + new Date().toISOString().slice(0, 10) + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
// Handle image upload
function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
// Clear canvas and draw the image
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Calculate dimensions to maintain aspect ratio
const ratio = Math.min(
canvas.width / img.width,
canvas.height / img.height
);
const width = img.width * ratio;
const height = img.height * ratio;
const x = (canvas.width - width) / 2;
const y = (canvas.height - height) / 2;
ctx.drawImage(img, x, y, width, height);
saveCanvasState();
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
// Event listeners
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
// Touch events for mobile
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
startDrawing(e);
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
draw(e);
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
stopDrawing();
});
// Color buttons
colorButtons.forEach(button => {
button.addEventListener('click', () => {
currentColor = button.dataset.color;
// Update active state
colorButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
});
});
// Background buttons
bgButtons.forEach(button => {
button.addEventListener('click', () => {
setBackground(button.dataset.bg);
// Update active state
bgButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
});
});
// Brush size
brushSizeInput.addEventListener('input', () => {
currentBrushSize = brushSizeInput.value;
});
// Clear canvas
clearCanvasBtn.addEventListener('click', clearCanvas);
// Download
downloadCanvasBtn.addEventListener('click', downloadCanvas);
// Image upload
uploadImageBtn.addEventListener('click', () => {
imageUpload.click();
});
imageUpload.addEventListener('change', handleImageUpload);
// Handwriting effect toggle
handwritingEffect.addEventListener('change', () => {
handwritingEnabled = handwritingEffect.checked;
});
// Undo/redo buttons
undoBtn.addEventListener('click', undo);
redoBtn.addEventListener('click', redo);
// Initialize
initCanvas();
// Set black as default active color
document.querySelector('[data-color="#000000"]').classList.add('active');
document.querySelector('[data-bg="blank"]').classList.add('active');
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Mustafa7assan/hand-written" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>