<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: member_2ef2ebd8</title>
    <description>The latest articles on DEV Community by member_2ef2ebd8 (@member_2ef2ebd8).</description>
    <link>https://dev.to/member_2ef2ebd8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4079763%2F18924418-d0f1-43d0-bdb6-3cf38918d382.png</url>
      <title>DEV Community: member_2ef2ebd8</title>
      <link>https://dev.to/member_2ef2ebd8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/member_2ef2ebd8"/>
    <language>en</language>
    <item>
      <title>What the browser can actually tell you about your hardware (and what it can't)</title>
      <dc:creator>member_2ef2ebd8</dc:creator>
      <pubDate>Sun, 16 Aug 2026 06:24:10 +0000</pubDate>
      <link>https://dev.to/member_2ef2ebd8/what-the-browser-can-actually-tell-you-about-your-hardware-and-what-it-cant-2d2j</link>
      <guid>https://dev.to/member_2ef2ebd8/what-the-browser-can-actually-tell-you-about-your-hardware-and-what-it-cant-2d2j</guid>
      <description>&lt;p&gt;I spent a while building browser-based hardware diagnostics and came away with a much clearer sense of where the web platform is genuinely capable and where it quietly lies to you. Notes below, with live demos for each API so you can poke at them yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh rate: &lt;code&gt;requestAnimationFrame&lt;/code&gt; is the only signal you get
&lt;/h2&gt;

&lt;p&gt;There's no &lt;code&gt;screen.refreshRate&lt;/code&gt;. The only approach is timing &lt;code&gt;requestAnimationFrame&lt;/code&gt; callbacks and inferring the rate from the median frame delta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deltas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;deltas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deltas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two gotchas that cost me time. Use the &lt;strong&gt;median&lt;/strong&gt;, not the mean — a single dropped frame wrecks an average. And browsers throttle &lt;code&gt;rAF&lt;/code&gt; in background tabs, so the measurement is meaningless unless the tab is visible; gate it on &lt;code&gt;document.visibilityState&lt;/code&gt;. (&lt;a href="https://www.toptechs.space/tools/refresh-rate-test" rel="noopener noreferrer"&gt;live version&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen dimensions: four different answers, all "correct"
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;screen.width&lt;/code&gt;, &lt;code&gt;window.innerWidth&lt;/code&gt;, &lt;code&gt;window.devicePixelRatio&lt;/code&gt; and &lt;code&gt;screen.availWidth&lt;/code&gt; measure genuinely different things, and the one people usually want — actual native panel resolution — is &lt;code&gt;screen.width * devicePixelRatio&lt;/code&gt;. Except that's still CSS-pixel derived, so on a scaled display it can disagree with what the panel physically is. The browser simply does not expose true hardware resolution. (&lt;a href="https://www.toptechs.space/tools/screen-resolution" rel="noopener noreferrer"&gt;demo&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard: &lt;code&gt;event.code&lt;/code&gt; vs &lt;code&gt;event.key&lt;/code&gt;, and the keys you never receive
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;event.key&lt;/code&gt; is layout-dependent, &lt;code&gt;event.code&lt;/code&gt; is physical position — for a hardware tester you want &lt;code&gt;code&lt;/code&gt;. The real limitation is that some keys never reach JS at all: &lt;code&gt;PrintScreen&lt;/code&gt; often doesn't fire &lt;code&gt;keydown&lt;/code&gt;, &lt;code&gt;Meta&lt;/code&gt; combinations get swallowed by the OS, and &lt;code&gt;Fn&lt;/code&gt; isn't a browser-visible key on most laptops.&lt;/p&gt;

&lt;p&gt;N-key rollover testing works surprisingly well though, since you just track the size of the currently-held &lt;code&gt;Set&lt;/code&gt;. Cheap keyboards drop inputs at 3-4 simultaneous keys and it's very visible. (&lt;a href="https://www.toptechs.space/tools/keyboard-tester" rel="noopener noreferrer"&gt;demo&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Pointer events beat mouse events, with one catch
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pointerdown&lt;/code&gt;/&lt;code&gt;pointerup&lt;/code&gt; unify mouse, touch and pen, and &lt;code&gt;pointerId&lt;/code&gt; lets you track multi-touch properly. For detecting the classic worn-switch double-click fault, you just measure the gap between consecutive &lt;code&gt;pointerdown&lt;/code&gt; events on the same button — under ~80ms with no intervening movement is almost certainly a hardware fault rather than a human. (&lt;a href="https://www.toptechs.space/tools/mouse-test" rel="noopener noreferrer"&gt;mouse demo&lt;/a&gt;, &lt;a href="https://www.toptechs.space/tools/touch-screen-test" rel="noopener noreferrer"&gt;touch demo&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The catch: &lt;code&gt;preventDefault()&lt;/code&gt; on &lt;code&gt;pointerdown&lt;/code&gt; kills the subsequent compatibility mouse events, which will silently break anything expecting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gamepad API: polling only, and it starts asleep
&lt;/h2&gt;

&lt;p&gt;No events for axis movement — you have to poll &lt;code&gt;navigator.getGamepads()&lt;/code&gt; inside your rAF loop. Worse, controllers don't appear at all until the user presses a button, for fingerprinting reasons. That confused me for an hour: the array is full of &lt;code&gt;null&lt;/code&gt; until first input.&lt;/p&gt;

&lt;p&gt;Stick drift shows up as a resting axis value that isn't 0. Anything past about 0.08 at rest is a worn potentiometer. (&lt;a href="https://www.toptechs.space/tools/gamepad-tester" rel="noopener noreferrer"&gt;demo&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Media devices: labels are gated behind permission
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;navigator.mediaDevices.enumerateDevices()&lt;/code&gt; will happily list devices before you request permission — but every &lt;code&gt;label&lt;/code&gt; is an empty string. You only get human-readable device names after &lt;code&gt;getUserMedia()&lt;/code&gt; succeeds. So a device picker UI has to either request permission first or show useless blank entries.&lt;/p&gt;

&lt;p&gt;For mic level metering, &lt;code&gt;AnalyserNode&lt;/code&gt; with &lt;code&gt;getByteTimeDomainData&lt;/code&gt; and computing RMS is more stable than frequency data. (&lt;a href="https://www.toptechs.space/tools/mic-test" rel="noopener noreferrer"&gt;mic demo&lt;/a&gt;, &lt;a href="https://www.toptechs.space/tools/webcam-test" rel="noopener noreferrer"&gt;webcam demo&lt;/a&gt;, &lt;a href="https://www.toptechs.space/tools/speaker-test" rel="noopener noreferrer"&gt;speaker demo&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead pixels: nothing programmatic, and that's fine
&lt;/h2&gt;

&lt;p&gt;There is no API. The only viable approach is filling the viewport with solid colours via the Fullscreen API and letting the human look. Worth remembering that not every problem needs a programmatic answer — sometimes rendering &lt;code&gt;#ff0000&lt;/code&gt; across the whole screen &lt;em&gt;is&lt;/em&gt; the tool. (&lt;a href="https://www.toptechs.space/tools/dead-pixel-test" rel="noopener noreferrer"&gt;demo&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern underneath all of this
&lt;/h2&gt;

&lt;p&gt;Every hardware-adjacent API on the web has been deliberately blunted for fingerprinting resistance. You get relative measurements, permission-gated labels, and input-gated device lists. That's the right trade, but it means "detect the user's hardware" is mostly not a solvable problem — and the honest tools are the ones that measure behaviour rather than claim to read specs.&lt;/p&gt;

&lt;p&gt;All ten of these run client-side with nothing uploaded if you want to compare notes: &lt;a href="https://www.toptechs.space/tools" rel="noopener noreferrer"&gt;the full set is here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy to hear if anyone has found better approaches, particularly on refresh rate — the rAF median approach still feels like a hack.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
