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, I want to show how you can copy drawings from the Paint clone to other applications and paste them back.

In diesem Artikel:

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

In the previous part of this series, I showed how you can use the HTML canvas element to implement the drawing area. In order to copy and paste images, the pixel-based image data needs to be converted to and from an exchange format such as PNG. For the copy and paste part, I will demonstrate the use of the Async Clipboard API. This API is supported on Google Chrome, other Chromium-based browsers such as Opera or Edge since version 62, and on Apple Safari starting from version 13.1 (support table on At the time of this writing, Mozilla Firefox only allows writing plain text to the clipboard.

Image to Blob

Before we can copy to another application, it needs to be converted to an exchange format. The most compatible format that can be used in combination with the Async Clipboard API across all platforms is the PNG format (Portable Network Graphics). The canvas API offers a toBlob() method that creates a binary-large object (BLOB) from the current image data of the canvas. This method takes a callback receiving the generated blob as a parameter. It also allows specifying the target format and image quality. Unless specified, the target format is PNG.

					canvas.toBlob(blob => {
  /* do something with the blob */

Copying Images to the Clipboard

The blob can now be written to the clipboard. The Async Clipboard API offers the writeText() and write() methods on the navigator’s clipboard object to copy data. While the first method is a convenient method to write plain text to the clipboard, the second one can be used to copy arbitrary data—as long as the browser or platform supports it. Currently, Safari only supports plain text, HTML, URI lists, and PNG data.

The write() method takes an array of clipboard items. They are created by calling the ClipboardItem() constructor that takes an object containing one or more representations of the item, with the representation’s MIME type as a key. The write() method returns a promise that resolves when copying was successful and rejects if it wasn’t. For security reasons, you may only invoke this action as a part of a user gesture (i.e., a keypress or a click). Depending on the browser, the user may need to give their permission first.

					canvas.toBlob(async (blob) => {
  await navigator.clipboard.write([
    new ClipboardItem({ [blob.type]: blob })

That’s all. With the help of Async Clipboard API, you can now copy your drawing to another application—for example, the macOS app Preview.

Pasting Images from the Clipboard

Let’s say you edited the image in the other application and want to paste the result back to the application, or you took a screenshot and want to edit it in Paint. In this case, the read() method of the Async Clipboard API is used. Again, the API also provides a readText() method for convenience. Reading back the data is a little more complicated, as you need to iterate over the list of clipboard items and their representations and pick the ones that match. Again, you may only invoke this action as a part of a user gesture, the user may be asked to allow reading from the clipboard, and the supported formats vary between browsers.

The following sample iterates over the items in the clipboard and their types. Whenever it finds a clipboard item with the image/png MIME type, it retrieves the blob data by calling the getType() method on the clipboard item.

					const items = await;
for (const item of items) {
    try {
      for (const type of image.types) {
          if (type === 'image/png') {
              const blob = await item.getType(type);
              /* draw image data from blob */
    } catch (err) {

Blob to Image

In order to draw the image to the canvas, its pixel data must first be extracted from the exchange format in the blob. This is where the Image() constructor comes into play. It creates a new instance of the HTMLImageElement (<img>). This can be used to load an image from a URL. The canvas API provides a drawImage() method that takes an HTMLImageElement as a parameter.

However, as the image was pasted from the clipboard, there’s no URL that could be used as the image source. Fortunately, with URL.createObjectUrl(), the web provides a method to create temporary URLs that point to a blob in memory. After the image was successfully loaded, the temporary URL is revoked to prevent memory leaks, and the image data can be drawn on the canvas using the drawImage() method.

					const image = new Image();
image.onload = () => {
  context.drawImage(image, 0, 0);
image.src = URL.createObjectURL(blob);

Et voilà! You can now paste images from other applications back to the Paint clone. In the sample application, this can either be done by pressing Ctrl+C/Ctrl+V, or by selecting the copy and paste entries in the application menu.

In the next part of this series, I want to show you how to save the drawings to the local file system and how to read them back. Furthermore, the Paint clone will be registered as a file handler for PNG files.


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-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.

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.

Implementing Smart and Presentational Components with Angular: Condensed Angular Experiences – Part 4

In this article, we will explore how to apply the concept of smart and presentational components with Angular. We will choose a complex-enough target to see all aspects in action, yet understandable and within the scope of this article. The goal is to teach you how to use this architecture in your way. For that, we will iterate through different development stages, starting with the target selection and implementing it in a naive way. After the first development, we will refactor that naive solution into smart and presentational components that are reusable, refactor-friendly, and testable.
Web Components

Master Web Component Forms Integration – with Lit and Angular

When a company has cross-framework teams, it is a good choice to use Web Components to build a unified and framework-independent component library. However, some pitfalls are to consider when integrating these components into web forms. Therefore, for a better understanding, we will look at two possible approaches and try to integrate them into an Angular form as an example.

Notice: All code samples are available on Github!

About Smart and Presentational Components, Architecture Overview & Concepts: Condensed Angular Experiences – Part 3

Modern web technologies enable us to write huge business applications that are performant and easy to use. But with time comes complexity to our projects naturally. That added complexity sums up and makes further app development slow and cumbersome. This article discusses how a simple architectural concept can make every app more maintainable, faster to develop, and extendable in the long run.
Project Fugu

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, I want to show how to implement support for dark mode in your web applications.