Canvas & Input – Real-World PWA: The Making Of Paint.Js.Org – Part 2

After introducing into the project about the web-based Microsoft Paint clone in the first part of this series and talking about the choice of Web Components and the architecture of paint.js.org, I now want to demonstrate how I implemented the drawing functionality.

In diesem Artikel:

cl-neu
Christian Liebel ist Consultant bei Thinktecture. Sein Fokus liegt auf Webstandards und Progressive Web Apps. Er vertritt Thinktecture beim W3C.

Canvas: A Bitmap for the Web

First, you need an element to draw on. Fortunately, the web provides a canvas element for this purpose. It was introduced with HTML5, and support for it is shipped in all major browsers for over a decade now. It provides a two-dimensional bitmap that can be drawn on using a low-level JavaScript API.

				
					<canvas class="main" width="300" height="300" style="image-rendering: pixelated"></canvas>
				
			

A canvas has a certain width and height, specified in logical pixels via HTML attributes. By using CSS, you can specify the width and height of the canvas element in CSS pixels. By combining the two, you can support high-definition („retina“) displays that use more than one physical for showing an actual CSS pixel. When Paint was created, high DPI screens weren’t a thing, so only logical pixels will be used. By setting the CSS property image-rendering to pixelated, drawings will appear pixelated on high DPI screens and not blurry.

Contexts: Choose the Right Tool for the Right Job

To draw on the canvas, you can make use of different rendering contexts. They provide a set of tools to set the pixels of the canvas. The WebGL 2.0 and WebGL contexts can be used to render 3D visualizations to the canvas, while the 2D context provides 2D operations such as drawing a rectangle or a line. To obtain a context, you call the getContext() method and pass the context identifier to it:

				
					const canvas = this.shadowRoot.querySelector('canvas.main') as HTMLCanvasElement;
const context = canvas.getContext('2d');
				
			

Our Paint sample makes use of the 2D drawing context. As most of Paint’s tools don’t support anti-aliasing, the clone will primarily use the fillRect() method to fill entire pixels. The following code is called in order to clear all pixels on the canvas:

				
					context.fillStyle = colors.secondary;
context.fillRect(0, 0, canvas.width, canvas.height);
				
			

Moreover, the clone makes use of Jack Bresenham’s algorithms to paint rasterized lines and ellipses.

Speed up Drawing with a Desynchronized Canvas

For additional drawing performance, I activated low-latency rendering. Per default, the canvas synchronizes with the DOM on graphics updates. This can cause latencies leading to problems with eye-hand coordination, thus making drawing more difficult.

By enabling low-latency rendering, DOM synchronization is skipped, which leads to a more natural experience when drawing. At the time of this writing, low-latency rendering is only supported in Google Chrome for ChromeOS and Windows. Opting-in is very easy: When retrieving the context, you can pass different parameters in a configuration object. To enable low-latency rendering, you have to set the desynchronized hint to true.

				
					const context = canvas.getContext('2d', { desynchronized: true });
				
			

If the browser or platform does not support low-latency rendering, the hint will simply be ignored. Although low-latency rendering is not available on Android and iOS, I didn’t notice any performance penalties there.

Pointer Events: Pen, Finger, and Mouse under One Roof

With the introduction of Windows 8, Microsoft implemented the so-called Pointer Events in Internet Explorer 10. Pointer Events provide an abstract interface for recognizing user input from various pointing devices, such as a mouse, pen, or touchscreen. Before Pointer Events, developers had to register for events from different input devices separately. Pointer events are supported by all major browsers for some time. As the Paint clone should be truly cross-platform, all different pointing devices should be supported.

The clone registers for Pointer Events to recognize input from all pointing devices at once: The pointerdown event is raised when the finger or pen was placed on the screen, or a mouse button was pressed. The pointermove event is called when the finger, pen, or mouse is moved across the screen. Finally, the pointerup event is raised when the user lifts their finger or pen again or the pressed mouse button is released.

With this article’s knowledge, you should now understand the implementation of the Pencil tool from the code below. The line() method implements Bresenham’s line algorithm noted above.

				
					export class PencilTool implements Tool {
  private previous: Point = { x: 0, y: 0 };

  onPointerDown(x: number, y: number, { context }: DrawingContext, color: ToolColor): void {
    if (context) {
      context.fillStyle = color.stroke.value;
      context.fillRect(x, y, 1, 1);
      this.previous = { x, y };
    }
  }

  onPointerMove(x: number, y: number, { context }: DrawingContext): void {
    line(this.previous.x, this.previous.y, x, y, (x, y) => {
      context?.fillRect(x, y, 1, 1);
    });
    this.previous = { x, y };
  }
}
				
			

By combining the HTML canvas element, the 2D drawing context, and Pointer Events, the core functionality of the Paint clone can be implemented. Now that you can actually draw paintings, it makes sense to share them with other applications.

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Low-angle photography of metal structure
AI
cl-neu

AI-Funktionen zu Angular-Apps hinzufügen: lokal und offlinefähig

Künstliche Intelligenz (KI) ist spätestens seit der Veröffentlichung von ChatGPT in aller Munde. Wit WebLLM können Sie einen KI-Chatbot in Ihre eigenen Angular-Anwendungen integrieren. Wie das funktioniert und welche Vor- und Nachteile WebLLM hat, lesen Sie hier.
26.02.2024
.NET
cl-neu

Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Blazor WebAssembly is a powerful framework for building web applications that run on the client-side. With Project Fugu APIs, you can extend the capabilities of these apps to access new device features and provide an enhanced user experience. In this article, learn about the benefits of using Project Fugu APIs, the wrapper packages that are available for Blazor WebAssembly, and how to use them in your application.

Whether you're a seasoned Blazor developer or just getting started, this article will help you add superpowers to your Blazor WebAssembly app.
28.02.2023
Project Fugu
cl-neu

Dark Mode Support – Real-World PWA: The Making Of Paint.Js.Org – Part 5

In part five of the series about the making of the web-based Microsoft Paint clone paint.js.org, I want to show how to implement support for dark mode in your web applications.
20.05.2021
Project Fugu
cl-neu

Accessing Files & File Handler – Real-World PWA: The Making Of Paint.Js.Org – Part 4

In this fourth part of the series about the Microsoft Paint remake on paint.js.org, I want to demonstrate how you can save your drawings to your local disk, read them back later and how to add your web app as a handler for certain file extensions.
12.05.2021
Project Fugu
cl-neu

Copy & Paste Images – Real-World PWA: The Making Of Paint.Js.Org – Part 3

In part three of the series about the making of the web-based Microsoft Paint clone paint.js.org, I want to show how you can copy drawings from the Paint clone to other applications and paste them back.
27.04.2021
Project Fugu
cl-neu

Overview, Web Components & Architecture – Real-World PWA: The Making Of Paint.Js.Org – Part 1

Progressive Web Apps and the new powerful web APIs provided by Project Fugu allow developers to implement desktop-class productivity apps using web technologies. In this six-part article series, Christian Liebel shows you the critical parts of how paint.js.org was made, a web-based clone of the productivity app dinosaur Microsoft Paint. In this first article, Christian gives you an overview of the project, explains the choice of Web Components, and discusses the basic app architecture of the web-based Microsoft Paint clone.
07.04.2021