К содержанию

Светопись — сайт фотографа без единой библиотеки

Собственная работа: витрина фотографа, где WebGL-шейдер, инерционная прокрутка и бронирование сделаны на чистом HTML, CSS и JavaScript. Ни одной внешней зависимости.

Год
2026
Роль
Дизайн, вёрстка, разработка
Стек
HTML · CSS · JavaScript · WebGL · Canvas
275 КБ
весит вся страница целиком
0,5 с
до первой отрисовки
0
внешних библиотек и сборщиков
922
строки собственного JavaScript

Что это

Сайт для фотографа: витрина работ, описание пакетов съёмки и бронирование даты. Проект собственный — я делал его, чтобы проверить, как далеко можно уйти в визуальной части, не подключая ни одного стороннего пакета.

Условие было жёстким: никаких фреймворков, никакой сборки, никаких CDN. Файл index.html открывается двойным щелчком с диска и работает целиком, включая шейдеры и анимации. Хостится где угодно — хоть на бесплатном статическом хостинге.

Что здесь движется

Титульный экран занимает живая дисперсия света — фрагментный шейдер на WebGL, где шум fbm разложен на три канала со сдвигом, отчего получается призма. Шейдер реагирует на курсор и на прокрутку: свет ведёт себя как настоящий, а не как зацикленное видео.

Прокрутка инерционная, собственная — один цикл requestAnimationFrame на весь сайт, все эффекты считаются в нём же. Заголовки поднимаются из-под маски по словам, технические подписи расшифровываются посимвольно, кнопки притягиваются к курсору, а секция «Лучшие моменты» превращает вертикальную прокрутку в горизонтальную ленту.

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

Где было больно

Инерция дралась с эффектами. Когда каждый эффект вешает свой обработчик на прокрутку, браузер захлёбывается: на движении получалось меньше тридцати кадров в секунду. Пришлось свести всё в один цикл анимации — позиция считается один раз за кадр, а все эффекты читают уже готовое значение. Ничего не подписывается на scroll напрямую.

Шейдер жёг батарею на ноутбуках. Полноэкранный WebGL крутился постоянно, даже когда титул уже уехал за пределы экрана. Решение простое и очевидное задним числом: IntersectionObserver останавливает отрисовку, как только титул уходит из вида, и запускает обратно при возврате.

Текст со сплошным градиентом. Дизайн требовал, чтобы градиент шёл через всю строку непрерывно, а не начинался заново в каждом слове. Пришлось считать для каждого слова его срез общего градиента по ширине контейнера — и пересчитывать при изменении размера окна.

Что получилось

Страница весит 275 КБ вместе со шрифтами и всей графикой — это меньше, чем одна фотография с телефона. Первая отрисовка за полсекунды, одиннадцать запросов на всю загрузку. Для сравнения: типичный сайт на конструкторе тянет три-четыре мегабайта и полсотни запросов.

Отдельная выгода такого подхода — у сайта нечему устареть. Нет пакетов, которые через год потребуют обновления, нет сборщика, который перестанет запускаться на новой версии Node. Через пять лет этот файл откроется так же, как сегодня.

Что можно проверить

Код открыт целиком: github.com/nadirzhon/svetopis. Никаких «чёрных ящиков» — видно каждую строку, включая шейдер и логику бронирования.

Это моя собственная работа, а не заказ: контакты на сайте — заглушки, фотографии демонстрационные. Показываю как пример подхода к визуально сложным сайтам, а не как выполненный клиентский проект.

Экраны

Снимки рабочей системы, а не макеты. Нажмите на любой — откроется во весь экран.