DeviceOrientation
How the device is tilted and how it is being moved - the accelerometer, gyroscope and compass behind a phone's level, shake gesture and heading indicator, read straight from C#.
@inject Bit.Butil.DeviceOrientation deviceOrientationMDN reference
IsOrientationSupported tells you
the API exists, not that readings are coming. Open this page on a phone to see it work.
Support and permission
IsOrientationSupported / IsMotionSupported / NeedsPermission / RequestPermissioniOS gates both streams behind an explicit grant that must be asked for from a click handler and is not remembered across page loads. Everywhere else there is no gate, and RequestPermission returns Granted immediately - so it is always safe to call, and NeedsPermission tells you whether a button is worth showing.
@inject Bit.Butil.DeviceOrientation deviceOrientation
var hasOrientation = await deviceOrientation.IsOrientationSupported();
var hasMotion = await deviceOrientation.IsMotionSupported();
var gated = await deviceOrientation.NeedsPermission(); // true on iOS
// must run inside a click handler:
var permission = await deviceOrientation.RequestPermission();
if (permission == DeviceSensorPermission.Granted)
{
// safe to subscribe
}Alpha is rotation about the vertical axis, beta the front-to-back tilt (0 flat, 90 upright) and gamma the left-to-right tilt. With absolute: true it prefers the deviceorientationabsolute event, whose alpha is a real compass heading rather than being relative to wherever the device happened to be pointing when the listener attached - the Absolute flag on each reading tells you which one you actually got.
private ButilSubscription? _tilt;
_tilt = await deviceOrientation.SubscribeOrientation(
reading => InvokeAsync(() =>
{
_reading = reading; // Alpha / Beta / Gamma in degrees, Absolute flag
StateHasChanged();
}),
absolute: true,
minIntervalMs: 100); // these events fire tens of times a second
// later:
await _tilt.DisposeAsync();Acceleration in m/s², with and without gravity, plus the rotation rate in degrees per second. A device lying flat reports about 9.8 on AccelerationIncludingGravityZ and roughly nothing on the gravity-free members - the difference between the two is what a shake detector keys off. Interval is the sensor's own refresh rate, not the rate readings reach your handler.
_motion = await deviceOrientation.SubscribeMotion(
reading => InvokeAsync(() =>
{
// a crude shake detector:
var magnitude = Math.Sqrt(
reading.AccelerationX * reading.AccelerationX +
reading.AccelerationY * reading.AccelerationY +
reading.AccelerationZ * reading.AccelerationZ);
if (magnitude > 15) OnShake();
StateHasChanged();
}),
minIntervalMs: 100);minIntervalMs defaults to 100, which is plenty for a
UI. Passing 0 forwards every event and will keep a Blazor render loop permanently busy.
API reference
ValueTask<bool> IsOrientationSupported()ValueTask<bool> IsMotionSupported()ValueTask<bool> NeedsPermission()ValueTask<DeviceSensorPermission> RequestPermission()ValueTask<ButilSubscription> SubscribeOrientation(Action<DeviceOrientationReading> handler, bool absolute = false, int minIntervalMs = 100)ValueTask<ButilSubscription> SubscribeMotion(Action<DeviceMotionReading> handler, int minIntervalMs = 100)ValueTask DisposeAsync()Alpha, Beta, Gamma, AbsoluteAcceleration{X,Y,Z}, AccelerationIncludingGravity{X,Y,Z}, Rotation{Alpha,Beta,Gamma}, Interval