Skip to content

Commit d19bb66

Browse files
authored
Add DeviceMotion Rate Tester HTML file
This file contains an HTML document for a DeviceMotion Rate Tester application, including styles, structure, and JavaScript functionality to handle device motion events.
1 parent dcb2401 commit d19bb66

1 file changed

Lines changed: 272 additions & 0 deletions

File tree

manjindex.html

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width,initial-scale=1" />
6+
<title>DeviceMotion Rate Tester</title>
7+
<style>
8+
:root{
9+
--bg:#0b1220; --card:#0f1724; --muted:#9aa4b2; --accent:#60a5fa; --text:#e6eef6;
10+
}
11+
*{box-sizing:border-box}
12+
body{
13+
margin:0;
14+
font-family:Inter,system-ui,-apple-system,"Segoe UI",Roboto,Arial;
15+
background:linear-gradient(180deg,#071022 0%, #0b1220 100%);
16+
color:var(--text);
17+
display:flex;
18+
align-items:center;
19+
justify-content:center;
20+
min-height:100vh;
21+
padding:20px;
22+
}
23+
.card{
24+
width:min(760px,96%);
25+
background:linear-gradient(180deg, rgba(255,255,255,0.02), var(--card));
26+
padding:20px;
27+
border-radius:12px;
28+
box-shadow:0 10px 30px rgba(0,0,0,0.6);
29+
}
30+
h1{margin:0 0 10px;font-size:1.2rem}
31+
p {color:var(--muted);margin:0 0 12px}
32+
.controls{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
33+
button{
34+
background:transparent;border:1px solid rgba(255,255,255,0.06);
35+
color:var(--text);padding:10px 12px;border-radius:8px;cursor:pointer;
36+
}
37+
button.primary{border-color:var(--accent);box-shadow:0 6px 18px rgba(96,165,250,0.08)}
38+
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:14px}
39+
.stat{
40+
background:rgba(255,255,255,0.02);padding:12px;border-radius:8px;text-align:center;
41+
}
42+
.stat .label{color:var(--muted);font-size:0.85rem}
43+
.stat .value{font-weight:700;font-size:1.3rem;margin-top:6px}
44+
canvas{width:100%;height:80px;border-radius:8px;background:linear-gradient(180deg,#06101a, #08121a);display:block}
45+
.note{font-size:0.85rem;color:var(--muted);margin-top:12px}
46+
a {color:var(--accent)}
47+
</style>
48+
</head>
49+
<body>
50+
<div class="card">
51+
<h1>DeviceMotion Rate Tester</h1>
52+
<p>Shows how fast your browser is sending <code>devicemotion</code> events (events per second, intervals, and a small live graph).</p>
53+
54+
<div class="controls">
55+
<button id="btn-perm" class="primary">Request Motion Permission</button>
56+
<button id="btn-start">Start Listening</button>
57+
<button id="btn-stop">Stop</button>
58+
<button id="btn-reset">Reset</button>
59+
</div>
60+
61+
<div class="stats">
62+
<div class="stat">
63+
<div class="label">Events / sec (instant)</div>
64+
<div id="eps" class="value"></div>
65+
</div>
66+
67+
<div class="stat">
68+
<div class="label">Average interval (ms)</div>
69+
<div id="avg-interval" class="value"></div>
70+
</div>
71+
72+
<div class="stat">
73+
<div class="label">Last interval (ms)</div>
74+
<div id="last-interval" class="value"></div>
75+
</div>
76+
77+
<div class="stat">
78+
<div class="label">Total events</div>
79+
<div id="total" class="value">0</div>
80+
</div>
81+
</div>
82+
83+
<canvas id="chart" width="800" height="120" aria-label="event rate chart"></canvas>
84+
85+
<p class="note">
86+
Notes: Most desktop browsers do not emit device motion events. On iOS 13+ you must explicitly grant permission; press <strong>Request Motion Permission</strong> first. Try moving or rotating the device to see events.
87+
</p>
88+
89+
<p class="note">Repo: <a href="https://chaicode.com/" target="_blank" rel="noopener">ChaiCode example</a></p>
90+
</div>
91+
92+
<script>
93+
(() => {
94+
// DOM
95+
const btnPerm = document.getElementById('btn-perm');
96+
const btnStart = document.getElementById('btn-start');
97+
const btnStop = document.getElementById('btn-stop');
98+
const btnReset = document.getElementById('btn-reset');
99+
const epsEl = document.getElementById('eps');
100+
const avgEl = document.getElementById('avg-interval');
101+
const lastEl = document.getElementById('last-interval');
102+
const totalEl = document.getElementById('total');
103+
const canvas = document.getElementById('chart');
104+
const ctx = canvas.getContext('2d');
105+
106+
// State
107+
let listening = false;
108+
let eventCount = 0;
109+
let lastTs = null;
110+
let intervals = []; // ms
111+
const MAX_SAMPLES = 120;
112+
let timestamps = []; // recent timestamps for instantaneous EPS
113+
114+
// Helpers
115+
function msToFixed(n){ return (Math.round(n*10)/10).toFixed(1); }
116+
117+
// Permission flow for iOS Safari
118+
async function requestPermission() {
119+
if (typeof DeviceMotionEvent !== 'undefined' && typeof DeviceMotionEvent.requestPermission === 'function') {
120+
try {
121+
const resp = await DeviceMotionEvent.requestPermission();
122+
if (resp === 'granted') {
123+
alert('Motion permission granted. Now press "Start Listening".');
124+
} else {
125+
alert('Motion permission denied.');
126+
}
127+
} catch (err) {
128+
console.error(err);
129+
alert('Permission request failed: ' + err);
130+
}
131+
} else {
132+
alert('No permission prompt required (or not supported). Try "Start Listening".');
133+
}
134+
}
135+
136+
function onDeviceMotion(e) {
137+
const t = performance.now(); // high-res timestamp
138+
eventCount++;
139+
totalEl.textContent = eventCount;
140+
141+
if (lastTs !== null) {
142+
const interval = t - lastTs;
143+
intervals.push(interval);
144+
if (intervals.length > MAX_SAMPLES) intervals.shift();
145+
lastEl.textContent = msToFixed(interval);
146+
}
147+
lastTs = t;
148+
149+
// keep recent timestamps for instant EPS (1s window)
150+
timestamps.push(t);
151+
// drop timestamps older than 1s
152+
const cutoff = t - 1000;
153+
while (timestamps.length && timestamps[0] < cutoff) timestamps.shift();
154+
const eps = timestamps.length;
155+
epsEl.textContent = eps;
156+
157+
// average interval over stored intervals
158+
if (intervals.length) {
159+
const sum = intervals.reduce((a,b)=>a+b,0);
160+
const avg = sum / intervals.length;
161+
avgEl.textContent = msToFixed(avg);
162+
} else {
163+
avgEl.textContent = '—';
164+
}
165+
166+
drawChart();
167+
}
168+
169+
function startListening() {
170+
if (listening) return;
171+
// Some browsers require the handler to be passive: false if you want preventDefault (not needed here)
172+
window.addEventListener('devicemotion', onDeviceMotion);
173+
listening = true;
174+
btnStart.disabled = true;
175+
btnStop.disabled = false;
176+
}
177+
178+
function stopListening() {
179+
if (!listening) return;
180+
window.removeEventListener('devicemotion', onDeviceMotion);
181+
listening = false;
182+
btnStart.disabled = false;
183+
btnStop.disabled = true;
184+
}
185+
186+
function reset() {
187+
stopListening();
188+
eventCount = 0;
189+
lastTs = null;
190+
intervals = [];
191+
timestamps = [];
192+
epsEl.textContent = '—';
193+
avgEl.textContent = '—';
194+
lastEl.textContent = '—';
195+
totalEl.textContent = '0';
196+
clearChart();
197+
}
198+
199+
// Chart drawing (simple sparkline for events per frame calculated from intervals)
200+
function drawChart(){
201+
// compute samples: convert intervals[] to EPS-like values (1000 / interval)
202+
const samples = intervals.map(i => 1000 / i);
203+
// pad to length
204+
const len = MAX_SAMPLES;
205+
const padded = Array(Math.max(0, len - samples.length)).fill(0).concat(samples);
206+
207+
const w = canvas.width;
208+
const h = canvas.height;
209+
ctx.clearRect(0,0,w,h);
210+
211+
// background gradient
212+
const g = ctx.createLinearGradient(0,0,0,h);
213+
g.addColorStop(0,'rgba(96,165,250,0.08)');
214+
g.addColorStop(1,'rgba(6,8,12,0.02)');
215+
ctx.fillStyle = g;
216+
ctx.fillRect(0,0,w,h);
217+
218+
// find max for scaling (or use fallback)
219+
const max = Math.max(5, ...padded);
220+
// draw line
221+
ctx.beginPath();
222+
padded.forEach((v,i) => {
223+
const x = (i / (padded.length-1)) * w;
224+
const y = h - ((v / max) * (h - 8) + 4);
225+
if (i === 0) ctx.moveTo(x,y);
226+
else ctx.lineTo(x,y);
227+
});
228+
ctx.strokeStyle = 'rgba(96,165,250,0.95)';
229+
ctx.lineWidth = 2.0;
230+
ctx.stroke();
231+
232+
// fill under curve
233+
ctx.lineTo(w, h);
234+
ctx.lineTo(0, h);
235+
ctx.closePath();
236+
ctx.fillStyle = 'rgba(96,165,250,0.08)';
237+
ctx.fill();
238+
239+
// draw current EPS text small
240+
ctx.fillStyle = 'rgba(230,238,246,0.9)';
241+
ctx.font = '12px Inter, Arial';
242+
const current = padded[padded.length-1] ? padded[padded.length-1].toFixed(1) : '0.0';
243+
ctx.fillText('EPS (recent): ' + current, 8, 14);
244+
}
245+
246+
function clearChart(){
247+
ctx.clearRect(0,0,canvas.width,canvas.height);
248+
}
249+
250+
// Attach events
251+
btnPerm.addEventListener('click', requestPermission);
252+
btnStart.addEventListener('click', startListening);
253+
btnStop.addEventListener('click', stopListening);
254+
btnReset.addEventListener('click', reset);
255+
256+
// initial state
257+
btnStop.disabled = true;
258+
clearChart();
259+
260+
// Helpful UX: if devicemotion events were active in the past, offer auto-start on load (commented out)
261+
// startListening();
262+
263+
// Minor: handle page visibility to pause chart updates if needed (not strictly necessary)
264+
document.addEventListener('visibilitychange', () => {
265+
if (document.hidden) {
266+
// optionally pause chart updates or logging
267+
}
268+
});
269+
})();
270+
</script>
271+
</body>
272+
</html>

0 commit comments

Comments
 (0)