1/* Refresh-rate measurement. 2 Also drives the small live Hz tile on the homepage, so both stay in step. 3 4 Method: time successive animation frames and take the MEDIAN interval. 5 Median, not mean, because a single dropped frame (GC, tab work, a scroll) 6 doubles one interval and would drag an average down hard. */ 7(function () { 8 'use strict'; 9 10 var out = document.getElementById('hz-value'); 11 if (!out) return; 12 13 var sub = document.getElementById('hz-sub'); 14 var barsEl = document.getElementById('hz-bars'); 15 var statEl = document.getElementById('hz-stats'); 16 var WINDOW = 180; // frames kept for the rolling estimate 17 var times = []; 18 var last = 0; 19 var settled = false; 20 var history = []; // recent Hz readings, for the sparkline 21 22 function median(a) { 23 if (!a.length) return 0; 24 var s = a.slice().sort(function (x, y) { return x - y; }); 25 var m = s.length >> 1; 26 return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; 27 } 28 29 /* Panels advertise round numbers but run slightly off them (59.94, 143.9). 30 Snap only when we are very close, so a real 75Hz panel is not called 60. */ 31 function label(hz) { 32 var common = [24, 30, 48, 50, 60, 75, 90, 100, 120, 144, 165, 240, 360, 480]; 33 for (var i = 0; i < common.length; i++) { 34 if (Math.abs(hz - common[i]) < 1.2) return common[i]; 35 } 36 return Math.round(hz); 37 } 38 39 function drawBars() { 40 if (!barsEl) return; 41 var n = 40; 42 var slice = history.slice(-n); 43 if (!slice.length) return; 44 var max = Math.max.apply(null, slice); 45 var min = Math.min.apply(null, slice); 46 var span = Math.max(2, max - min); 47 var html = ''; 48 for (var i = 0; i < slice.length; i++) { 49 var h = 12 + 88 * ((slice[i] - min) / span); 50 html += '<i style="height:' + h.toFixed(1) + '%"></i>'; 51 } 52 barsEl.innerHTML = html; 53 } 54 55 function tick(ts) { 56 if (last) { 57 var dt = ts - last; 58 // Ignore absurd gaps: tab was backgrounded, or the thread stalled. 59 if (dt > 0.5 && dt < 200) { 60 times.push(dt); 61 if (times.length > WINDOW) times.shift(); 62 } 63 } 64 last = ts; 65 66 if (times.length >= 12) { 67 var med = median(times); 68 var hz = 1000 / med; 69 70 out.textContent = hz.toFixed(1); 71 72 if (!settled && times.length >= 60) { 73 settled = true; 74 if (sub) sub.textContent = 'Your display is running at about ' + label(hz) + ' Hz.'; 75 } 76 77 if (times.length % 6 === 0) { 78 history.push(hz); 79 if (history.length > 120) history.shift(); 80 drawBars(); 81 } 82 83 if (statEl && times.length % 12 === 0) { 84 var mn = Math.min.apply(null, times), mx = Math.max.apply(null, times); 85 statEl.innerHTML = 86 '<span><b>' + label(hz) + '</b> Hz nominal</span>' + 87 '<span><b>' + med.toFixed(2) + '</b> ms per frame</span>' + 88 '<span><b>' + (1000 / mx).toFixed(1) + 'â' + (1000 / mn).toFixed(1) + '</b> Hz range</span>' + 89 '<span><b>' + times.length + '</b> frames sampled</span>'; 90 } 91 } 92 93 requestAnimationFrame(tick); 94 } 95 96 requestAnimationFrame(tick); 97 98 // A backgrounded tab throttles rAF hard; drop the stale sample on return. 99 document.addEventListener('visibilitychange', function () { 100 if (!document.hidden) { times = []; last = 0; } 101 }); 102})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.