惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
T
The Blog of Author Tim Ferriss
H
Help Net Security
博客园 - 叶小钗
云风的 BLOG
云风的 BLOG
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
D
DataBreaches.Net
博客园 - 聂微东
A
About on SuperTechFans
大猫的无限游戏
大猫的无限游戏
P
Proofpoint News Feed
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
B
Blog
Engineering at Meta
Engineering at Meta
V
V2EX
Hugging Face - Blog
Hugging Face - Blog

Red Blob Games: latest blog posts

Red Blob Games: Differential heuristics Red Blob Games: Responsive design calculator Red Blob Games: Academic citations Red Blob Games: Optimizing page size Red Blob Games: Writing a guide to SDF fonts Red Blob Games: URLs with trailing punctuation Red Blob Games: What I did in 2025 Red Blob Games: Goodbye Sass Goodbye Sass Red Blob Games: RotMG map seeds Red Blob Games: Mapgen4 Mapgen4's use of WebGL2 Red Blob Games: Mapgen4 river shader Red Blob Games: Mapgen4 renderer Red Blob Games: Harnessing ChatGPT hallucinations Red Blob Games: Let Let's write a search engine, part 2 of 2 Red Blob Games: Let Let's write a search engine, part 1 of 2 Red Blob Games: Hexagon conversions Red Blob Games: Mapgen4 trade routes Red Blob Games: De-optimizing mapgen4 Red Blob Games: Hexagon spiral coordinates Red Blob Games: Thoughts on Flash Red Blob Games: What I did in 2024 Red Blob Games: Hexagon page animations Red Blob Games: SDF Halos SDF Halos Red Blob Games: SDF headless tests, part 2 Red Blob Games: SDF headless tests, part 1
Red Blob Games: Highlighting interactive code blocks
2026-04-17 · via Red Blob Games: latest blog posts
Blog post: 16 Apr 2026

I have sample code on some of my pages. I try to use syntax highlighting when I can, but I don’t use it everywhere. In particular, Hexagons guide and Terrain from Noise don’t have syntax highlighting. Why?

(screenshot of sample code without highlightint) (screenshot of sample code with an interactive element)
Sample code with no highlighting

It’s a lot of work to manually syntax highlight everything, and it makes my HTML harder to read and maintain:

<code>q + s + r</code><code><span class="q">q</span> + <span class="s">s</span>
 + <span class="r">r</span> = 0</code>

Why not use a syntax highlighting library? These libraries take text as input and produce HTML as output. But on these interactive pages, the input is HTML. I have manually marked up the code with embedded interactive elements that respond to the reader’s choices:

html-merge.png

The syntax highlighting libraries I looked at don’t work on HTML out of the box. It’s possible to merge two highlighters together but it can be tricky. Let’s see what the merging looks like. To keep the examples concise, I’ll use <b> for tags I added manually and <i> for tags the highlighter added:

  1. highlighting tags <i> enclosing the manual markup <b>:

    function <b>abc</b>_to_hex(p):
    →
    <i>function</i> <i><b>abc</b>_to_hex</i>(p):
    <i>        </i> <i><b>   </b>       </i>
                       [--------]
                    [----------------------]
    
  2. manual markup <b> enclosing the highlighting tags <i>:

    <b>hex.q + 1</b><b>hex.<i>q</i> + 1</b>
    <b>    <i> </i>    </b>
           [------]
    [---------------------]
    
  3. manual markup and highlighting tags crossing:

    var y_<b>prev = 3</b><i>var</i> <i>y_<b>prev</i> = 0</b>
    <i>   </i> <i>  <b>    </i>    </b>
               [--------------]
                    [-----------------]
    

    Oops, that’s not right. Tags must be nested correctly. The </i> can’t be until after the </b>. We have to split the <b> tag here:

    <i>var</i> <i>y_<b>prev</b></i><b> = 0</b>
    <i>   </i> <i>  <b>    </b></i><b>    </b>
                    [---------]    [---------]
               [------------------]
    

    Note that splitting it will break hover and some other interactions.

On the A* page I wrote a highlighter that takes HTML input and produces HTML output. It works for the samples on the A* page, but doesn’t work on code in general. It’s simple but fragile.

html-to-html.png
(screenshot)
Highlighting non-interactive code

The code samples on the A* page aren’t interactive. Interactivity complicates things. Actions on the page can change the text of the code being displayed. The simplest thing is to highlight it again when the code changes:

interactive-loop.png

In an interactive system, the DOM has more than just the HTML: it can have event handlers, focus status, hover state, text selection, pointer capture, and other things. All of that is lost when we generate HTML from scratch. This type of problem is partly solved with a Virtual DOM library (React/Vue/etc.) that attempts to reuse existing DOM nodes where possible.

I wasn’t sure how best to solve this problem so I decided to study the modules of an existing syntax highlighting library:

  1. Tokenize: find the ranges in the text that need to be highlighted.
  2. Markup: surround those ranges with markup such as <span class=keyword>.
  3. Style: use CSS to apply colors to span.keyword.

I wanted to replace step 2 of an existing library with the new CSS Custom Highlight API[1], available in mid-2025. This is a non-destructive approach to highlighting, similar to how selecting text applies styling to the selected text without modifying the HTML. However, the Custom Highlight API also requires new CSS rules, so I have to replace step 3. And it turns out the only part I could reuse, step 1, is tiny (under 30 lines). So I decided to write everything myself:

  1. Convert the DOM element to plain text using el.textContent. Don’t make the same mistake I did of using el.innerText. That is almost right but has some differences. For example, <br> has text content "" and inner text "\n". That leads to off-by-one errors in a later step.
  2. Tokenize the plain text, the same way a syntax highlighter library would. I looked at libraries such as rse/tokenizr[2] and no-context/moo[3] but they are overkill for my needs, around 10✕ as much code as I ended up with.
  3. Construct Range[4] objects for each token. The tokenizer gives me an index into the plain text but I need to convert that into an index into the specific DOM node. I can walk the tree with document.createTreeWalker() until I find the correct node.
  4. Create a Highlight[5] object to hold all the ranges for each token type (comment, keyword, etc.). Add the ranges to that highlight object.
  5. Write CSS rules ::highlight(keyword) for each token type. There are limitations on the styling. These rules can’t cause a reflow, so I can change foreground/background but not bold or any other change in fonts.
  6. Use MutationObserver[6] to watch for changes to the DOM element. When the text changes, create new Range objects to replace the existing ones. One limitation of this is that I’m watching the existing <pre> elements, not watching for new or removed <pre> elements. I made sure the page doesn’t create any after initial load.

This worked really well! I can apply highlighting without messing up any of the interactivity.

On the Hexagons guide I decided to primarily use highlighting for non-syntax (variable names) rather than syntax (keywords, comments, etc.). The diagrams use three colors for the three axes of a hexagonal grid, and I use the same colors for the variables representing those axes:

(screenshot with highlighting)
Highlighting using custom highlight api

The code highlighting on the Hexagons guide is now live, with 829 tokens highlighted. Try it out! If you want to see my code, it’s code-highlighting.js.