The team that brought you squoosh.app is back! This time, we built a web-based game called PROXX (proxx.app). PROXX is a game of proximity inspired by the legendary game Minesweeper. The game is situated in the space and your job is to find out where the black holes are. It works on all kinds of devices—from desktop all the way to feature phones. Users can play the game using a mouse, keyboard, d-pad even with a screen reader.
Before building this game, we set the following goals and budgets for the application:
- Same core experience: all devices must function the same way
- Accessible: mouse, keyboard, touch, d-pad, screen readers
- Less than 25kb of initial payload
- Less than 5 seconds TTI (time to interactive) on slow 3G
- Consistent 60fps animation
The game consists of 4 main entities, the core game logic, the UI service, the state service, and the animation graphics. Since we knew from the get-go we would have to run heavily animated graphics on the main thread, we moved the game logic and state service to a web worker in order to keep the main thread as free as possible.
Build time pre-render
Our UI is built with Preact, as it allows us to hit our aggressive target for an initial payload that is less than 25kb. In order to give a good initial loading experience, we decided to pre-render our 1st view. We prerender at build time using Puppeteer to access the top page and let preact populate the DOM. The resulting DOM is then serialized to HTML and saved as index.html
Canvas for animation, (invisible) DOM For accessibility
We render the game graphics in a canvas using WebGL. One canvas is responsible for the background animation and another one canvas for the game grid on top. We also have an HTML table with buttons for accessibility reasons, that is on top of both of these canvases, but is made invisible (opacity: 0). Even though what you see is a canvas rendering of the game state, the player is interacting with the invisible DOM table, giving us the ability to attach event listeners and rely on the browser's focus management.
By keeping the DOM element in the canvas, we are able to tap into browsers
native accessibility features. For example: by setting
role="grid" on our game
table, screen readers can announce the row and column of the focused cell
without us having to implement that.
Rollup for bundling and code splitting
Our total size for the app comes down to 100KB gzipped. Out of that, 20KB is for the initial payload (index.html). We use Rollup.js for this project. We have shared dependencies between the main thread and our web worker, and Rollup can put these shared dependencies in a separate chunk that only needs to be loaded once. Other bundlers like webpack duplicate the shared dependencies which results in double-loading.
Supporting feature phones
Smart feature phones such as KaiOS phones are rapidly gaining popularity. These are very resource constrained devices, but our approach of using web workers whenever we can allowed us to make the experience highly responsive on these phones as well. Since feature phones come with different input interface (d-pad and number keys, no touchscreen), we also implemented key-based interface.
We had great but busy time building this game in time for Google I/O 2019, so we will take some well-deserved time off to rest, but plan to come back with more in-depth documentation on each of these areas of the game.
Until then, please check the talk Mariko gave at I/O on this project.
You can browse the code at the proxx github repo.
Cheers! Surma, Jake, Mariko