TypeScript Tutorial: Creating a Simple Web-Based Drawing App

Ever wanted to create your own digital art or simply sketch out ideas without the need for complex software? This tutorial guides you through building a basic, yet functional, drawing application using TypeScript, HTML, and CSS. This project is perfect for beginners and intermediate developers looking to expand their skills in web development and understand the practical application of TypeScript.

Why Build a Drawing App?

Building a drawing app offers a fantastic learning experience. It combines several key web development concepts, including:

  • DOM Manipulation: Interacting with HTML elements to create, modify, and manage the drawing canvas.
  • Event Handling: Responding to user input such as mouse clicks, mouse movements, and touch events.
  • Canvas API: Utilizing the HTML canvas element for drawing shapes, lines, and handling color.
  • TypeScript Fundamentals: Applying types, interfaces, and classes for robust and maintainable code.

This project is also a great way to solidify your understanding of TypeScript while creating something visually engaging and interactive.

Project Setup and Prerequisites

Before we dive into the code, let’s set up our development environment. You’ll need the following:

  • A text editor or IDE: Such as Visual Studio Code, Sublime Text, or WebStorm.
  • Node.js and npm (or yarn): For managing project dependencies and running the TypeScript compiler.
  • Basic knowledge of HTML and CSS: To understand the structure and styling of the application.
  • A basic understanding of JavaScript: TypeScript is a superset of JavaScript, so familiarity with JavaScript is helpful.

Step 1: Create a Project Directory

Create a new directory for your project, for example, “drawing-app”. Navigate into this directory using your terminal.

Step 2: Initialize a Node.js Project

Run the following command in your terminal to initialize a new Node.js project:

npm init -y

This command creates a package.json file, which will manage your project’s dependencies.

Step 3: Install TypeScript

Install TypeScript as a development dependency:

npm install --save-dev typescript

Step 4: Create a TypeScript Configuration File

Create a tsconfig.json file in your project directory. This file configures the TypeScript compiler. You can generate a basic configuration by running:

npx tsc --init

This command creates a tsconfig.json file with default settings. You can customize these settings as needed. For this project, you might want to adjust the following:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}
  • target: Specifies the JavaScript version to compile to (e.g., “es5”, “es6”, “esnext”).
  • module: Specifies the module system to use (e.g., “commonjs”, “esnext”).
  • outDir: Specifies the output directory for compiled JavaScript files.
  • rootDir: Specifies the root directory of your TypeScript source files.
  • strict: Enables strict type-checking options.
  • esModuleInterop: Enables interoperability between CommonJS and ES modules.
  • skipLibCheck: Skips type checking of declaration files.
  • forceConsistentCasingInFileNames: Enforces consistent casing in file names.
  • include: Specifies the files or patterns to include in compilation.

Step 5: Create Project Files

Create the following files in your project directory:

  • index.html: The HTML file for your application.
  • src/index.ts: The main TypeScript file.
  • src/style.css: The CSS file for styling.

HTML Structure (index.html)

Let’s start by creating the HTML structure for our drawing app. This file will contain the canvas element and some basic UI elements.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Drawing App</title>
    <link rel="stylesheet" href="src/style.css">
</head>
<body>
    <canvas id="drawingCanvas"></canvas>
    <div class="controls">
        <label for="colorPicker">Color:</label>
        <input type="color" id="colorPicker" value="#000000">
        <label for="lineWidth">Line Width:</label>
        <input type="number" id="lineWidth" value="5" min="1" max="20">
        <button id="clearButton">Clear</button>
    </div>
    <script src="dist/index.js"></script>
</body>
</html>

Here’s a breakdown of the HTML code:

  • <canvas id="drawingCanvas"></canvas>: This is the HTML canvas element where the drawing will take place.
  • <input type="color" id="colorPicker">: A color picker for selecting the drawing color.
  • <input type="number" id="lineWidth">: An input field for setting the line width.
  • <button id="clearButton">: A button to clear the canvas.
  • <script src="dist/index.js"></script>: Includes the compiled JavaScript file.

CSS Styling (style.css)

Now, let’s add some basic styling to make our drawing app visually appealing.

body {
    font-family: sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 0;
    height: 100vh;
    background-color: #f0f0f0;
}

canvas {
    border: 1px solid #ccc;
    margin-top: 20px;
}

.controls {
    margin-top: 20px;
    display: flex;
    gap: 10px;
    align-items: center;
}

This CSS provides basic styling for the body, canvas, and controls. Feel free to customize the styles to your liking.

TypeScript Implementation (index.ts)

This is where the core logic of our drawing app resides. We will use TypeScript to handle user interactions, draw on the canvas, and manage the drawing settings.

Step 1: Get References to HTML Elements

First, we need to get references to the HTML elements we created in index.html.

const canvas = document.getElementById('drawingCanvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
const colorPicker = document.getElementById('colorPicker') as HTMLInputElement;
const lineWidthInput = document.getElementById('lineWidth') as HTMLInputElement;
const clearButton = document.getElementById('clearButton') as HTMLButtonElement;

Here, we use document.getElementById() to get the elements by their IDs and cast them to their respective types using the as keyword. This ensures type safety and provides better autocompletion in your IDE.

Step 2: Set Canvas Dimensions

We need to set the width and height of the canvas. It’s good practice to set these dynamically to fit the screen or a specific container.

canvas.width = window.innerWidth - 50;
canvas.height = window.innerHeight - 200;

This sets the canvas width to the window’s inner width minus 50 pixels (for some margin) and the height to the window’s inner height minus 200 pixels (to accommodate the controls). Adjust these values as needed.

Step 3: Define Drawing Variables

We’ll create variables to track whether the mouse is down and the current coordinates.

let isDrawing = false;
let lastX = 0;
let lastY = 0;

Step 4: Drawing Functions

Let’s define a function to start drawing, another to draw lines, and a function to stop drawing.

function startDrawing(x: number, y: number) {
    isDrawing = true;
    [lastX, lastY] = [x, y];
}

function draw(x: number, y: number) {
    if (!isDrawing) return; // Stop the function if not drawing

    ctx.strokeStyle = colorPicker.value;
    ctx.lineWidth = parseInt(lineWidthInput.value, 10);
    ctx.lineCap = 'round';

    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(x, y);
    ctx.stroke();

    [lastX, lastY] = [x, y];
}

function stopDrawing() {
    isDrawing = false;
}

Explanation:

  • startDrawing(x: number, y: number): Sets isDrawing to true and updates the last known coordinates.
  • draw(x: number, y: number): Checks if isDrawing is true. If not, it returns. If it is, it sets the stroke style, line width, and line cap. It then starts a new path, moves to the last known coordinates, draws a line to the current coordinates, and strokes the path. Finally, it updates the last known coordinates.
  • stopDrawing(): Sets isDrawing to false.

Step 5: Event Listeners

Now, let’s add event listeners to handle mouse events and the clear button click.


canvas.addEventListener('mousedown', (e: MouseEvent) => {
    startDrawing(e.offsetX, e.offsetY);
});

canvas.addEventListener('mousemove', (e: MouseEvent) => {
    draw(e.offsetX, e.offsetY);
});

canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);

clearButton.addEventListener('click', () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
});

Explanation:

  • mousedown: When the mouse button is pressed down on the canvas, the startDrawing function is called.
  • mousemove: When the mouse moves on the canvas, the draw function is called.
  • mouseup and mouseout: When the mouse button is released or the mouse leaves the canvas, the stopDrawing function is called.
  • click on the clear button: Clears the canvas using ctx.clearRect().

Step 6: Touch Event Support

To make the app work on touch devices, we need to add touch event listeners.


canvas.addEventListener('touchstart', (e: TouchEvent) => {
    e.preventDefault(); // Prevent scrolling on touch devices
    const touch = e.touches[0];
    if (touch) {
        startDrawing(touch.clientX - canvas.offsetLeft, touch.clientY - canvas.offsetTop);
    }
});

canvas.addEventListener('touchmove', (e: TouchEvent) => {
    e.preventDefault();
    const touch = e.touches[0];
    if (touch) {
        draw(touch.clientX - canvas.offsetLeft, touch.clientY - canvas.offsetTop);
    }
});

canvas.addEventListener('touchend', stopDrawing);
canvas.addEventListener('touchcancel', stopDrawing);

Explanation:

  • touchstart: When a touch starts on the canvas, it calls startDrawing, using the touch coordinates. We use e.preventDefault() to prevent scrolling.
  • touchmove: When a touch moves on the canvas, it calls draw, using the touch coordinates. e.preventDefault() prevents scrolling.
  • touchend and touchcancel: When a touch ends or is cancelled, the stopDrawing function is called.

Step 7: Compile and Run

Save all the files. Now, compile your TypeScript code using the TypeScript compiler:

tsc

This command will generate a dist/index.js file. Open index.html in your web browser. You should now be able to draw on the canvas.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid or fix them:

  • Incorrect Element References: Make sure you are correctly referencing the HTML elements by their IDs. Typos can lead to null values and errors. Double-check your element IDs in both the HTML and TypeScript code.
  • Canvas Context Errors: If you get an error related to getContext('2d'), ensure the canvas element exists in your HTML and that you are accessing the context correctly. Also, make sure the canvas element is fully loaded before trying to access the context.
  • Incorrect Coordinate Calculations: When handling touch events, make sure you’re calculating the correct coordinates relative to the canvas. Using clientX and clientY needs to be adjusted by subtracting the canvas’s offsetLeft and offsetTop.
  • Line Drawing Issues: If your lines aren’t drawing correctly, check your strokeStyle, lineWidth, and lineCap properties. Make sure isDrawing is correctly toggled.
  • Type Errors: TypeScript helps prevent many errors. Pay attention to type errors in your IDE and fix them accordingly. Make sure your variables and function parameters have the correct types.
  • Missing Imports: If you are using modules, make sure you import them correctly.

Enhancements and Next Steps

This is a basic drawing app. Here are some ideas for enhancements:

  • Color Picker Improvements: Add more color options, such as a color palette or a color wheel.
  • Different Brush Types: Implement different brush styles (e.g., pen, brush, eraser).
  • Shape Drawing: Allow users to draw shapes like rectangles, circles, and lines.
  • Saving and Loading: Add functionality to save the drawing as an image and load images.
  • Undo/Redo Functionality: Implement undo and redo features.
  • Responsiveness: Improve the app’s responsiveness for different screen sizes.

Key Takeaways

In this tutorial, you’ve learned how to build a simple drawing app using TypeScript, HTML, and CSS. You’ve gained experience with:

  • Setting up a TypeScript project.
  • Working with the HTML canvas element.
  • Handling mouse and touch events.
  • Using the Canvas API for drawing.
  • Creating a basic user interface.

This project is a great foundation for further exploration in web development and TypeScript. By experimenting with the enhancements suggested above, you can deepen your understanding and create more sophisticated applications.

FAQ

Q: How do I handle touch events?

A: Use the touchstart, touchmove, touchend, and touchcancel event listeners. Remember to prevent default behavior (e.preventDefault()) to avoid scrolling issues on touch devices. Access the touch coordinates using e.touches[0].clientX and e.touches[0].clientY, and adjust them relative to the canvas using canvas.offsetLeft and canvas.offsetTop.

Q: Why is my drawing not smooth?

A: Ensure you are using the correct event listeners (mousemove, touchmove) to continuously draw as the mouse/touch moves. Make sure your draw() function is correctly calculating the coordinates and drawing lines based on the current and previous mouse/touch positions. Check the lineCap property and set it to “round” for smoother lines.

Q: How can I clear the canvas?

A: Use the clearRect() method of the canvas context. For example: ctx.clearRect(0, 0, canvas.width, canvas.height);. This clears the entire canvas.

Q: How do I change the drawing color?

A: Use a color picker input in your HTML (<input type="color" id="colorPicker">). In your TypeScript code, get a reference to this element. Then, in your draw() function, set the strokeStyle of the canvas context to the value of the color picker: ctx.strokeStyle = colorPicker.value;.

Q: How do I handle different line widths?

A: Add a number input field in your HTML (<input type="number" id="lineWidth">). Get a reference to it in TypeScript. In your draw() function, set the lineWidth of the canvas context to the value of the number input: ctx.lineWidth = parseInt(lineWidthInput.value, 10);. Make sure to parse the input value to an integer.

Building a drawing application is a rewarding project that allows you to combine various web development skills. It’s a journey of learning, experimentation, and creativity. As you refine your app, you’ll gain a deeper understanding of TypeScript, canvas manipulation, and the art of crafting interactive web experiences. The possibilities are endless, and the satisfaction of building something from scratch is immense. So, continue to experiment, explore, and most importantly, enjoy the process of bringing your digital art to life.