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.
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 >
< 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:
Recommended Articles