Web-приложение в Capacitor работает внутри WebView — по сути браузера без адресной строки. А браузер живёт в песочнице: он не может просто взять и открыть камеру или прочитать файл на устройстве. Для этого нужно вырваться за границу веба.
Механизм, который даёт этот выход — мост (bridge). Он позволяет TypeScript-коду вызывать нативный Swift или Kotlin, получить результат и продолжить работу как ни в чём не бывало.
Почему браузер не может напрямую
Стандартный браузер на телефоне умеет многое через web-API: getUserMedia для камеры, navigator.geolocation для координат, localStorage для данных. Но возможности ограничены тем, что разрешает браузерная платформа, а не операционная система.
Когда нужно что-то за пределами этого списка — например, тихое снятие фото без интерфейса браузера, запись в произвольный каталог файловой системы, нативный диалог выбора контакта или Bluetooth-устройства — стандартные web-API уже не помогают.
Как работает мост
Мост Capacitor — двусторонний канал между JS и нативом:
- TypeScript вызывает метод плагина.
- Вызов сериализуется и уходит в нативный слой — Swift на iOS, Kotlin на Android.
- Нативный код обращается к системному SDK.
- Результат возвращается обратно в JS как обычный промис.
Для вызывающего кода это выглядит как любой другой await:
import { Geolocation } from "@capacitor/geolocation";
const position = await Geolocation.getCurrentPosition();
const { latitude, longitude } = position.coords;
Вся машинерия моста скрыта за плагином — в TypeScript не видно ни Swift, ни Kotlin.
Готовые плагины
Команда Capacitor поддерживает набор официальных плагинов, которые покрывают большинство типовых задач. Они устанавливаются как обычные npm-пакеты:
npm install @capacitor/camera @capacitor/geolocation @capacitor/filesystem
npm install @capacitor/preferences @capacitor/device
npx cap sync
Команда npx cap sync копирует веб-ресурсы и подтягивает нативные зависимости плагинов в проекты iOS и Android — её нужно запускать после каждой установки нового плагина.
Основные плагины:
@capacitor/camera— съёмка и выбор фото из галереи;@capacitor/geolocation— координаты и слежение за позицией;@capacitor/filesystem— чтение и запись файлов на устройстве;@capacitor/preferences— простое хранилище ключ-значение;@capacitor/device— информация об устройстве и платформе.
Помимо официальных существует большая экосистема сторонних плагинов — для Bluetooth, биометрии, сканирования штрихкодов. При выборе стоит смотреть на совместимость с вашей версией Capacitor и активность репозитория.
Пример: снимок с камеры
import { Camera, CameraResultType } from "@capacitor/camera";
async function takePhoto(): Promise<string> {
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
});
return photo.webPath ?? "";
}
CameraResultType.Uri просит вернуть путь к файлу, а не тяжёлую строку в формате base64: значение photo.webPath можно сразу подставить в атрибут src элемента <img>. Возвращаемое значение типизировано — опечатка в имени поля поймается компилятором.
Вызов оборачивают в try/catch: пользователь может отменить съёмку или отказать в доступе, и в обоих случаях плагин выбросит ошибку.
Разрешения
Доступ к камере, геолокации и файловой системе требует согласия пользователя. Плагины, которым нужны разрешения, предоставляют единую пару методов: checkPermissions возвращает текущий статус, requestPermissions показывает системный диалог.
Запрашивать разрешения стоит лениво — в момент, когда возможность реально понадобилась, а не при запуске приложения:
import { Camera } from "@capacitor/camera";
async function ensureCameraAccess(): Promise<boolean> {
const status = await Camera.checkPermissions();
if (status.camera === "granted") return true;
const requested = await Camera.requestPermissions();
return requested.camera === "granted";
}
Кроме вызовов в коде, нужно прописать описания разрешений в нативных конфигурационных файлах:
- iOS — ключ
NSCameraUsageDescriptionвInfo.plist; - Android — тег
<uses-permission>вAndroidManifest.xml.
Без этих строк системный диалог не появится, а приложение упадёт при первом обращении к защищённому ресурсу.
Свой плагин
Если нужного API нет ни в официальных, ни в сторонних плагинах, можно написать свой. Плагин состоит из трёх частей: TypeScript-интерфейс, реализация на Swift для iOS и реализация на Kotlin для Android.
TypeScript — объявляем интерфейс и регистрируем плагин:
import { registerPlugin } from "@capacitor/core";
export interface EchoPlugin {
echo(options: { value: string }): Promise<{ value: string }>;
}
export const Echo = registerPlugin<EchoPlugin>("Echo");
iOS — наследуемся от CAPPlugin:
import Capacitor
@objc(EchoPlugin)
public class EchoPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "EchoPlugin"
public let jsName = "Echo"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise)
]
@objc func echo(_ call: CAPPluginCall) {
let value = call.getString("value") ?? ""
call.resolve(["value": value])
}
}
Android — аннотация @CapacitorPlugin, имя должно совпадать с именем в registerPlugin:
@CapacitorPlugin(name = "Echo")
class EchoPlugin : Plugin() {
@PluginMethod
fun echo(call: PluginCall) {
val value = call.getString("value")
val ret = JSObject()
ret.put("value", value)
call.resolve(ret)
}
}
Для TypeScript-кода Echo.echo(...) неотличим от любого готового плагина — мост склеивает реализации по имени.
Мост или стандартный web-API
Для части возможностей на выбор: либо мост через плагин, либо стандартный web-API браузера. Например, геолокацию можно получить и через navigator.geolocation, и через @capacitor/geolocation.
Общее правило:
- web-API подходит, если не нужна глубокая нативная интеграция и важна совместимость с браузером;
- плагин выбирают, когда нужны возможности выше браузерного уровня или точный контроль над системными диалогами.
Коротко
- WebView изолирован от нативного SDK — для выхода за его пределы нужен мост Capacitor.
- Мост превращает вызов нативного кода в обычный
await-промис в TypeScript. - Официальные плагины закрывают камеру, геолокацию, файловую систему, хранилище и информацию об устройстве.
- После установки любого плагина обязательно запускать
npx cap sync. - Разрешения запрашивают лениво; строки-описания прописывают в
Info.plistиAndroidManifest.xml. - Свой плагин — один TypeScript-интерфейс плюс две нативные реализации (Swift и Kotlin).
Что почитать дальше
- Capacitor: веб-приложение в нативной обёртке — как работает сам контейнер и WebView.
- iOS: WKWebView, App Store и ограничения — специфика iOS-платформы.
- Android: TWA и WebView — специфика Android-платформы.
- Офлайн и хранилище — работа с данными без сети.