Skip to content

Commit 36e7333

Browse files
committed
Implement touch and pencil support for drawing tools with advanced drawing techniques
1 parent 7531e62 commit 36e7333

File tree

1 file changed

+306
-8
lines changed

1 file changed

+306
-8
lines changed

notes.html

Lines changed: 306 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@
141141
#canvas {
142142
background-color: white;
143143
display: none;
144+
touch-action: none;
145+
border: 1px solid #ccc;
144146
}
145147

146148
.color-picker {
@@ -311,6 +313,12 @@ <h2>Your Notes</h2>
311313
let startX, startY;
312314
let autoSaveTimeout;
313315
const maxStackSize = 50; // Limit for undo/redo stack
316+
let tempCanvas = document.createElement('canvas');
317+
let tempCtx = tempCanvas.getContext('2d');
318+
let currentX, currentY; // Store current mouse position
319+
let animationFrameId = null;
320+
let points = [];
321+
let lastPoint = null;
314322

315323
// DOM elements
316324
const canvas = document.getElementById('canvas');
@@ -344,6 +352,12 @@ <h2>Your Notes</h2>
344352
canvas.addEventListener('mouseup', stopDrawing);
345353
canvas.addEventListener('mouseout', stopDrawing);
346354

355+
// Touch and Pencil events
356+
canvas.addEventListener('touchstart', handleTouchStart, { passive: false });
357+
canvas.addEventListener('touchmove', handleTouchMove, { passive: false });
358+
canvas.addEventListener('touchend', handleTouchEnd);
359+
canvas.addEventListener('touchcancel', handleTouchEnd);
360+
347361
// Window resize handler
348362
window.addEventListener('resize', function() {
349363
if (canvas.style.display !== 'none') {
@@ -387,28 +401,26 @@ <h2>Your Notes</h2>
387401
saveStatus.style.display = 'block';
388402
saveStatus.textContent = 'Changes saved!';
389403

390-
// Remove any existing timeout
391404
if (saveStatus.timeout) {
392405
clearTimeout(saveStatus.timeout);
393406
}
394407

395-
// Set new timeout
396408
saveStatus.timeout = setTimeout(() => {
397409
saveStatus.style.display = 'none';
398410
}, 2000);
399411
}
400412

401413
function switchTab(index) {
402-
if (currentTab === index) return; // Don't switch if clicking current tab
414+
if (currentTab === index) return;
403415

404-
saveNotes(true); // Save current tab
416+
saveNotes(true);
405417
currentTab = index;
406418
const tabs = document.querySelectorAll('.tab');
407419
tabs.forEach((tab, i) => {
408420
tab.classList.toggle('active', i === index);
409421
});
410422
loadNoteContent();
411-
saveToUndoStack(); // Initialize undo stack for new tab
423+
saveToUndoStack();
412424
}
413425

414426
function loadNoteContent() {
@@ -440,7 +452,6 @@ <h2>Your Notes</h2>
440452
canvas.style.display = 'none';
441453
document.getElementById('textFormatTools').style.display = 'flex';
442454
document.querySelector('button[onclick="switchMode(\'text\')"]').classList.add('active');
443-
// Hide drawing tools
444455
document.querySelectorAll('.tool-button:not([onclick^="switchMode"])').forEach(btn =>
445456
btn.style.display = 'none'
446457
);
@@ -449,10 +460,10 @@ <h2>Your Notes</h2>
449460
canvas.style.display = 'block';
450461
document.getElementById('textFormatTools').style.display = 'none';
451462
document.querySelector('button[onclick="switchMode(\'draw\')"]').classList.add('active');
452-
// Show drawing tools
453463
document.querySelectorAll('.tool-button:not([onclick^="switchMode"])').forEach(btn =>
454464
btn.style.display = 'inline-block'
455465
);
466+
setupCanvas();
456467
}
457468
}
458469

@@ -462,7 +473,6 @@ <h2>Your Notes</h2>
462473
buttons.forEach(btn => btn.classList.remove('active'));
463474
document.querySelector(`button[onclick="setTool('${tool}')"]`).classList.add('active');
464475

465-
// Reset drawing state
466476
ctx.beginPath();
467477
isDrawing = false;
468478
}
@@ -495,6 +505,294 @@ <h2>Your Notes</h2>
495505
}
496506
saveToUndoStack();
497507
}
508+
509+
function startDrawing(e) {
510+
isDrawing = true;
511+
const rect = canvas.getBoundingClientRect();
512+
const scaleX = canvas.width / rect.width;
513+
const scaleY = canvas.height / rect.height;
514+
515+
startX = (e.clientX - rect.left) * scaleX;
516+
startY = (e.clientY - rect.top) * scaleY;
517+
currentX = startX;
518+
currentY = startY;
519+
520+
// Reset points array and last point
521+
points = [];
522+
lastPoint = null;
523+
524+
if (currentTool === 'pen') {
525+
ctx.beginPath();
526+
ctx.moveTo(startX, startY);
527+
}
528+
529+
saveToUndoStack();
530+
}
531+
532+
function draw(e) {
533+
if (!isDrawing) return;
534+
535+
const rect = canvas.getBoundingClientRect();
536+
const scaleX = canvas.width / rect.width;
537+
const scaleY = canvas.height / rect.height;
538+
539+
currentX = (e.clientX - rect.left) * scaleX;
540+
currentY = (e.clientY - rect.top) * scaleY;
541+
542+
// Get pressure from Apple Pencil if available
543+
let pressure = e.pressure || e.force || 1;
544+
545+
if (currentTool === 'pen' || currentTool === 'eraser') {
546+
points.push({
547+
x: currentX,
548+
y: currentY,
549+
pressure: pressure
550+
});
551+
552+
if (points.length > 4) {
553+
points.shift();
554+
}
555+
556+
if (points.length > 1) {
557+
ctx.strokeStyle = currentTool === 'eraser' ? '#ffffff' : currentColor;
558+
ctx.lineCap = 'round';
559+
ctx.lineJoin = 'round';
560+
561+
// Apply pressure sensitivity to line width
562+
ctx.lineWidth = currentSize * pressure;
563+
564+
// Start a new path
565+
ctx.beginPath();
566+
567+
// Move to the first point
568+
if (lastPoint) {
569+
ctx.moveTo(lastPoint.x, lastPoint.y);
570+
} else {
571+
ctx.moveTo(points[0].x, points[0].y);
572+
}
573+
574+
// If we have enough points, draw a smooth curve
575+
if (points.length > 2) {
576+
// Calculate control points
577+
const xc = (points[1].x + points[2].x) / 2;
578+
const yc = (points[1].y + points[2].y) / 2;
579+
580+
// Draw curve using quadratic bezier
581+
ctx.quadraticCurveTo(points[1].x, points[1].y, xc, yc);
582+
} else {
583+
// Draw straight line if only 2 points
584+
ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
585+
}
586+
587+
ctx.stroke();
588+
589+
// Store last point
590+
lastPoint = points[points.length - 1];
591+
}
592+
} else {
593+
// For shapes, use requestAnimationFrame
594+
if (!animationFrameId) {
595+
tempCanvas.width = canvas.width;
596+
tempCanvas.height = canvas.height;
597+
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
598+
tempCtx.drawImage(canvas, 0, 0);
599+
animationFrameId = requestAnimationFrame(drawShape);
600+
}
601+
}
602+
}
603+
604+
function drawShape() {
605+
// Clear main canvas and restore the initial state
606+
ctx.clearRect(0, 0, canvas.width, canvas.height);
607+
ctx.drawImage(tempCanvas, 0, 0);
608+
609+
// Draw the current shape
610+
ctx.beginPath();
611+
ctx.strokeStyle = currentColor;
612+
ctx.lineWidth = currentSize;
613+
ctx.lineCap = 'round';
614+
615+
if (currentTool === 'rectangle') {
616+
ctx.rect(startX, startY, currentX - startX, currentY - startY);
617+
} else if (currentTool === 'circle') {
618+
const radius = Math.sqrt(Math.pow(currentX - startX, 2) + Math.pow(currentY - startY, 2));
619+
ctx.arc(startX, startY, radius, 0, 2 * Math.PI);
620+
} else if (currentTool === 'line') {
621+
ctx.moveTo(startX, startY);
622+
ctx.lineTo(currentX, currentY);
623+
}
624+
ctx.stroke();
625+
626+
// Continue animation if still drawing
627+
if (isDrawing) {
628+
animationFrameId = requestAnimationFrame(drawShape);
629+
} else {
630+
animationFrameId = null;
631+
}
632+
}
633+
634+
function stopDrawing() {
635+
if (!isDrawing) return;
636+
isDrawing = false;
637+
ctx.closePath();
638+
639+
// Reset points array and last point
640+
points = [];
641+
lastPoint = null;
642+
643+
// Cancel any pending animation frame
644+
if (animationFrameId) {
645+
cancelAnimationFrame(animationFrameId);
646+
animationFrameId = null;
647+
}
648+
649+
// Save the current state
650+
notes[currentTab].drawing = canvas.toDataURL();
651+
saveNotes(true);
652+
}
653+
654+
function loadDrawing(dataUrl, callback) {
655+
if (!dataUrl) {
656+
ctx.clearRect(0, 0, canvas.width, canvas.height);
657+
if (callback) callback();
658+
return;
659+
}
660+
661+
const img = new Image();
662+
img.onload = function() {
663+
ctx.clearRect(0, 0, canvas.width, canvas.height);
664+
ctx.drawImage(img, 0, 0);
665+
if (callback) callback();
666+
};
667+
img.src = dataUrl;
668+
}
669+
670+
function saveToUndoStack() {
671+
undoStack.push({
672+
text: textarea.value,
673+
drawing: canvas.toDataURL()
674+
});
675+
676+
if (undoStack.length > maxStackSize) {
677+
undoStack.shift();
678+
}
679+
redoStack = [];
680+
}
681+
682+
function undoAction() {
683+
if (undoStack.length === 0) return;
684+
685+
redoStack.push({
686+
text: textarea.value,
687+
drawing: canvas.toDataURL()
688+
});
689+
690+
const previousState = undoStack.pop();
691+
textarea.value = previousState.text;
692+
loadDrawing(previousState.drawing);
693+
694+
notes[currentTab].text = previousState.text;
695+
notes[currentTab].drawing = previousState.drawing;
696+
saveNotes(true);
697+
}
698+
699+
function redoAction() {
700+
if (redoStack.length === 0) return;
701+
702+
undoStack.push({
703+
text: textarea.value,
704+
drawing: canvas.toDataURL()
705+
});
706+
707+
const nextState = redoStack.pop();
708+
textarea.value = nextState.text;
709+
loadDrawing(nextState.drawing);
710+
711+
notes[currentTab].text = nextState.text;
712+
notes[currentTab].drawing = nextState.drawing;
713+
saveNotes(true);
714+
}
715+
716+
function saveNotes(autosave = false) {
717+
notes[currentTab].text = textarea.value;
718+
notes[currentTab].drawing = canvas.toDataURL();
719+
localStorage.setItem("allNotes", JSON.stringify(notes));
720+
721+
if (!autosave) {
722+
showSaveStatus();
723+
}
724+
}
725+
726+
function clearNotes() {
727+
if (confirm('Are you sure you want to clear the current note?')) {
728+
saveToUndoStack();
729+
textarea.value = '';
730+
ctx.clearRect(0, 0, canvas.width, canvas.height);
731+
notes[currentTab] = { text: '', drawing: null };
732+
saveNotes();
733+
}
734+
}
735+
736+
function showDownloadOptions() {
737+
const options = document.getElementById('download-options');
738+
options.classList.toggle('show');
739+
}
740+
741+
function downloadAsPDF() {
742+
const { jsPDF } = window.jspdf;
743+
const pdf = new jsPDF();
744+
745+
// Add text
746+
pdf.text(notes[currentTab].text, 10, 10);
747+
748+
// Add drawing if exists
749+
if (notes[currentTab].drawing) {
750+
pdf.addImage(notes[currentTab].drawing, 'PNG', 10, 100, 190, 100);
751+
}
752+
753+
pdf.save(`note${currentTab + 1}.pdf`);
754+
}
755+
756+
function downloadAsImage() {
757+
const link = document.createElement('a');
758+
link.download = `note${currentTab + 1}.png`;
759+
link.href = canvas.toDataURL();
760+
link.click();
761+
}
762+
763+
function downloadAsTXT() {
764+
const link = document.createElement('a');
765+
const file = new Blob([notes[currentTab].text], { type: 'text/plain' });
766+
link.href = URL.createObjectURL(file);
767+
link.download = `note${currentTab + 1}.txt`;
768+
link.click();
769+
}
770+
771+
// Add these new touch handling functions
772+
function handleTouchStart(e) {
773+
e.preventDefault(); // Prevent scrolling
774+
const touch = e.touches[0];
775+
const mouseEvent = new MouseEvent('mousedown', {
776+
clientX: touch.clientX,
777+
clientY: touch.clientY
778+
});
779+
startDrawing(mouseEvent);
780+
}
781+
782+
function handleTouchMove(e) {
783+
e.preventDefault(); // Prevent scrolling
784+
const touch = e.touches[0];
785+
const mouseEvent = new MouseEvent('mousemove', {
786+
clientX: touch.clientX,
787+
clientY: touch.clientY
788+
});
789+
draw(mouseEvent);
790+
}
791+
792+
function handleTouchEnd(e) {
793+
e.preventDefault();
794+
stopDrawing();
795+
}
498796
</script>
499797
</body>
500798
</html>

0 commit comments

Comments
 (0)