How We Built a Bilingual Color Blindness Test on Canvas
A complete walkthrough of building a web-based Ishihara color blindness test with dynamic Canvas plate generation, multi-type question bank, bilingual support, and the tricky JavaScript scoping bug that almost broke everything.
1. Why Build a Color Blindness Test Tool?
Color vision deficiency affects approximately 8% of men and 0.5% of women worldwide. Yet most people never get tested — they simply live their lives unaware that they see colors differently. When we decided to add an interactive tool to AIGoMoon, the color blindness test was a natural choice: it is engaging, educational, and genuinely useful.
The goal was ambitious: build a web-based Ishihara-style test that works on both desktop and mobile, supports English and Chinese, generates its own test plates dynamically, and provides personalized results with lifestyle advice. No frameworks, no backend — just pure HTML, CSS, and JavaScript.
2. Requirements & Technical Approach
Before writing a single line of code, we mapped out the core requirements:
- Dynamic plate generation — Each test plate must be generated with Canvas, creating dot patterns that form numbers, letters, or animals
- Multiple color blindness types — Cover red-green, blue-yellow, and total color blindness scenarios
- 4-option multiple choice — One correct answer, two distractors, one "I can't see" option
- Auto-advance — Clicking an option immediately moves to the next question, no "Next" button needed
- Bilingual support — Full English/Chinese switching with no page reload
- Responsive design — Looks great on both desktop and mobile browsers
- Favorites & click tracking — Save favorites to browser cookies, track usage via Cloudflare KV
We chose vanilla JavaScript for maximum compatibility and zero build complexity. The entire tool lives in a single HTML file with an inline script — easy to deploy, easy to maintain.
3. Building the Test Plates with Canvas
The most technically interesting part is generating Ishihara-style test plates. The classic approach uses two color palettes: a "background" palette and a "figure" palette. People with normal vision can distinguish the figure from the background; those with specific color deficiencies cannot.
Here is the core algorithm:
- Define a shape (number, letter, or animal silhouette) using a hidden canvas
- Fill the circular plate area with randomly sized and positioned dots
- For each dot, check if its center falls inside the shape — if yes, use the figure palette; if no, use the background palette
- Add subtle color variation within each palette to make the pattern look natural
The key insight is that the dot positions are random, but the color assignment is deterministic based on the shape mask. This creates the characteristic Ishihara look where a number "emerges" from the dot field for viewers with normal color vision.
4. Designing the Question Bank
We created 12 test plates covering three categories:
- Numbers — Single digits (3, 5, 6, 8), two-digit numbers (29, 74), and four-digit numbers (4286, 7395)
- Letters — C, E, and other characters that are distinguishable in dot form
- Animals — Butterfly, camel, giraffe, fish, whale, and more complex silhouettes
Each plate has a carefully chosen color pair. For example, red-green plates use red dots for the figure and orange-green dots for the background — people with protanopia or deuteranopia struggle to distinguish these. Blue-yellow plates test for tritanopia, a rarer form of color deficiency.
The 4-option format includes one correct answer, two plausible distractors (similar numbers or animals), and one "I can't see" option. This last option is crucial — it allows users to honestly report that they cannot distinguish the figure, which is itself a diagnostic signal.
5. The Result Algorithm
After all 12 questions, the tool analyzes the answer pattern to determine the user's color vision type. The algorithm tracks which plates were answered correctly and which were missed, then maps the pattern to one of several outcomes:
- Normal color vision — Most answers correct, including the tricky plates
- Possible red-green deficiency — Missed red-green plates but got blue-yellow ones right
- Possible blue-yellow deficiency — The opposite pattern
- Possible total color blindness — Missed most or all plates
Each result includes a detailed explanation and personalized lifestyle advice — from professional diagnosis recommendations to daily living tips and assistive technology suggestions. This transforms the tool from a fun quiz into a genuinely helpful health resource.
6. The Bilingual Challenge
Supporting two languages sounds simple, but it caused the most frustrating bug in the entire project. The website's main.js defines a translation dictionary using const I18N = {...}. The color blindness test's t() function checks for window.I18N to get the current language's translations.
Here is the problem: in JavaScript, const declarations at the top level of a script do not become properties of the window object. So window.I18N was always undefined, and the test fell back to its English default dictionary — even when the user switched to Chinese!
The fix was deceptively simple: add two lines at the end of main.js:
window.I18N = I18N;
window.currentLang = currentLang;
This explicitly exposes the variables to the global scope, making them accessible to the inline script in the test page. We also added missing translation keys for the result type names (protanopia, deuteranopia, tritanopia, etc.) to ensure complete coverage.
The lesson: always verify that variables you expect to be global actually are global. A 30-second check in the browser console would have saved hours of debugging.
7. Mobile Responsiveness & UI Polish
Mobile testing revealed several issues. The favorite banner was getting cut off at the top of the screen — only half visible. We fixed this by adjusting the z-index and positioning, ensuring the banner appears fully below the navigation bar.
The test card uses a soft cream background (#faf8f3) with rounded corners and a subtle shadow, creating a warm, approachable feel. The color palette of the UI itself draws from the test plates — warm oranges, soft reds, and gentle blues — reinforcing the theme without being overwhelming.
Buttons use a gradient from deep red to indigo blue for primary actions, with clear hover and active states. The progress bar at the top of the test card gives users a sense of how far they have progressed.
8. Favorites & Analytics
Users can favorite the tool with a single click. The favorite status is stored in a browser cookie, so it persists across sessions. Click usage is tracked via Cloudflare KV storage through a serverless function, allowing us to measure popularity and inform future feature development.
The "Restart" and "Exit" buttons are placed below the test card, styled distinctly — blue for restart, red for exit — so users can always find them regardless of which screen they are on.
9. Final Result
The finished tool is a polished, accessible color blindness test that anyone can use in their browser. It generates its own test plates, supports two languages, works on any device, and provides meaningful results with actionable advice.
The development process taught us valuable lessons about JavaScript scoping, Canvas graphics, and the importance of real-device testing. Most importantly, it showed that a seemingly simple tool can require surprising depth — and that the best debugging tool is often a fresh pair of eyes (or a console.log).
Try it yourself at aigomoon.com/color-blind-test — you might discover something about your own vision!