Geolocation e Device APIs
TL;DR
A Geolocation API fornece coordenadas GPS/Wi-Fi do dispositivo. A DeviceOrientation e DeviceMotion expõem acelerômetro, giroscópio e bússola. O MediaDevices API acessa câmera e microfone. Todas exigem HTTPS, permissão do usuário e tratamento de erros cuidadoso — o usuário pode negar, o hardware pode não existir, e a posição pode ser imprecisa.
Geolocation API
// Verificar suporte
if (!('geolocation' in navigator)) {
console.error('Geolocation não suportada');
}
// Uma leitura (one-shot)
navigator.geolocation.getCurrentPosition(
(position) => {
position.coords.latitude; // graus decimais, ex: -23.5505
position.coords.longitude; // graus decimais, ex: -46.6333
position.coords.accuracy; // metros de precisão
position.coords.altitude; // metros acima do nível do mar (ou null)
position.coords.altitudeAccuracy; // metros de precisão da altitude (ou null)
position.coords.heading; // graus (0=Norte, 90=Leste) ou null
position.coords.speed; // m/s ou null
position.timestamp; // ms desde epoch
},
(error) => {
switch (error.code) {
case GeolocationPositionError.PERMISSION_DENIED: // 1
console.error('Usuário negou a permissão');
break;
case GeolocationPositionError.POSITION_UNAVAILABLE: // 2
console.error('Posição indisponível (GPS sem sinal)');
break;
case GeolocationPositionError.TIMEOUT: // 3
console.error('Timeout ao obter posição');
break;
}
},
{
enableHighAccuracy: true, // GPS em vez de Wi-Fi/cell (mais lento, mais preciso, mais bateria)
timeout: 10000, // ms para timeout (padrão: Infinity)
maximumAge: 60000, // usar cache de até 1 min (0 = sempre fresco)
}
);Monitorar posição em tempo real
// watchPosition: callback a cada atualização
const watchId = navigator.geolocation.watchPosition(
(position) => updateMap(position.coords),
(error) => handleError(error),
{ enableHighAccuracy: true, maximumAge: 5000 }
);
// Parar de monitorar
navigator.geolocation.clearWatch(watchId);Wrappers com Promises
// Wrapper promise para getCurrentPosition
function getPosition(options = {}) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
}
// Wrapper observable para watchPosition
function watchPosition(options = {}) {
return {
subscribe(callback, errorCallback) {
const id = navigator.geolocation.watchPosition(callback, errorCallback, options);
return () => navigator.geolocation.clearWatch(id); // unsubscribe
}
};
}
// Uso
async function showNearby() {
try {
const { coords } = await getPosition({ enableHighAccuracy: false, timeout: 5000 });
const results = await fetchNearby(coords.latitude, coords.longitude, 5000);
renderResults(results);
} catch (error) {
if (error.code === 1) {
showMessage('Permita acesso à localização para ver resultados próximos');
}
}
}DeviceOrientation API
// Orientação do dispositivo no espaço (giroscópio/magnetômetro)
window.addEventListener('deviceorientation', (event) => {
event.alpha; // rotação ao redor do eixo Z (0-360°, bússola — 0 = Norte)
event.beta; // rotação ao redor do eixo X (-180 a 180 — inclinação frente/trás)
event.gamma; // rotação ao redor do eixo Y (-90 a 90 — inclinação esquerda/direita)
event.absolute; // true = orientação absoluta (bússola); false = relativa
});
// Bússola digital
function setupCompass(needle) {
window.addEventListener('deviceorientation', (event) => {
// alpha: 0 = Norte, 90 = Leste, 180 = Sul, 270 = Oeste
needle.style.transform = `rotate(${-event.alpha}deg)`;
});
}
// Experiência de "paralax" por inclinação
function setupParallax(elements) {
let lastGamma = 0, lastBeta = 0;
window.addEventListener('deviceorientation', (event) => {
const gamma = event.gamma ?? 0; // -90 a 90
const beta = event.beta ?? 0; // -180 a 180
elements.forEach((el, i) => {
const depth = (i + 1) * 0.5;
el.style.transform = `translate(${gamma * depth}px, ${beta * 0.3 * depth}px)`;
});
});
}DeviceMotion API
// Aceleração e velocidade de rotação (acelerômetro + giroscópio)
window.addEventListener('devicemotion', (event) => {
// Aceleração sem gravidade (m/s²)
event.acceleration.x;
event.acceleration.y;
event.acceleration.z;
// Aceleração com gravidade incluída
event.accelerationIncludingGravity.x;
event.accelerationIncludingGravity.y;
event.accelerationIncludingGravity.z;
// Velocidade de rotação (graus/segundo)
event.rotationRate.alpha;
event.rotationRate.beta;
event.rotationRate.gamma;
event.interval; // ms entre eventos
});
// Detectar shake do dispositivo
function onShake(callback, threshold = 15) {
let lastX = null, lastY = null, lastZ = null;
window.addEventListener('devicemotion', (event) => {
const { x, y, z } = event.accelerationIncludingGravity;
if (lastX === null) { lastX = x; lastY = y; lastZ = z; return; }
const delta = Math.abs(x - lastX) + Math.abs(y - lastY) + Math.abs(z - lastZ);
if (delta > threshold) callback();
lastX = x; lastY = y; lastZ = z;
});
}
onShake(() => console.log('Dispositivo sacudido!'));Permissões no iOS 13+
No iOS 13+,
devicemotionedeviceorientationrequerem permissão explícita. ChamarDeviceMotionEvent.requestPermission()dentro de um gesto do usuário.button.onclick = async () => { if (typeof DeviceMotionEvent.requestPermission === 'function') { const permission = await DeviceMotionEvent.requestPermission(); if (permission !== 'granted') return; } startListening(); };
MediaDevices — câmera e microfone
// Listar dispositivos disponíveis
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');
const microphones = devices.filter(d => d.kind === 'audioinput');
// Capturar stream de vídeo/áudio
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
facingMode: 'user', // 'user' = frontal, 'environment' = traseira
deviceId: cameraId, // câmera específica
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100,
},
});
// Exibir no elemento <video>
const video = document.querySelector('video');
video.srcObject = stream;
video.play();
// Parar todas as tracks quando terminar
function stopStream(stream) {
stream.getTracks().forEach(track => track.stop());
}
// Capturar screenshot da câmera
function captureFrame(videoEl) {
const canvas = document.createElement('canvas');
canvas.width = videoEl.videoWidth;
canvas.height = videoEl.videoHeight;
canvas.getContext('2d').drawImage(videoEl, 0, 0);
return canvas.toDataURL('image/jpeg', 0.9);
}getDisplayMedia — compartilhar tela
// Capturar a tela
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'always' }, // 'always' | 'motion' | 'never'
audio: true, // capturar áudio do sistema (suporte limitado)
});
// Combinar câmera + tela (picture-in-picture)
const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true });
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const ctx = canvas.getContext('2d');
function composite(screenVideo, cameraVideo) {
requestAnimationFrame(() => composite(screenVideo, cameraVideo));
ctx.drawImage(screenVideo, 0, 0, 1280, 720);
ctx.drawImage(cameraVideo, 20, 20, 320, 180); // PiP no canto
});Para fixar
- Qual a diferença entre
getCurrentPositionewatchPosition? Como você cancela o segundo?- O que o campo
maximumAgefaz nas opções de geolocalização? Quando usarmaximumAge: 0?- Qual a diferença entre
accelerationeaccelerationIncludingGravity?- Por que você precisa chamar
stream.getTracks().forEach(t => t.stop())após usar câmera/microfone?- O que
facingMode: 'environment'faz em um dispositivo móvel?
Veja também
- 05 — Notifications e Permissions — anterior
- 07 — Web Share e outras APIs — próxima
- 01 — Intersection Observer — outra API que requer contexto seguro