Open In App

How to Create Virtual Whiteboard using JavaScript and Tailwind ?

Last Updated : 23 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

The Whiteboard project aims to create a collaborative digital whiteboard application using the Tailwind CSS framework. This web application will allow users to draw, write, and collaborate in real time providing a virtual space for brainstorming, presentations, and creative collaboration.

Output Preview: Let us have a look at how the final output will look like.

vw11vw
Preview

Approach to create Virtual Whiteboard:

  • Set up a new project using Node.js and install Tailwind CSS.
  • Create the basic structure of the whiteboard using HTML, CSS, and JavaScript.
  • Implement drawing functionality using a library like Canvas API or SVG.
  • Add real-time collaboration features using the WebSocket or a similar technology.
  • Enhance the UI with the Tailwind CSS to create an attractive and responsive user interface.

Example: Illustration of online WhiteBoard App in Tailwind CSS.

HTML
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport"
		content="width=device-width, initial-scale=1.0">
	<title>The Virtual Whiteboard</title>
	<script src="https://cdn.tailwindcss.com"></script>
	<style>
		.draggable {
			cursor: move;
		}
	</style>
</head>

<body class="bg-green-100">
	<div class="flex justify-center items-center h-screen">
		<div class="max-w-3xl w-full bg-white 
					shadow-lg rounded-lg p-8">
			<h1 class="text-3xl font-semibold
					mb-8 text-center">
				Virtual Whiteboard
			</h1>
			<div class="border border-green-300
						rounded-lg mb-6 p-4 relative">
				<div class="flex justify-center items-center">
					<canvas id="whiteboard" width="700" height="500"></canvas>
				</div>
				<div class="absolute top-0 right-0 p-2">
					<button id="clearBtn" class="px-4 py-2 bg-blue-500
												text-white rounded-lg 
												hover:bg-blue-600">
						Clear
					</button>
					<button id="eraserBtn" class="ml-2 px-4 py-2 bg-red-500
												text-white rounded-lg 
												hover:bg-red-600">
						Eraser
					</button>
				</div>
			</div>
			<div class="flex justify-between items-center mb-4">
				<div class="relative flex items-center">
					<label for="toolSelect" class="mr-2">
						Tool:
					</label>
					<select id="toolSelect"
							class="px-3 py-2 bg-gray-200 
								rounded-lg appearance-none">
						<option value="pen">Pen</option>
						<option value="line">Line</option>
						<option value="rectangle">Rectangle</option>
						<option value="circle">Circle</option>
						<option value="text">Text</option>
					</select>
				</div>
				<div>
					<button id="saveBtn" class="px-4 py-2 bg-green-500
												text-white rounded-lg 
												hover:bg-green-600">
						Save
					</button>
					<input type="file" id="imageInput"
						accept="image/*" class="hidden">
					<button id="imageBtn" class="px-4 py-2 bg-yellow-500 
												text-white rounded-lg 
												hover:bg-yellow-600">
						Insert Image
					</button>
				</div>
			</div>
			<div id="textInput" class="mb-4 hidden">
				<input type="text" id="textBox"
					class="border border-gray-300 rounded
							px-3 py-2 focus:outline-none">
				<button id="textBtn"
						class="ml-2 px-4 py-2 bg-blue-500 
							text-white rounded-lg 
							hover:bg-blue-600">
					Add Text
				</button>
			</div>
		</div>
	</div>
	<script>
		const canvas = document.getElementById('whiteboard');
		const context = canvas.getContext('2d');
		let isDrawing = false;
		let lastX = 0;
		let lastY = 0;
		let tool = 'pen';
		let shapes = [];
		let drag = false;
		let dragStartX, dragStartY;

		function draw(e) {
			if (!isDrawing) return;
			const { offsetX, offsetY } = e;
			context.lineWidth = 2;
			context.strokeStyle = '#000';
			context.fillStyle = '#000';
			if (tool === 'pen') {
				context.lineTo(offsetX, offsetY);
				context.stroke();
				lastX = offsetX;
				lastY = offsetY;
			} else if (tool === 'line') {
				context.clearRect(0, 0, canvas.width, canvas.height);
				context.beginPath();
				context.moveTo(lastX, lastY);
				context.lineTo(offsetX, offsetY);
				context.stroke();
			} else if (tool === 'rectangle') {
				context.clearRect(0, 0, canvas.width, canvas.height);
				context.beginPath();
				context.rect(lastX, lastY, offsetX - lastX, offsetY - lastY);
				context.stroke();
			} else if (tool === 'circle') {
				context.clearRect(0, 0, canvas.width, canvas.height);
				const radius = Math.sqrt(Math.pow(offsetX - lastX, 2) +
					Math.pow(offsetY - lastY, 2));
				context.beginPath();
				context.arc(lastX, lastY, radius, 0, Math.PI * 2);
				context.stroke();
			}
		}
		canvas.addEventListener('mousedown', (e) => {
			isDrawing = true;
			if (tool !== 'pen') {
				lastX = e.offsetX;
				lastY = e.offsetY;
			}
			if (tool === 'line' || tool === 'rectangle' || tool === 'circle') {
				drag = true;
				dragStartX = e.offsetX;
				dragStartY = e.offsetY;
			}
		});
		canvas.addEventListener('mousemove', (e) => {
			if (tool === 'pen' && isDrawing) {
				draw(e);
			} else if (tool === 'line' || tool === 'rectangle' 
										|| tool === 'circle') {
				if (!drag) return;
				const { offsetX, offsetY } = e;
				context.clearRect(0, 0, canvas.width, canvas.height);
				shapes.forEach(shape => {
					if (shape.type === 'line') {
						context.beginPath();
						context.moveTo(shape.startX, shape.startY);
						context.lineTo(shape.endX, shape.endY);
						context.stroke();
					} else if (shape.type === 'rectangle') {
						context.beginPath();
						context.rect(shape.startX, shape.startY,
							shape.endX - shape.startX,
							shape.endY - shape.startY);
						context.stroke();
					} else if (shape.type === 'circle') {
						context.beginPath();
						context.arc(shape.startX, shape.startY,
							shape.radius, 0, Math.PI * 2);
						context.stroke();
					}
				});
				if (tool === 'line') {
					context.beginPath();
					context.moveTo(lastX, lastY);
					context.lineTo(offsetX, offsetY);
					context.stroke();
				} else if (tool === 'rectangle') {
					context.beginPath();
					context.rect(lastX, lastY,
						offsetX - lastX, offsetY - lastY);
					context.stroke();
				} else if (tool === 'circle') {
					const radius = Math.sqrt(Math.pow(offsetX - lastX, 2) +
						Math.pow(offsetY - lastY, 2));
					context.beginPath();
					context.arc(lastX, lastY, radius, 0, Math.PI * 2);
					context.stroke();
				}
			}
		});
		canvas.addEventListener('mouseup', (e) => {
			isDrawing = false;
			if (tool === 'line' || tool === 'rectangle' 
								|| tool === 'circle') {
				shapes.push({
					type: tool,
					startX: dragStartX,
					startY: dragStartY,
					endX: e.offsetX,
					endY: e.offsetY,
					radius: Math.sqrt(Math
									.pow(e.offsetX - dragStartX, 2) +
							Math.pow(e.offsetY - dragStartY, 2))
				});
			}
			drag = false;
		});
		document.getElementById('clearBtn')
				.addEventListener('click', () => {
					context.clearRect(0, 0, canvas.width, canvas.height);
					shapes = [];
			});
		document.getElementById('eraserBtn')
			.addEventListener('click', () => {
				tool = 'eraser';
			});
		canvas.addEventListener('mousemove', (e) => {
			if (tool === 'eraser' && isDrawing) {
				const { offsetX, offsetY } = e;
				context.clearRect(offsetX - 5, offsetY - 5, 10, 10);
			}
		});
		document.getElementById('toolSelect')
			.addEventListener('change', (e) => {
				tool = e.target.value;
				if (tool === 'text') {
					document.getElementById('textInput')
							.classList.remove('hidden');
					document.getElementById('textBox').focus();
				} else {
					document.getElementById('textInput')
							.classList.add('hidden');
				}
			});
		document.getElementById('textBtn')
				.addEventListener('click', () => {
			const text = document.getElementById('textBox')
								.value;
			context.font = '16px Arial';
			context.fillText(text, lastX, lastY);
			document.getElementById('textInput')
					.classList.add('hidden');
			tool = 'pen';
		});
		document.getElementById('imageBtn')
				.addEventListener('click', () => {
			document.getElementById('imageInput').click();
		});
		document.getElementById('imageInput')
				.addEventListener('change', (e) => {
			const file = e.target.files[0];
			const reader = new FileReader();
			reader.onload = () => {
				const img = new Image();
				img.onload = () => {
					context.drawImage(img, 0, 0);
				};
				img.src = reader.result;
			};
			reader.readAsDataURL(file);
		});
		document.getElementById('saveBtn')
				.addEventListener('click', () => {
			const image = canvas.toDataURL('image/png');
			const link = document.createElement('a');
			link.href = image;
			link.download = 'whiteboard.png';
			link.click();
		});
	</script>
</body>

</html>

Output:


Next Article

Similar Reads