Open In App

How to Create Virtual Whiteboard using JavaScript and Tailwind ?

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.



Preview

Approach to create Virtual Whiteboard:

Example: Illustration of online WhiteBoard App in Tailwind CSS.




<!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:



Output


Article Tags :