Гид по технологиям

Сканирование QR в браузере: jsQR + Web Worker

7 min read Веб-разработка Обновлено 01 Dec 2025
Сканирование QR в браузере: jsQR + Web Worker
Сканирование QR в браузере: jsQR + Web Worker

Сканирование QR-кода смартфоном

Быстрые ссылки

  • Получение камеры
  • Захват видеопотока в
  • Создание canvas
  • Добавление jsQR в воркер
  • Обновляющий цикл и отправка кадров
  • Формат результата jsQR
  • Критерии приёмки и тесты
  • Альтернативные подходы и ограничения
  • Безопасность и конфиденциальность

Введение

QR-коды широко используются для передачи ссылок, идентификаторов и коротких данных. Веб‑реализация сканера позволяет пользователю считывать коды без установки приложения. В этой инструкции показано, как совместить jsQR и Web Worker, чтобы: 1) избежать тормозов интерфейса, 2) обрабатывать по одному кадру за раз, 3) обеспечить стабильность на мобильных устройствах.

Определение: jsQR — библиотека на JavaScript для детекции QR-кодов в изображениях. Web Worker — фоновый веб‑поток, изолированная среда исполнения JavaScript.

1. Получение камеры

Основная задача — получить MediaStream через navigator.mediaDevices. Ниже — аккуратный пример с обработкой ошибок и выбором камеры.

const getCamera = async () => {
  if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
    throw new Error("mediaDevices API недоступен.");
  }

  const devices = await navigator.mediaDevices.enumerateDevices();
  const cameras = devices.filter(d => d.kind === "videoinput");

  if (cameras.length === 0) {
    throw new Error("Камера не найдена.");
  }

  // Можно добавить логику выбора конкретной камеры по deviceId
  return cameras[0];
};

Если пользователь блокировал доступ к камере, браузер выбросит ошибку при запросе прав. Предусмотрите UI‑подсказку с инструкциями по открытию настроек браузера.

2. Захват видеопотока в

Получите поток и прикрепите его к

const startVideo = async () => {
  const selectedCamera = await getCamera();
  const stream = await navigator.mediaDevices.getUserMedia({
    video: { deviceId: selectedCamera.deviceId }
  });

  const video = document.getElementById("video");
  video.srcObject = stream;
  await video.play();
  return stream;
};

Пояснение: playsinline нужен на iOS для воспроизведения встраиваемого видео без перехода в полноэкранный режим.

3. Создание canvas и извлечение пикселей

Canvas служит как буфер для текущего кадра видео. Его размер должен совпадать с разрешением видеопотока.

const setupCanvasFromStream = (stream) => {
  const tracks = stream.getVideoTracks();
  const settings = tracks[0].getSettings();

  const canvas = document.createElement("canvas");
  canvas.width = settings.width || 640; // fallback
  canvas.height = settings.height || 480;
  const ctx = canvas.getContext("2d");

  return { canvas, ctx };
};

Замечание: на некоторых устройствах getSettings() может вернуть undefined для width/height — используйте резервные значения или измеряйте video.videoWidth/video.videoHeight после начала воспроизведения.

4. Добавление jsQR в Web Worker

Запуск jsQR в главном потоке может замедлить интерфейс, особенно на старых телефонах. Вынесите вызов jsQR в воркер.

Главный поток создаёт воркер и слушает сообщения:

const qrWorker = new Worker("/qr-worker.js");

qrWorker.addEventListener("message", ({ data }) => {
  if (data) {
    // Данные QR-кода обнаружены
    // Например: обработать data.data (строка), показать UI, выполнить навигацию
    console.log('QR:', data.data);
    // здесь можно остановить цикл или предпринять дальнейшие действия
  } else {
    // Нет кода в кадре — подать следующий кадр
    tick();
  }
});

Код воркера (/qr-worker.js):

importScripts("jsQR.js");

self.addEventListener("message", e => {
  const { data, width, height } = e.data; // data: ImageData или Uint8ClampedArray
  const qrData = jsQR(data.data || data, width, height);
  self.postMessage(qrData || null);
});

Пояснение: importScripts загружает библиотеку jsQR в контекст воркера. В сообщении мы ожидаем либо ImageData, либо объект с полями data,width,height.

5. Обновляющий цикл: передаём кадры во воркер

Создайте цикл, который рисует текущий кадр video на canvas, извлекает imageData и отправляет его в воркер. Важно: отправляйте кадры последовательно — ждите ответа воркера, прежде чем отправлять следующий, чтобы избежать накопления задач.

let running = true;

const updateJsQr = () => {
  // Нарисовать текущий кадр на canvas
  canvasCtx.drawImage(video, 0, 0, canvas.width, canvas.height);
  const imageData = canvasCtx.getImageData(0, 0, canvas.width, canvas.height);

  // Передать только необходимые данные — ImageData включает .data (Uint8ClampedArray)
  qrWorker.postMessage({ data: imageData, width: canvas.width, height: canvas.height });
};

const tick = () => {
  if (!running) return;
  requestAnimationFrame(updateJsQr);
};

// Запуск после инициализации видео/канвы
tick();

Замечание: при отправке больших объектов между потоками можно использовать Transferable объекты (ArrayBuffer) для повышения производительности. ImageData.data.buffer можно передавать как transferable, но нужно аккуратно работать с типами и совместимостью браузеров.

6. Формат результата jsQR

При успешном распознавании jsQR возвращает объект со свойствами:

  • data — строка, извлечённая из QR-кода.
  • binaryData — Uint8ClampedArray с сырыми байтами (если требуется двоичная обработка).
  • version — версия QR-кода.
  • location — объект с координатами углов обнаруженного кода (topLeft, topRight, bottomLeft, bottomRight), полезен для оверлея и проверки положения.

Если код не найден, возвращается null/undefined.

Пример обработки результата в главном потоке:

qrWorker.addEventListener('message', ({ data }) => {
  if (data) {
    // Отобразить рамку по data.location
    drawOverlay(data.location);
    // Обработать полезную нагрузку
    handleQrPayload(data.data);
    running = false; // остановить цикл, если нужно одноразовое сканирование
  } else {
    tick();
  }
});

7. UX и устойчивость: лучшие практики

  • Попросите пользователя обеспечить достаточное освещение и держать камеру устойчиво.
  • Покажите подсказку, когда камера не имеет разрешения или отсутствует.
  • Дайте пользователю выбор камеры (фронт/тыл) при наличии нескольких.
  • Включите визуальный оверлей (крупная рамка) и анимацию при успешном считывании.
  • Ограничьте размер canvas, если видеопоток очень большой, для уменьшения нагрузки.

Important: не отправляйте кадры чаще, чем воркер успевает обрабатывать. Ожидайте ответа, прежде чем посылать следующий.

8. Критерии приёмки

  1. Скринтесты: при хорошем освещении код распознаётся за <3 попыток (с поправкой на устройство).
  2. UI не блокируется во время сканирования (главный поток остаётся отзывчивым).
  3. При запрете камеры показывается понятное сообщение с инструкциями по включению прав.
  4. Обработка результата вызывает ожидаемую навигацию/вызов API без дублирования.
  5. Воркеры корректно закрываются при уходе со страницы.

9. Тестовые случаи / Приёмочные тесты

  • Позитивные тесты:
    • QR с URL => приложение открывает URL.
    • QR с коротким текстом => текст показывается в модальном окне.
  • Негативные тесты:
    • Плохое освещение — приложение остаётся отзывчивым и не падает.
    • Блокировка камеры — выводится ошибка без крушения.
    • Дублирующиеся отправки — single‑shot детектирование не срабатывает дважды.
  • Граничные случаи:
    • Высокое разрешение камеры — проверка производительности.
    • Несколько камер — выбор фронт/тыл.

10. Чек‑лист по ролям

Разработчик:

  • Реализовать корректную инициализацию камеры.
  • Создать canvas с корректными размерами.
  • Вырезать и отправлять ImageData в воркер.
  • Обработать результат и закрыть воркер.

Тестировщик:

  • Проверить сценарии освещения и углов.
  • Выполнить нагрузочные тесты на слабых устройствах.

Продуктовый менеджер:

  • Утвердить требуемое поведение после распознавания (навигация/показ данных).

11. Альтернативные подходы и ограничения

Альтернативы:

  • Использовать нативные API (например, Barcode Detection API): проще, но поддержка браузеров ограничена.
  • Применять WebAssembly‑библиотеки (ZXing в виде WASM): потенциально быстрее, но сложнее в интеграции.
  • Обрабатывать кадры на сервере (отправлять изображение на бэкенд): повышает задержку и создаёт проблемы конфиденциальности.

Когда это не работает:

  • Очень плохое освещение, повреждённый/искажённый QR-код.
  • Камера с низким разрешением или сильным шумом.
  • Браузеры без поддержки Web Worker или без getUserMedia (очень старые).

Рекомендация: если ваше приложение должно работать в большом количестве устаревших браузеров, реализуйте graceful fallback с ручным вводом кода.

12. Производительность и оптимизации

  • Используйте transferable objects: передавайте imageData.data.buffer во воркер для избежания копирования.
  • Масштабируйте canvas до меньшего разрешения при обнаружении, что обработка занимает слишком много времени.
  • Профилируйте на целевых устройствах: частотность кадров (FPS) и время выполнения jsQR — ключевые метрики.

Фактическая метрика: время выполнения jsQR зависит от размера изображения и мощности CPU. На слабых устройствах уменьшение размера кадра в 2× часто даёт заметное ускорение.

13. Безопасность и конфиденциальность

  • Камера: не храните необработанные кадры на сервере без явного согласия пользователя.
  • Передача: если отправляете кадры/изображения на сервер для распознавания, используйте HTTPS и удаляйте снимки после обработки.
  • Правила GDPR: при обработке персональных данных (видео лиц, метаданные) обеспечьте юридическую основу (согласие) и минимизацию данных.

Privacy note: сканирование QR-кода обычно извлекает только текст/URL — опасность появляется, если вы сохраняете или пересылаете полные кадры камеры.

14. Совместимость и миграция

Поддерживаемые платформы:

  • Современные десктопные браузеры и мобильные браузеры с поддержкой getUserMedia и Web Workers.

Проверки:

  • iOS Safari: требует playsinline и пользовательского взаимодействия для запуска некоторых потоков.
  • Android Chrome: работает из коробки в большинстве версий.

Миграция: если вы используете сборщик (webpack/Parcel), можно подключать jsQR как npm‑пакет и инлайнить воркер через worker-loader или создавая отдельный бандл.

15. Примеры кода: полный сценарий

Ниже — упрощённый поток инициализации, объединяющий шаги вместе.

(async () => {
  const camera = await getCamera();
  const stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: camera.deviceId } });

  const video = document.getElementById('video');
  video.srcObject = stream;
  await video.play();

  const { canvas, ctx } = setupCanvasFromStream(stream);
  document.body.appendChild(canvas); // опционально для отладки

  const qrWorker = new Worker('/qr-worker.js');

  let processing = false;
  let stopped = false;

  qrWorker.addEventListener('message', ({ data }) => {
    processing = false;
    if (data) {
      console.log('QR payload:', data.data);
      // здесь можно остановить или продолжать в зависимости от UX
      stopped = true; // пример: остановить после первого обнаружения
    }
  });

  const loop = () => {
    if (stopped) return;
    if (!processing) {
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      // Можно передать Transferable: imageData.data.buffer
      qrWorker.postMessage({ data: imageData, width: canvas.width, height: canvas.height });
      processing = true;
    }
    requestAnimationFrame(loop);
  };

  loop();
})();

16. Проблемы и отладка

  • Если воркер не загружаются: проверьте путь /qr-worker.js и права CORS.
  • Если jsQR возвращает null всегда: проверьте корректность передаваемого imageData (ширина/высота), убедитесь, что код виден целиком и освещён.
  • Если интерфейс тормозит: ограничьте разрешение canvas или уменьшите частоту отправки кадров.

17. Короткая методология внедрения (SOP)

  1. Подключите jsQR (локально или npm).
  2. Сделайте компонент камеры и проверку разрешений.
  3. Создайте canvas с размерами потока.
  4. Напишите воркер с importScripts(‘jsQR.js’).
  5. Реализуйте цикл отправки кадров и ожидания ответа.
  6. Обработайте результат и закройте воркер/поток.
  7. Проведите тесты на целевых устройствах и добавьте fallback.

18. Краткий словарь (1‑строчники)

  • jsQR: библиотека для распознавания QR на JavaScript.
  • Web Worker: фоновый поток выполнения JavaScript.
  • MediaStream: поток медиа (видео/аудио) от устройства.
  • canvas: HTML элемент для рисования и извлечения пикселей.

Заключение

Комбинация jsQR и Web Worker — надёжный подход для реализации веб‑сканера QR: она сохраняет отзывчивость интерфейса и позволяет масштабировать решение под разные устройства. При внедрении обратите внимание на обработку ошибок, безопасность и тесты на целевых платформах.

Ключевые вещи: держите обработку кадров последовательной, не отправляйте следующий кадр, пока воркер не ответил; минимизируйте объём данных и уважайте приватность пользователя.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Обнаруживаемо другими в iOS: что это и как отключить
Конфиденциальность

Обнаруживаемо другими в iOS: что это и как отключить

Как бесплатно разместить fan-gate Facebook на Heroku
Веб-разработка

Как бесплатно разместить fan-gate Facebook на Heroku

Twitch PiP: как включить и смотреть
Руководство

Twitch PiP: как включить и смотреть

CSGO и высокая загрузка CPU — что делать
Игры

CSGO и высокая загрузка CPU — что делать

CS:GO не подключается к серверам — как исправить
Техподдержка игр

CS:GO не подключается к серверам — как исправить

Как начать продавать на Amazon — 12 ресурсов
Электронная коммерция

Как начать продавать на Amazon — 12 ресурсов