Capacitor 디바이스 센서는 다음과 같은 가볍고 특화된 플러그인을 통해 사용할 수 있습니다: @capgo/capacitor-accelerometer 모션 감지용, @capgo/capacitor-barometer 기압 측정용, @capgo/capacitor-compass 방위 감지용, @capgo/capacitor-pedometer 걸음 수 측정용이며, 조도, 근접 센서, 흔들림 감지 플러그인도 있습니다. 모든 플러그인은 사용 가능 여부 확인, 필요 시 권한 요청, 업데이트 시작, 이벤트 수신, 완료 후 중지라는 동일한 패턴을 따릅니다. 이 가이드에서는 Capacitor 8 code의 모든 센서와 주요 플랫폼별 차이점, 그리고 앱에 문제가 생기지 않게 하는 배터리 규칙을 다룹니다.
센서 개요
| 센서 | 플러그인 | iOS | Android | 권한 |
|---|---|---|---|---|
| 가속도계 | @capgo/capacitor-accelerometer |
지원 | 지원 | iOS NSMotionUsageDescription |
| 기압계 | @capgo/capacitor-barometer |
네, 상대 고도 포함 | 네, 센서가 있는 기기에서 압력만 지원 | iOS 모션 사용 설명 문구 |
| 나침반 | @capgo/capacitor-compass |
지원 | 네, 정확도 이벤트 포함 | iOS 사용 중 위치 |
| 만보기 | @capgo/capacitor-pedometer |
걸음 수, 거리, 층, 페이스, 카던스 | 단계 | iOS 모션, Android ACTIVITY_RECOGNITION |
| 주변 조도 | @capgo/capacitor-light-sensor |
공개된 API 없음 | 지원 | 없음 |
| 근접성 | @capgo/capacitor-proximity |
지원 | 지원 | 없음 |
| 흔들기 제스처 | @capgo/capacitor-shake |
지원 | 지원 | 없음 |
| 자이로스코프 / 방향 | 웹 DeviceMotionEvent / DeviceOrientationEvent |
지원 | 지원 | iOS requestPermission() WebView에서 |
사용하는 것만 설치하세요. 각 플러그인은 작고, 권한 문자열이 적을수록 App Review에서 받는 질문도 줄어듭니다.
bun add @capgo/capacitor-accelerometer @capgo/capacitor-compass @capgo/capacitor-pedometer
bunx cap sync
iOS 설정
센서에 필요한 사용 문자열을 다음에 추가하세요 ios/App/App/Info.plist:
<!-- Accelerometer, pedometer, barometer altitude -->
<key>NSMotionUsageDescription</key>
<string>Counts your steps and detects movement during workouts.</string>
<!-- Compass heading -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Shows which direction you are facing on the map.</string>
사용자에게 돌아가는 이점을 평이한 언어로 설명하세요. “이 앱은 모션 접근이 필요합니다” 같은 일반적 문구는 App Review 거절의 흔한 이유입니다.
Android 설정
대부분의 센서는 권한이 필요하지 않습니다. pedometer 플러그인은 다음을 추가합니다 ACTIVITY_RECOGNITION 병합된 manifest에 추가하고, Android 10+에서는 런타임에 요청합니다.
센서가 필수라면 하드웨어 기능을 선언하여 해당 기능이 없는 기기에서 Google Play가 앱을 숨기도록 하세요. 선택 사항이라면 다음으로 선언하고 required="false" 런타임에 사용 가능 여부를 확인하세요:
<uses-feature android:name="android.hardware.sensor.barometer" android:required="false" />
<uses-feature android:name="android.hardware.sensor.stepcounter" android:required="false" />
<uses-feature android:name="android.hardware.sensor.compass" android:required="false" />
가속도계: 모션 및 기울기
가속도계는 x, y, z 축의 가속도를 G 단위로 보고합니다. 평평하게 놓인 전화기는 약 z = -1 또는 1 플랫폼 컨벤션에 따라 달라지며, 중력이 포함되어 있기 때문입니다.
import { CapacitorAccelerometer } from '@capgo/capacitor-accelerometer';
const { isAvailable } = await CapacitorAccelerometer.isAvailable();
if (isAvailable) {
await CapacitorAccelerometer.requestPermissions();
const listener = await CapacitorAccelerometer.addListener('measurement', ({ x, y, z }) => {
const tiltX = Math.atan2(x, Math.sqrt(y * y + z * z)) * (180 / Math.PI);
ball.style.transform = `translateX(${tiltX * 3}px)`;
});
await CapacitorAccelerometer.startMeasurementUpdates();
// later
await CapacitorAccelerometer.stopMeasurementUpdates();
await listener.remove();
}
사용 사례: 게임의 기울기 컨트롤, “들어올려 깨우기” UI, 낙하 또는 충격 감지, 활동 강도. 원시 데이터는 노이즈가 많으므로 사용 전에 저역통과 필터로 부드럽게 처리하세요:
const alpha = 0.2;
let smoothed = { x: 0, y: 0, z: 0 };
function lowPass(m: { x: number; y: number; z: number }) {
smoothed = {
x: smoothed.x + alpha * (m.x - smoothed.x),
y: smoothed.y + alpha * (m.y - smoothed.y),
z: smoothed.z + alpha * (m.z - smoothed.z),
};
return smoothed;
}
모든 샘플마다 DOM을 업데이트하지 마세요. 최신 값을 저장하고 requestAnimationFrame 루프에서 렌더링하세요.
흔들기 제스처
“흔들어서 버그 보고”나 “흔들어서 실행 취소”의 경우, 임계값을 직접 조정할 필요가 없습니다:
import { CapacitorShake } from '@capgo/capacitor-shake';
const handle = await CapacitorShake.addListener('shake', () => openFeedbackForm());
자이로스코프 및 기기 방향
회전에 별도의 플러그인이 필요하지 않습니다. WebView가 이미 이를 노출합니다:
async function enableMotion() {
// iOS 13+ requires a user gesture and explicit permission
const DME = DeviceMotionEvent as unknown as { requestPermission?: () => Promise<'granted' | 'denied'> };
if (typeof DME.requestPermission === 'function') {
const result = await DME.requestPermission();
if (result !== 'granted') return;
}
window.addEventListener('devicemotion', (e) => {
const r = e.rotationRate; // alpha, beta, gamma in degrees per second
if (r) updateRotation(r.alpha ?? 0, r.beta ?? 0, r.gamma ?? 0);
});
window.addEventListener('deviceorientation', (e) => {
// alpha: 0-360 around z, beta: -180..180 front/back, gamma: -90..90 left/right
updateOrientation(e.alpha, e.beta, e.gamma);
});
}
document.querySelector('#enable-motion')!.addEventListener('click', enableMotion);
호출하세요 requestPermission 페이지 로드 시가 아닌 버튼 탭에서 요청해야 하며, 그렇지 않으면 iOS가 요청을 거부합니다.
나침반: 방위각
import { CapgoCompass, CompassAccuracy } from '@capgo/capacitor-compass';
const status = await CapgoCompass.requestPermissions(); // iOS: location permission
if (status.compass === 'granted') {
await CapgoCompass.addListener('headingChange', ({ value }) => {
needle.style.transform = `rotate(${-value}deg)`;
});
await CapgoCompass.startListening({
minInterval: 100, // ms between events
minHeadingChange: 2, // degrees
});
// Android only: ask the user to calibrate when accuracy drops
await CapgoCompass.addListener('accuracyChange', ({ accuracy }) => {
calibrationHint.hidden = accuracy >= CompassAccuracy.MEDIUM;
});
await CapgoCompass.watchAccuracy();
}
// One-off reading
const { value } = await CapgoCompass.getCurrentHeading();
value 자기북에서 0~360도 사이의 각도입니다. 알아야 할 점:
- 자석계는 폰 케이스, 노트북, 차량 마운트의 자석에 의해 간섭을 받습니다. Android에서 정확도가 낮다고 보고되면 보정 힌트(전화를 8자 형태로 움직이기)를 표시하세요.
- JavaScript가 아닌
minInterval및minHeadingChange에서 스로틀링을 적용하세요. 브리지 트래픽을 절약할 수 있습니다. - 사용자를 따라 회전하는 지도의 경우, heading과 geolocation을 조합하세요. 앱이 백그라운드일 때 위치 정보가 필요하면 백그라운드 geolocation 플러그인.
Pedometer: 걸음 수, 거리 및 층 수
스텝 카운터는 앱이 실행되지 않더라도 지속적으로 계수하는 저전력 칩입니다.
import { CapacitorPedometer } from '@capgo/capacitor-pedometer';
const features = await CapacitorPedometer.isAvailable();
// { stepCounting, distance, pace, cadence, floorCounting }
const perm = await CapacitorPedometer.requestPermissions();
if (perm.activityRecognition !== 'granted') {
// explain why and link to settings
}
// Steps today (start and end are required on iOS)
const startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);
const today = await CapacitorPedometer.getMeasurement({
start: startOfDay.getTime(),
end: Date.now(),
});
console.log(today.numberOfSteps, today.distance, today.floorsAscended);
// Live updates during a walk
await CapacitorPedometer.addListener('measurement', (m) => {
stepsEl.textContent = String(m.numberOfSteps ?? 0);
});
await CapacitorPedometer.startMeasurementUpdates();
플랫폼별 차이:
- iOS (Core Motion)는 약 최근 7일간의 과거 쿼리에 응답하며, 지원되는 기기에서는 거리, 층 수, 페이스, 카던스도 제공합니다.
- Android 걸음 수만 보고합니다. 하드웨어 카운터는 재부팅하면 리셋되니, 전체 누적 값으로 보지 마세요. 직접 기준값을 저장하세요.
장기 히스토리, 여러 기기의 일일 합계, 워치 데이터는 대신 @capgo/capacitor-health 에서 읽으세요. Apple Health와 Health Connect의 모든 소스에서 걸음 수를 집계합니다. 저희 Health Connect 마이그레이션 가이드 설정 방법을 확인하세요.
바로미터: 기압과 고도
import { CapacitorBarometer } from '@capgo/capacitor-barometer';
const { isAvailable } = await CapacitorBarometer.isAvailable();
if (isAvailable) {
await CapacitorBarometer.requestPermissions();
await CapacitorBarometer.addListener('measurement', ({ pressure, relativeAltitude }) => {
pressureEl.textContent = `${pressure.toFixed(1)} hPa`;
altitudeEl.textContent = `${relativeAltitude.toFixed(1)} m`;
});
await CapacitorBarometer.startMeasurementUpdates();
}
pressure 는 두 플랫폼 모두 헥토파스칼(hPa) 단위입니다. relativeAltitude 는 업데이트가 시작된 이후 미터(m) 단위 변화량이며, iOS에서만 제공됩니다. Android에서는 항상 0이므로, 기압 공식으로 직접 계산하세요:
const P0 = 1013.25; // standard sea-level pressure in hPa
const altitudeMeters = (p: number) => 44330 * (1 - Math.pow(p / P0, 1 / 5.255));
let baseline: number | null = null;
function relativeAltitude(pressure: number) {
baseline ??= altitudeMeters(pressure);
return altitudeMeters(pressure) - baseline;
}
기압에서 구한 절대 고도는 날씨에 따라 해수면 기압이 변해서 수십 미터씩 어긋납니다. 몇 분 사이의 상대 변화는 약 1미터 수준으로 정확해 층 수나 등반 횟수를 세기에 충분합니다. 저가형 Android 폰은 바로미터가 없는 경우가 많으니 항상 isAvailable.
조도 센서 (Android)
import { LightSensor } from '@capgo/capacitor-light-sensor';
const { available } = await LightSensor.isAvailable(); // always false on iOS
if (available) {
await LightSensor.addListener('lightSensorChange', ({ illuminance }) => {
document.body.classList.toggle('high-contrast', illuminance > 10_000); // bright sunlight
});
await LightSensor.start({ updateInterval: 500 });
}
일반적인 값: 10 lux 미만은 어두운 방, 수백 lux는 사무실, 수만 lux는 직사광선입니다. 주변 밝기에 맞춰지는 독서 모드나 야외에서 대비를 높이는 데 활용하세요. 화면 밝기 자체를 바꾸려면 @capgo/capacitor-brightness.
근접 센서
근접 센서는 화면 상단이 가려졌을 때를 알려줍니다. 보통 통화 중 사용자의 귀가 닿을 때죠.
import { CapacitorProximity } from '@capgo/capacitor-proximity';
const status = await CapacitorProximity.getStatus();
if (status.available) {
await CapacitorProximity.enable(); // screen turns off when covered
}
// when the call or voice message ends
await CapacitorProximity.disable();
iOS에서는 이 기능이 UIDevice.isProximityMonitoringEnabled를 토글해 가려진 동안 디스플레이를 끕니다. Android에서는 플러그인이 근접 센서를 수신해 앱 창을 어둡게 합니다. 음성 통화, 음성 메시지, 워키토키 모드가 활성화된 동안에만 켜서 사용자의 볼이 버튼을 누르지 않도록 하세요.
센서 앱 실전 규칙
- 먼저 사용 가능 여부를 확인하세요. 태블릿은 스텝 카운터나 바로미터가 없는 경우가 많고, 에뮬레이터는 대부분의 센서를 가짜로 구현합니다. 지원하지 않는 기능은 숨기세요.
- 화면이 보이지 않을 때 업데이트를 중지하세요. 사용
@capacitor/app의appStateChange앱이 백그라운드로 갈 때 리스너를 중지하고 다시 돌아오면 재시작하도록 하세요. 센서를 계속 켜두면 배터리가 소모되고 별점 1점 리뷰로 이어집니다. - 플러그인이 허용하는 경우 네이티브 code에서 스로틀링을 적용하세요 (나침반
minInterval, 조도 센서updateInterval)는 JavaScript가 아닙니다. - 상황에 맞춰 권한을 요청하세요. 앱 첫 실행 시가 아니라 사용자가 워크아웃을 시작할 때 모션 접근을 요청하세요.
- 실제 하드웨어에서 테스트하세요. 시뮬레이터는 상수 또는 합성 값을 반환합니다. 센서가 없거나 노이즈가 많은 저가형 Android 폰을 최소 한 대는 테스트하세요.
- 개인정보 보호 규칙을 준수하세요. 모션 및 위치 데이터는 App Store 개인정보 보호 라벨과 Play 데이터 보안 양식에서 민감 정보로 간주됩니다. 수집하는 항목을 명시하세요.
문제 해결
iOS에서 이벤트 없음: 사용 설명 문자열이 누락된 위치: Info.plist, 또는 권한이 거부되었습니다. 설정 > 개인정보 보호 및 보안 > 동작 및 피트니스를 확인하세요.
Android에서 Pedometer가 0을 반환합니다: ACTIVITY_RECOGNITION 거부되었거나, 기기에 스텝 카운터가 없습니다. 확인하세요 isAvailable().stepCounting.
나침반 값이 튑니다: 자기장 간섭 또는 보정되지 않음. Android에서 정확도를 확인하고 보정 힌트를 표시하세요.
relativeAltitude 항상 0입니다: Android에서 예상되는 값입니다. 위와 같이 pressure로 계산하세요.
DeviceMotionEvent.requestPermission 예외 발생: 사용자 제스처를 통해 호출되지 않았습니다.
배터리 소모 리포트: 리스너가 백그라운드에서 계속 실행됩니다. 다음에서 중지하세요: appStateChange.
센서 기능을 빠르게 출시하세요
센서 기능의 임계값, 스무딩 계수 및 UI는 보통 실제 사용자와 함께 여러 차례 튜닝 과정을 거쳐야 합니다. 이 로직은 JavaScript에 있으므로 다음을 통해 조정할 수 있습니다. Capgo Live Update 사용하기 스토어 심사 없이 조정할 수 있지만, 새 플러그인과 권한 문자열은 일반 빌드를 거쳐야 합니다. 가슴 스트랩 같은 외부 센서를 연결한다면, 다음 가이드로 이어가세요: Capacitor로 심박수 모니터 만들기.