Pure CSS rounding for crisp chessboard images

Immediately after deploying a responsive web design for lichess.org, users were complaining about blurry chessboards. In a fluid grid layout, the board sizes were no longer guaranteed to be a multiple of 8px! Surprisingly, the problem is worst in Chrome, which sometimes(!?) even has trouble with vector graphics.

As of 2019, CSS calc() does not support rounding. So what to do?

Note that using JavaScript would be a major performance regression. In the fixed size design the layout (including board and pieces) could be rendered before running any scripts, making for very snappy page loads.

A rendering quirk in Chrome

Because the issue is worst in Chrome, we can use another quirk of Chrome to work around it.

div.gray {
  width: 399px;
}
div.blue {
  width: 12.5%;
}
div.green {
  width: 12.5%;
  display: table;
}

12.5% of the parent 399px is evaluated to 49.875px, and Chrome happily uses subpixel rendering to show that (blue div).

However when using display:table, the width of the element is truncated to full pixels (green div).

Screenshot taken in Chrome 78, in case you're viewing in a different browser, or if rendering changes in the future:

Screenshot with display-table quirk

The horrible hack

Now that we can round to full pixels, we can also round to arbitrary full pixel intervals.

Have one container of arbitrary size.

div.gray {
  width: 399px;
}

Add a child with size 12.5%. Use the display:table quirk to round the size to full pixels (green).

div.green {
  width: 12.5%;
  display: table;
}

Add another child with size 800%, essentially computing floor(width / 8) * 8 of the outer element size in pure CSS (yellow).

div.yellow {
  width: 800%;
}

Screenshot taken in Chrome 78:

Screenshot with the rounding hack

As always, the well-known padding-bottom:100% trick can be used to make the inner-most element a square. As of May 2019 the hack is live on Lichess and the required wrapper elements are part of chessground.

JPG samples

To demonstrate the issue, here is the same JPG board with original size 800px in sizes 400px (hopefully sharp) to 393px (not consistently sharp).

SVG samples

Even scaled vector graphics can sometimes(!?) look blurry, regardless of shape-rendering="crispEdges".

Remaining issues


niklasf, 27th December 2019, comment on Twitter.