Skip to content

detectColorScheme() treats transparent body background as dark — player invisible on white pages in light mode #21

Description

@sporteka2

Summary

When a page does not set an explicit background color on body (relying on the UA color-scheme/canvas background or a <meta name="color-scheme" content="light dark">), the auto theme detector classifies the page as dark even when the visible page is white and the OS is in light mode. The player then applies the dark preset (white controls/waveform rgba(255,255,255,…)) on a white page and becomes invisible.

Package: @arraypress/waveform-player v1.24.0 · component: core player auto-theme detection (src/js/themes.js)

Root cause

In detectColorScheme():

function detectColorScheme() {
  if (hasThemeHint("dark")) return "dark";
  if (hasThemeHint("light")) return "light";
  try {
    const bodyBg = getComputedStyle(document.body).backgroundColor;
    const brightness = perceivedBrightness(bodyBg);
    if (brightness !== null) {
      if (brightness > 128) return "light";
      if (brightness < 128) return "dark";   // ← always taken for transparent bg
    }
  } catch (e) {}
  if (window.matchMedia) { /* prefers-color-scheme … never reached */ }
  return "dark";
}

perceivedBrightness():

function perceivedBrightness(color) {
  const rgb = typeof color === "string" ? color.match(/\d+/g) : null;
  if (!rgb || rgb.length < 3) return null;
  const [r, g, b] = rgb.map(Number);          // alpha digit ignored
  return (r * 299 + g * 587 + b * 114) / 1e3;
}

With no background set, getComputedStyle(document.body).backgroundColor returns rgba(0, 0, 0, 0). The regex yields [0,0,0,0]; the alpha channel is ignored, so brightness = (0·299+0·587+0·114)/1000 = 0, which is < 128"dark". The prefers-color-scheme: light branch is never reached.

It is also sticky: _scheme is resolved once at construction, and re-detection (refreshTheme()) only runs from a MutationObserver (class/data-theme/data-color-scheme/style attribute changes) and a matchMedia change event, so switching the OS theme doesn't fix it either.

Repro

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <meta name="color-scheme" content="light dark">
  <title>Repro</title>
  <link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-player/dist/waveform-player.css">
  <script src="https://unpkg.com/@arraypress/waveform-player/dist/waveform-player.min.js"></script>
</head>
<body>
  <div data-waveform-player data-src="https://example.com/track.mp3"></div>
</body>
</html>

Open with the OS in light mode. The player renders with white controls on a white page (invisible). Adding body { background: #fff } (so the computed background is rgb(255,255,255)) makes it correctly detect "light".

Expected

  • A transparent (fully invisible, alpha 0) background should be treated as "unknown" so detection falls through to prefers-color-scheme.
  • perceivedBrightness() should parse rgb()/rgba() properly (including the alpha channel) instead of picking up the alpha digit as a 4th match and ignoring it.

Suggested fix

function perceivedBrightness(color) {
  if (typeof color !== "string") return null;
  const m = color.match(/rgba?\(([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s]+([\d.]+))?/);
  if (!m) return null;
  const [, r, g, b, a = 1] = m.map(Number);
  if (a <= 0) return null; // transparent → unknown, defer to prefers-color-scheme
  return (r * 299 + g * 587 + b * 114) / 1e3;
}

Environment

  • Browser: Firefox (incl. Firefox Dev), Chromium
  • OS: Linux
  • Version: 1.24.0 (also reproduced against the unpkg CDN build)

Workarounds

  • Set an explicit background on body for both schemes (e.g. #fff / dark).
  • Pass data-color-preset="light" / data-color-preset="dark" to opt out of auto-detection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions