<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Building Global Data Tracker]]></title><description><![CDATA[Technical notes from building Global Data Tracker, a free website for country statistics, historical charts, comparisons, and an interactive globe. I own the pr]]></description><link>https://buildingglobaldatatracker.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Building Global Data Tracker</title><link>https://buildingglobaldatatracker.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 07:17:09 GMT</lastBuildDate><atom:link href="https://buildingglobaldatatracker.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why my interactive globe felt slow on a trackpad]]></title><description><![CDATA[I built Global Data Tracker with substantial help from Codex: a website for browsing world statistics, country profiles, and an interactive Earth. One visitor reported that zooming became frustratingl]]></description><link>https://buildingglobaldatatracker.hashnode.dev/why-my-interactive-globe-felt-slow-on-a-trackpad</link><guid isPermaLink="true">https://buildingglobaldatatracker.hashnode.dev/why-my-interactive-globe-felt-slow-on-a-trackpad</guid><category><![CDATA[ThreeJS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Benny]]></dc:creator><pubDate>Sat, 19 Sep 2026 00:33:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aadd6098666589d7dc047d5/2b9a634e-1dd3-4fe7-b4d4-df87e3fbea31.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I built Global Data Tracker with substantial help from Codex: a website for browsing world statistics, country profiles, and an interactive Earth. One visitor reported that zooming became frustratingly slow on a MacBook trackpad after selecting a country. That was useful feedback because a globe can look convincing in a screenshot while still being unpleasant to operate.</p>
<p>The implementation uses React Three Fiber and Three.js. Investigating the zoom brought together three problems: preserving small input, choosing the right distance to scale, and loading sharper imagery without interrupting movement. Here are the decisions in the current code, including what they don't establish about performance on real devices.</p>
<p>The first detail is that wheel events aren't interchangeable. The browser supplies a <code>deltaMode</code> alongside <code>deltaY</code>: pixels, lines, or pages. A trackpad can also produce fractional pixel deltas. Treating every event like one mouse-wheel notch throws away information about the gesture.</p>
<p>The normalizer keeps those fractions, converts the units into a common scale, and caps unusually large individual events:</p>
<pre><code class="language-ts">const mode = event.deltaMode;
const pixels = event.deltaY * (mode === 1 ? 16 : mode === 2 ? 300 : 1);
if (!Number.isFinite(pixels)) return 0;
return Math.max(-120, Math.min(120, pixels)) * (event.ctrlKey ? 0.01 : 0.0027);
</code></pre>
<p>These are tuning values from this application, not universal browser conversion constants. Ctrl-wheel gets a different gain because desktop trackpad pinches can arrive through that path. I would tune both paths again for a different scene or camera.</p>
<p>The second detail is what “zoom distance” means. Earth has radius one in this scene. A camera distance of 1.2 therefore means a height of 0.2 above the surface. Scaling the full distance from the center gives a very different result from scaling that height, especially close to Earth.</p>
<p>The current target calculation scales height exponentially, then adds the radius back:</p>
<pre><code class="language-ts">export function zoomTargetDistance(current: number, pending: number | null, logDelta: number) {
  if (!Number.isFinite(logDelta) || logDelta === 0) return pending ?? current;
  const base = pending !== null &amp;&amp; (pending - current) * logDelta &gt; 0 ? pending : current;
  return Math.max(MIN_GLOBE_DISTANCE, Math.min(MAX_GLOBE_DISTANCE,
    1 + (base - 1) * Math.exp(logDelta)));
}
</code></pre>
<p>The <code>base</code> choice matters as much as the exponential. While input continues in the same direction, it accumulates against the pending target. When the user reverses, the next target starts from the actual camera position. Otherwise, reversing can feel like pushing against an invisible queue of unfinished movement.</p>
<p>Easing needs to preserve the final little movement, too. The camera advances using an exponential factor based on elapsed seconds, with the frame interval capped after a long interruption. Once it is sufficiently close, the function returns the target itself. It must not simply abandon the remaining difference: repeatedly discarding tiny movements can make a sequence of valid trackpad inputs appear ineffective.</p>
<p>The twelve zoom tests cover fractional deltas, unit normalization, direction changes, bounds, and completing subpixel targets. They also compare simulated gestures at 60 and 120 updates per second. Those tests pass; they test the mathematics, not whether every MacBook/browser combination feels good. Physical device feedback remains necessary.</p>
<p>There is also an event ownership problem. Our wheel listener captures events within the globe's interaction area before OrbitControls can apply another zoom. It leaves inputs, sliders, and genuinely scrollable panels to their normal behavior. It is not attached to the whole document. A chart sidebar should still scroll when that is what the user is interacting with.</p>
<p>Sharper imagery introduces a separate cost. The globe keeps its base imagery while optional regional detail loads. The detail hook waits for a stable region, then requests day and night atlases. Obsolete requests are aborted; decoded images are disposed when no longer needed. Each atlas is 4096 by 2048 pixels.</p>
<p>Texture decoding is only part of the work. Uploading textures to the GPU can interrupt a frame, so the hook spreads the day and night uploads across separate frames when interaction has stopped. It then blends in the detail. This reduces how much work is scheduled together, but an individual upload can still stall. Unsupported devices or failed requests retain the base imagery. The camera does not wait for detail to finish.</p>
<p>There are deliberate limits: zoom is for countries and regions, not streets. Day imagery uses NASA GIBS Blue Marble; night lights come from historical imagery. Neither is a live satellite feed, and zooming cannot reveal information absent from the source.</p>
<p>Finally, I separated exploration from reading. The homepage leads with statistics, country pages use charts and tables, and the globe has its own route and dynamically imported component. Someone looking up a number shouldn't have to navigate a 3D scene first. Published observations retain their source periods; moving estimates need an explanation rather than an implication that every statistic is measured continuously.</p>
<p><a href="https://globaldatatracker.com/globe?utm_source=hashnode&amp;utm_medium=social&amp;utm_campaign=gdt_launch_20260919&amp;utm_content=trackpad_article">Try the globe</a>. Feedback on close-range zoom, direction changes, and scrolling over panels would be especially useful; please include your browser and input device.</p>
]]></content:encoded></item></channel></rss>