Веб-портфолио с управлением жестами через камеру: полевой дневник
- среда, 15 июля 2026 г. в 00:00:14
Я продуктовый дизайнер. И мне захотелось поэкспериментировать: можно ли уже сейчас сделать полноценное управление сайтом с помощью жестов. Да и ИИшки уже стали достаточно мощными. Так родился этот эксперимент. Для контента взял свое портфолио.
Получилось вот что. Страница встречает полем из частиц. Разрешаешь камеру, и частицы собираются в твоё объёмное отражение. Разделы висят вокруг силуэта и открываются рукой: наводишь палец, пункт заполняется и раскрывается. Текст листается щипком, фотографии берутся в кулак и растягиваются двумя руками. Дизайн при этом намеренно тихий: монохром, костяная типографика на чёрном, единственный цвет живёт внутри 3D-формы. Вся выразительность отдана взаимодействию.

Дальше — грабли по порядку наступания.
Потрогать: https://dimbo-design.github.io/dimbo_heavy-cv/ (десктоп, вебкамера).
Код: https://github.com/dimbo-design/dimbo_heavy-cv
Сразу о разделении труда, без него не объяснить формат статьи. Код писал Claude Code. За мной замысел, дизайн-решения и полевые тесты. Я дизайнер, и в инженерной части проекта понимаю далеко не всё, притворяться не буду. Поэтому договоримся так: как я работал над проектом описываю сам. Где начинается разработка, буду давать объяснение от Клода.
Да, и эта статья написана так же: черновик собирал Клод, окончательный текст и всё, за что тут можно поругать — мое.
Что я понимаю сам: всё считается на устройстве, ни один кадр с камеры никуда не отправляется, сервера нет вообще. Статика на GitHub Pages, без сборки.
Дальше цитирую Клода:
Глубину кадра считает Depth Anything V2 Small (ONNX) через Transformers.js: WebGPU, с WASM-фолбэком для Safari/Firefox. Инференс живёт в Web Worker и работает с адаптивной частотой: 20 fps, когда человек в кадре, 11 fps, пока он читает главу, 6 fps в пустой комнате. Руки и скелет отслеживает MediaPipe Hand/Pose Landmarker на основном потоке (~5 мс на кадр). Рендер на Three.js: одна геометрия точек, один шейдер на все состояния сайта, один draw call.
Из всего этого мне как владельцу продукта оказалось критично понять одно (а понял это не сразу): рендер практически бесплатный, вся цена в том, чтобы железо потянуло и не перегревалось. Частиц можно насыпать вдвое больше без последствий — подобрал приятное глазу количество. Компромисс с производительностью — в частоте нейросети: она решает, как часто обновляется отражение.
И сразу про ограничение, очевидное всем кто в теме, но важное для остальных: камера как единственный источник ввода — слабый сенсор. Она не меряет глубину, только дает плоское изображение. Весь проект — подтверждение того, что существующие библиотеки уже могут дотянуть этот слабый сигнал до рабочего интерфейса.
В первом проходе мы взяли стандартный словарь жестов из интернета, то, что устоялось в библиотеках. Через одну камеру сайт не смог с этим работать. Вводных не хватало для стабильной работы. А стартовые гипотезы разбились о простой факт: рука в воздухе ведёт себя не так, как палец на стекле — привычные плоские UX-паттерны к этому не готовы.
Большая часть моей роли свелась к дизайн-ревью: валидации жестов живой рукой. В сайт встроен дебаг-слой (⌥D): журнал всех жестовых событий за сессию плюс запись сырых движений кисти (⌥R). Цикл: я пользуюсь сайтом как обычный человек → ловлю момент «оно меня не поняло» → копирую журнал с записью → отдаю Клоду вместе с описанием чего я на самом деле хотел → он разбирает запись, объясняет механику отказа и предлагает правку → валидируем дальше. Прошли десятки таких итераций.

Ключевые слова здесь «чего я на самом деле хотел». Самый дорогой урок проекта: логи без описания намерений лгут. Однажды статистика движений показала Клоду, что 70% моих движений вниз — случайный шум. Реальность же: я целенаправленно пытался скроллить, а система читала подготовку к жесту как сам жест. Что показывает: чистые логи без контекста не дают полной картины.
Самое интуитивное движение: «толкнуть» ладонь к экрану. Оно не заработало, и я долго не понимал почему, пока не получил объяснение:
Одна RGB-камера измеряет приближение руки единственным способом: размером кисти в кадре. А он дрожит на ±35% от простого поворота ладони. Фильтры сглаживают дрожь, но вместе с ней и само движение.
От оси глубины отказались целиком. Из этого родилось правило, которое дальше держало весь словарь: жесты строятся только на том, что камера меряет надёжно. Положение кисти, раскрытость, щепоть, кулак, две руки.
Центральная проблема всего жестового ввода. На тачскрине жест дискретен, потому что есть стекло: поднял палец, жест кончился. В воздухе стекла нет, и у каждого взмаха есть возврат. Махнул вверх, рука вернулась вниз, система прочитала «вниз». Как пользователь я ощущал это как лаги: страница дёргается туда-сюда от одного моего движения.
Здесь я прошёл путь, который другие уже проходили до меня: попытался научить систему понимать обратные движения. Перепробовали умные фильтры: окна инерции, пороги решительности (обратный жест должен быть в полтора раза резче), анализ профиля скорости. Каждый работал на бумаге и разваливался в тесте по одной причине: любой подавленный ввод ощущается как лаг. Пользователь не ведёт себя предсказуемо и не знает, как подстроиться под способ анализа жестов. Он знает только своё намерение: махнул для скролла вниз, а оно не сработало.
Скролл разбился на два решения: безопасное и экспериментальное. В безопасном взял текущую стабильную практику с щипком. О том, что щипок индустриальный стандарт, узнал уже когда дошёл до решения: индустрия прошла тот же путь — от свайпов Kinect до Vision Pro все пришли к явным целям и щипку-скроллу.
А экспериментальное решение в итоге оказалось не алгоритмическим, а дизайнерским: двумя направлениями может владеть только жест с захватом: сжал, повёл, разжал — как щипок. У свободного взмаха нет ни начала, ни конца, поэтому каждым направлением владеет ровно одна семья жестов. Ладонь толкает лист вверх: читаешь дальше. Палец при неподвижной ладони тянет лист вниз: шаг назад. А мах ладонью вниз не жест вообще, тишина на любой скорости. Семьи не видят друг друга, и проблема возврата исчезла.
Живые показы это подтвердили. На демонстрациях, включая людей совсем не из ИТ, никто не прочитал «ладонь вверх» и «палец вниз» как пару «вперёд-назад»: каждый жест по отдельности очевиден, но обратными друг другу их никто не считает. Симметрия не построилась не потому, что мы не смогли, а потому, что в голове пользователя её нет.
Закон, который Клод сформулировал после того, как он трижды не понял, что каждому состоянию экрана нужен свой словарь:
Если жест на данном экране «не работает», его нельзя распознавать и игнорировать. Его нельзя распознавать вовсе. Распознанный-но-отброшенный взмах всё равно взводит кулдауны соседних жестов, и пользователь получает необъяснимые отказы там, где вообще ничего не делал.
В переводе на дизайнерский: у каждого состояния экрана свой словарь, и жесты другого экрана на нём не существуют. Не «запрещены», а именно не существуют.
Правда, вычистить сразу все нюансы Клод не смог и еще продолжал иногда наступать в ту же кучу, но проблема потихоньку ушла.
Руки не телепортируются. Разжал двуручный зум: вторая рука уходит из кадра ещё доли секунды, и её уход читается как новые жесты. Сжал-разжал кулак: кисть расслабляется в горсть, а горсть похожа на щепоть. Каждый выход из «тяжёлого» состояния закрыт тихим окном в полсекунды-полторы, когда соседние детекторы молчат. Мне нравится формулировать это так: жест кончается не тогда, когда кончилась команда, а когда рука вернулась в нейтраль.
Пороги держались, только когда за ними стояло физическое обоснование, а не подобранная константа. Как пример — фокус от статичной части тела. Она «выбирала» пункты меню, пока не появилось правило «выбирает то что двигается»: курсор, не проехавший 60 пикселей за 3,5 секунды, не рука, и его фокус ничего не открывает. Из той же серии: две ладони не могут занимать одну точку пространства, значит «фантомный» зум с нулевой дистанцией режется чистой геометрией, без всяких порогов.

В техническую кухню я не вдупляю. Как-то оно само регрессирует — вот описание от ИИ:
Два уровня. Первый — реплей: записанные сырые движения кисти (в том числе реальные сессии владельца с метками намерений) стали фикстурами, и детекторы прогоняются по ним как тесты: «эта запись обязана дать ровно один шаг назад и ноль щепотей». Второй — браузерный: puppeteer поднимает сайт с программно нарисованной депт-картой «человека» и синтетическими руками и проходит 22 проверки, от загрузки модели до открытия главы наведением. Если ваш ввод сенсоры, а не клики, запись и реплей реального ввода — самая дешёвая тестовая инфраструктура из возможных.

Вынес для себя: стоило записывать движения сразу. Это сократило бы число итераций уже на старте.
А так проверял сам и скидывал опробовать взаимодействие знакомым. Самым сильным тестом оказался показ компании не-айтишников: он вскрыл главный неучтённый разрыв — технологию они воспринимают совсем не так, как привык я. В итоге вернул точку курсора на главном экране и добавил призрачную руку-подсказку: когда посетитель буксует, сайт проигрывает запись моего собственного жеста, а после первого успешного выполнения жеста подсказка-призрак умирает.
Третья правка из поля оказалась не про жесты: у части посетителей из России нейросети просто не загружались — huggingface и CDN из их сетей недостижимы. Теперь все модели и библиотеки (~120 МБ) лежат в самом репозитории и грузятся с того же домена, что и страница, а внешние источники остались тихим резервом.
Пара «дизайнер-владелец + ИИ-инженер» рабочая, если у каждого есть своя зона ответственности. Мои: намерение, вкус, дизайн-навыки и полевые тесты. Только я знаю, что имел в виду движением и чувствую лаг взаимодействия. Его: архитектура, код и гипотезы. Каждая правка приходила с объяснением механики, которое я мог оспорить, и оспаривал: половина законов выше родилась из моего «погоди, я же не это имел в виду».
Как это делалось, я не скрываю: история коммитов открыта. Факт что «ИИ может написать сайт» давно не новость. Ценность истории в подходе: как была устроена работа — у кого какая зона, где чьё слово последнее и на чём проверялись гипотезы. По сути, вышло обычное разделение обязанностей, как в продуктовой команде.
Если соберётесь делать что-то сенсорное (жесты, голос, взгляд, любой ввод без «стекла»):
личные эксперименты погружают в сферу лучше чем статьи с готовыми ответами (но это дольше)
словарь выводится из записей реального использования, а не из текстового описания для ИИ; запись и реплей ввода стройте раньше самого словаря;
логи без меток намерений лгут: данные отвечают «что произошло», человек «что имелось в виду», работают только вместе;
стройте только на том, что можете мерить надёжно; у каждого направления один хозяин; мёртвый жест недетектируем; после сложного состояния тишина;
если работаете в паре с ИИ: определите зоны принятия решений; любую правку проводите с объяснением, которое можно оспорить.
Сайт живёт здесь: https://dimbo-design.github.io/dimbo_heavy-cv/ (нужен десктоп с gpu и вебкамерой; всё считается локально, видео никуда не уходит).
Полный словарь жестов в README репозитория: https://github.com/dimbo-design/dimbo_heavy-cv
А если найдёте в интерфейсе что-то, о чём здесь ни слова, значит нашли пасхалки)
Дмитрий Тогулев — Senior Product Designer, ex-KazanExpress, ex-Uzum.