Устройство
Информация о платформе, текущий размер дисплея, связность и событие ресайза от хоста. device —
обычный глобальный объект, ничего создавать не нужно.
Обзор
if (device.platform === 'ios') toast('running on iOS')
const layout = (w: number, h: number) => {
console.log('display is', w, 'x', h, 'logical px')
}
layout(device.width, device.height) // читается в любой момент
device.addEventListener('resize', layout) // и снова при каждом ресайзеИнфо о платформе
device.platform // "web" | "android" | "ios" | string (только чтение)
device.language // код языка хоста, напр. "en" (только чтение)
device.pixelRatio // физических px на логический px; 1 на обычных дисплеях, 2–3 на retina/iOSpixelRatio — коэффициент, с которым запекать Canvas для чёткого вывода без хардкода:
new Canvas(w, h, { pixelRatio: device.pixelRatio }). Откатывается к 1, если хост его не
сообщает.
веб-поверхность 2D/GL сейчас рендерит в логическом разрешении, поэтому на вебе pixelRatio помогает
только UI-канвасам; на iOS (физическая поверхность) он делает 2D-канвасы чёткими.
Размер дисплея
device.width // текущая ширина дисплея, логические px (только чтение)
device.height // текущая высота дисплея, логические px (только чтение)Те же значения, что доставляет событие resize, но читаемые в любой момент — не только внутри
слушателя. Оба — логические px (как clientX/clientY), никогда не физические пиксели.
оба равны 0, пока хост не сообщил размер. Не строй layout из device.width в начале файла,
который выполняется до первого кадра — подпишись ещё и на resize.
Событие resize
device.addEventListener('resize', (width, height) => { }) // логические px
device.removeEventListener('resize', callback) // та же ссылка на функциюСрабатывает при любом изменении вьюпорта хоста (поворот, ресайз окна).
Связность: device.online и события online / offline
device.online // boolean (только чтение)
device.addEventListener('offline', () => toast('No connection'))
device.addEventListener('online', () => sync())Семантика navigator.onLine по мере возможностей: false — только когда платформа уверена,
что сети нет; true не гарантирует, что интернет доступен — относись к нему как к подсказке для UI
(баннеры «ты офлайн», откладывание синхронизации) и всё равно обрабатывай ошибки fetch.
гейтится хостом — читается как true, и события никогда не срабатывают на хостах, которые не
отслеживают связность (headless).
Точный тач
device.setPreciseTouch(enabled: boolean) // ВЫКЛ по умолчаниюПодключи систему точного тача там, где платформа её поддерживает. Когда включено, быстрые штрихи сэмплируются на полной частоте дигитайзера (объединённые касания iOS, ≈120–240 Гц), а не раз в кадр дисплея (~60 Гц) — приложение с активным вводом (рисование, рукописный ввод, перетаскивание) получает больше точек и более гладкие линии. Для tap/кнопочных UI оставь выключенным.
гейтится хостом — тихий no-op на хостах без понятия объединённого ввода (веб уже объединяет pointermove; headless).
Хаптика
device.vibrate() // удар "medium" по умолчанию
device.vibrate("selection") // лёгкий тик для смены значенияВыдай одиночный хаптик семантического style (по умолчанию "medium"):
| Группа | Стили | Смысл |
|---|---|---|
| Impact | "light" "medium" "heavy" "soft" "rigid" |
физический «тап» разного веса |
| Notification | "success" "warning" "error" |
сигнал об исходе |
| Selection | "selection" |
лёгкий тик для меняющегося значения |
API семантический, а не длительность/паттерн, намеренно: это единственный словарь, который
ощущается нативно и на iOS (Taptic Engine / UIFeedbackGenerator), и на Android
(HapticFeedbackConstants / VibrationEffect). Длительность vibrate(ms) была бы честной только
на вебе/Android — у iOS нет публичного API для вибрации произвольной длины.
гейтится хостом — тихий no-op там, где нет хаптического железа (iPad, старые iPhone, веб, headless). Также уважает системную настройку хаптики пользователя.
Движение
Сфьюженный датчик ориентации устройства (гироскоп + акселерометр) — для управления наклоном/рулением и magic-window / 360°-панорам.
await device.motion.start() // начать обновления (батарея); Promise<boolean> — false = нет сенсора
setLoop(() => {
scene.camera.quaternion = device.motion.attitude // камера 360°-панорамы, одна строка
// или 2D-игра с наклоном:
ball.x += device.motion.gravity.x
ball.y += device.motion.gravity.y
})
device.motion.recenter() // сделать «здесь» направлением вперёд (только yaw)
device.motion.stop() // освободить сенсор| Член | Тип | Примечания |
|---|---|---|
start(options?) |
Promise<boolean> |
Начать обновления. false = нет сенсора / в разрешении отказано. |
stop() |
void |
Остановить обновления, освободить сенсор. |
recenter() |
void |
Обнулить текущее направление (pitch/roll остаются привязаны к гравитации). |
available |
boolean |
Есть ли у устройства гироскоп вообще? |
enabled |
boolean |
Идут ли обновления сейчас? |
attitude |
Quat |
Текущая ориентация. По умолчанию мировой фрейм — присваивай прямо в quaternion узла/камеры. |
gravity |
Vec3 |
Направление гравитации в экранном пространстве (x → вправо, y → вниз); читай x/y для 2D-наклона. |
start(options) принимает { interval?: number, frame?: "world" | "device" }. interval
(секунды, по умолчанию 1/60) — нижняя граница: сенсор фьюзит в фоне на частоте не ниже её, а ты
опрашиваешь самый свежий сэмпл каждый кадр, так что частота сенсора не обязана совпадать с частотой
кадров. frame по умолчанию "world": attitude/gravity приходят уже преобразованными в Y-up
мировое пространство движка (а gravity — в экранное) и знают об ориентации — поверни телефон
в ландшафт, и камера останется вертикальной автоматически. Передай "device" для сырого фрейма
сенсора, если хочешь считать сам.
Опрашивай внутри setLoop; читаемые поля всегда возвращают последний сфьюженный сэмпл. attitude
скорректирован от дрейфа, так что горизонт панорамы остаётся ровным бесконечно.
гейтится хостом — тихий no-op там, где нет гироскопа (старые iPad, веб без разрешения, headless).
available скажет заранее; attitude читается как Quat.identity, а gravity — как (0,0,0),
когда обновления не идут.
Смотрите также
- События указателя и жесты — события, которые питает точный тач.
- Соглашения — логические px, паттерн
addEventListener.