Final Degree Project

Canvas UI - Open source package

JavaScript
HTML Canvas

Overview

For the final project of my computer science degree, I challenged myself to build a JavaScript library that could simplify the process of creating user interfaces using the HTML5 canvas element. Rather than following the traditional DOM-based approach, I wanted to explore canvas as the sole rendering surface and build an abstraction layer on top of it. Inspired by the architecture used in Android Studio, the library I built introduced a structured and modular way of defining graphical interfaces, making canvas-based development far more intuitive and reusable.

The Problem

The HTML5 canvas element offers a powerful, low-level API for drawing graphics, but it lacks structure for creating complex, interactive user interfaces. Unlike the DOM, canvas has no concept of layout, events tied to visual elements, or reusable components. Every pixel and interaction must be manually calculated and managed, making it error-prone and difficult to scale. I found this especially limiting when trying to build interactive games or UI-heavy applications. There was no clean way to define layouts, position elements relative to each other, or detect common events like clicks on specific components—all of which are essential when building more than just simple drawings or animations.

What I Built

To solve this, I built a JavaScript library that introduced a structured, Android-inspired architecture for creating user interfaces directly on the canvas. At its core, the system revolved around three main types of elements:

  • Views: Basic graphical elements like text, images, or shapes.
  • Layouts: Containers that arrange elements using layout rules such as linear, grid, or relative positioning.
  • Composites: Reusable components built by combining multiple views and layouts into one element.

Each element came with a set of configurable properties that defined its visual appearance and interactive behavior. When placed within a layout, elements could be assigned layout-specific parameters to control positioning—such as row and column placement, alignment, or relative anchoring to other elements. This allowed for a high degree of flexibility when building dynamic, structured interfaces on a canvas.

To make this possible, I designed a lifecycle system that every element followed. The lifecycle had three distinct phases:

  • Measure: Each element calculated its size based on its own properties and the maximum available space.
  • Locate: Each layout computed the coordinates of each element, applying alignment, anchoring, or grid/linear rules to place them correctly.
  • Draw: Each element rendered itself onto the canvas, using its own drawing logic.

This structured flow turned what would normally be imperative, repetitive canvas code into a predictable system, where building new elements meant just defining how they behave in each phase.

The result was a library that not only shipped with built-in components such as text, images, and layout containers, but also empowered developers to create their own custom elements with unique sizing, positioning, and rendering behavior.

Results

With this system in place, I was able to build three complete games that demonstrated the library's versatility: a puzzle, a tetris, and a sudoku. What would have been a tedious and error-prone process using raw canvas drawing became much simpler and more maintainable thanks to the architectural foundation the library provided. Creating interactive layouts, handling user input, and updating the display became a matter of defining components and their relationships, rather than writing imperative code for every action and redraw.

Takeaway

This project taught me the value of creating structured systems in environments that don't natively support them. By taking inspiration from platforms like Android and applying those patterns to the canvas context, I was able to bridge the gap between low-level rendering and high-level UI development. The project became a testament to how architectural thinking can transform even the most unstructured platforms into something developer-friendly and scalable.