Лупа для изображений: как сделать на JavaScript
Лупа для изображений — лёгкий способ дать пользователю возможность увеличивать фрагменты фото прямо на странице. В статье описан пошаговый пример реализации на чистом HTML/CSS/JS, инструкции по настройке, проверочные критерии и рекомендации по адаптации под разные сценарии.
Важно: исходные пути изображений и атрибуты классов сохранены. В коде даны рабочие и читаемые фрагменты, которые можно копировать и адаптировать.

О чём эта статья
- Что такое лупа для изображений и где она полезна
- Пошаговое создание компонента с примерами кода
- Критерии приёмки и тестовые сценарии
- Подсказки по производительности и альтернативы
Зачем нужна лупа для изображений
Лупа даёт пользователю возможность подробно рассмотреть часть изображения без перехода в полноэкранный режим. Это полезно:
- Для интернет-магазинов, где покупатель оценивает качество и детали товара.
- Для фотогалерей и портфолио, где важны части кадра.
- Для карт, схем, технических иллюстраций.
Преимущества:
- Улучшает понятность изображений.
- Помогает пользователю принять решение о покупке.
- Уменьшает число возвратов за счёт точного представления товара.
Когда не стоит использовать лупу:
- Если изображения уже связаны с интерактивным 3D-просмотром или высокодетализированным viewer’ом.
- Для маленьких декоративных иконок — увеличение не даст смысла.
Файловая структура и лицензия
Код примера свободен и может распространяться по MIT-лицензии. Создайте папку проекта и добавьте файлы:
- index.html
- style.css
- main.js
Ниже приведён минимально полный рабочий пример. Вы можете менять тексты, пути к изображениям и стили.
HTML: базовая разметка
Этот HTML обеспечивает заголовок, инструкцию по управлению и контейнер для изображения с лупой. В примере локализованы тексты интерфейса — это удобно при копировании проекта в русскоязычные продукты.
Лупа для изображений
Лупа для изображений
Нажимайте Arrow Up или Arrow Down чтобы
увеличить или уменьшить уровень зума.
Уровень зума: 1
CSS: внешний вид и базовая позиция
Стиль задаёт размер контейнера, фон и оформление самой лупы. Переменные CSS позволяют легко менять размер лупы.
:root {
--magnifier-width: 150;
--magnifier-height: 150;
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
.container {
width: 400px;
height: 300px;
background-size: cover;
background-image: url("https://cdn.pixabay.com/photo/2019/03/27/15/24/animal-4085255_1280.jpg");
background-repeat: no-repeat;
position: relative;
cursor: none;
}
.magnifier {
border-radius: 400px;
box-shadow: 0px 11px 8px 0px #0000008a;
position: absolute;
width: calc(var(--magnifier-width) * 1px);
height: calc(var(--magnifier-height) * 1px);
cursor: none;
background-image: url("https://cdn.pixabay.com/photo/2019/03/27/15/24/animal-4085255_1280.jpg");
background-repeat: no-repeat;
}
span {
display: block;
}
.header {
display: flex;
flex-direction: column;
align-items: center;
}
.hidden {
visibility: hidden;
}
div > span:nth-child(3) {
font-size: 20px;
}JavaScript: логика лупы
В JS мы получаем элементы, отслеживаем позицию курсора, управляем уровнем зума и обновляем фон лупы. Ниже — аккуратно оформленный и исправленный пример, готовый к использованию.
let magnifier = document.querySelector(".magnifier");
let container = document.querySelector(".container");
// Читаем размеры лупы как числа (px)
let magnifierWidth = parseFloat(getComputedStyle(magnifier).width);
let magnifierHeight = parseFloat(getComputedStyle(magnifier).height);
let zoomLevelLabel = document.querySelector(".zoom-level");
let zoom = 2; // начальный множитель
let maxZoomLevel = 5;
let pointerX;
let pointerY;
let magnifyX;
let magnifyY;
function getZoomLevel() {
return zoom;
}
function getPointerPosition() {
return { x: pointerX, y: pointerY };
}
function updateMagImage() {
let evt = new MouseEvent("mousemove", {
clientX: getPointerPosition().x,
clientY: getPointerPosition().y,
bubbles: true,
cancelable: true,
view: window,
});
container.dispatchEvent(evt);
}
window.addEventListener("keyup", (e) => {
if (e.key === "ArrowUp" && Number(zoomLevelLabel.textContent) < maxZoomLevel) {
zoomLevelLabel.textContent = +zoomLevelLabel.textContent + 1;
zoom = zoom + 0.3;
updateMagImage();
}
if (e.key === "ArrowDown" && Number(zoomLevelLabel.textContent) > 1) {
zoomLevelLabel.textContent = +zoomLevelLabel.textContent - 1;
zoom = zoom - 0.3;
updateMagImage();
}
});
container.addEventListener("mousemove", (e) => {
magnifier.classList.remove("hidden");
let rect = container.getBoundingClientRect();
// Используем clientX/Y относительно контейнера
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
// Центрируем лупу так, чтобы курсор был в середине
let translateX = x - magnifierWidth / 2;
let translateY = y - magnifierHeight / 2;
magnifier.style.transform = `translate(${translateX}px, ${translateY}px)`;
const imgWidth = rect.width;
const imgHeight = rect.height;
magnifier.style.backgroundSize =
imgWidth * getZoomLevel() + "px " + imgHeight * getZoomLevel() + "px";
// Позиционирование фона так, чтобы видимый центр совпадал с курсором
magnifyX = x * getZoomLevel() - magnifierWidth / 2;
magnifyY = y * getZoomLevel() - magnifierHeight / 2;
magnifier.style.backgroundPosition = -magnifyX + "px " + -magnifyY + "px";
});
container.addEventListener("mouseout", () => {
magnifier.classList.add("hidden");
});
window.addEventListener("mousemove", (e) => {
pointerX = e.clientX;
pointerY = e.clientY;
});Пояснения к коду и подсказки
- Мы используем background-image в контейнере и в лупе, чтобы совпадало растровое содержимое. Это простая и совместимая техника.
- Для адаптивности можно динамически читать rect.width/height — в примере это сделано в обработчике mousemove.
- Значение zoom хранится как множитель. Удобно синхронизировать его с числами в интерфейсе (zoom-level).
- Применение transform: translate обеспечивает плавное позиционирование и работает аппаратно ускоренно.
Советы по улучшению:
- Добавьте плавное появление/исчезание лупы через transition для лучшего UX.
- Поддержите сенсорные устройства: на мобильных покажите кнопку «Увеличить» для полноэкранного просмотра, т.к. hover не всегда доступен.
- Загружайте отдельную, более высокомасштабную версию изображения для лупы, если исходник мал в разрешении.
Критерии приёмки
- Лупа появляется при наведении курсора внутрь контейнера.
- Лупа скрывается при выходе курсора из контейнера.
- Фон внутри лупы корректно показывает увеличенную область, соответствующую положению курсора.
- Клавиши Arrow Up/Arrow Down изменяют уровень зума, интерфейсный индикатор обновляется.
- На разных стационарных разрешениях позиционирование остаётся точным.
Роль‑ориентированные чеклисты
Разработчик:
- Проверить, что background-image совпадает в контейнере и в .magnifier.
- Убедиться, что размеры читаются динамически и не жёстко заданы.
- Тестировать на edge-cases: быстрый mousemove, ресайз окна.
Дизайнер:
- Подобрать форму и тень лупы, чтобы она не заслоняла критичные зоны интерфейса.
- Решить поведение для мобильных (другой паттерн увеличения).
QA:
- Проверить клавиатурную доступность и отсутствие препятствий для пользователей с ограничениями.
- Убедиться, что низкое разрешение источника не ломает UX.
Тестовые сценарии и приёмочные кейсы
- Навести курсор в центр изображения — лупа показывает увеличение соответствующего фрагмента.
- Перемещать курсор по краям — убедиться, что лупа не уезжает за границы контейнера некорректно.
- Быстро нажимать Arrow Up/Arrow Down — зум изменяется без ошибок и без исключений в консоли.
- Отключить фон-изображение в контейнере — лупа должна скрываться или показывать корректную заглушку.
Производительность и оптимизация
- Не создавайте тяжелые пер-кадровые операции внутри mousemove. Ограничьте вычисления и используйте requestAnimationFrame, если нужно оптимизировать при высокой нагрузке.
- Используйте отдельное изображение высокого разрешения только внутри лупы, чтобы не загружать сразу тяжелые ресурсы для всех пользователей.
- Для множества миниатюр применяйте делегирование событий, а не подключайте обработчики к каждому элементу.
Альтернативные подходы
- Использовать canvas: даёт полный контроль над рендерингом и позволяет применять фильтры и пиксельные эффекты. Но требует больше кода.
- Использовать SVG: удобно для векторной графики и масштабирования без потерь.
- Подключить готовую библиотеку (например, крупные open-source плагины) — быстрее внедряется, но увеличивает вес проекта и может переизбыточно покрывать требования.
Когда лупа не подойдёт
- Когда нужен многопользовательский viewer с синхронизацией изменений (требуется сложная логика).
- Для мобильных устройств без hover лучше предоставить альтернативный UI, например, полноэкранный viewer или pinch-to-zoom.
Мини‑методология внедрения (пошагово)
- Выбрать элемент(ы)-источники изображений и убедиться в доступности высококачественной версии.
- Добавить стили контейнера и лупы, настроить размеры через CSS-переменные.
- Подключить скрипт и протестировать в десктопных браузерах.
- Добавить адаптивный сценарий для мобильных.
- Запустить A/B тест, если нужно измерить влияние на конверсии.
Галерея крайних случаев
- Малое исходное изображение: лупа покажет пикселизацию — рекомендуется загружать отдельное hi-res изображение.
- Скролл страницы: убедиться, что координаты рассчитываются относительно clientX/clientY и rect, а не pageX без учёта скролла.
- Быстрое перемещение мыши: поведение остаётся предсказуемым, если вычисления оптимизированы.
Короткое резюме
Лупа для изображений — недорогой по усилиям, но мощный UX-компонент. Она повышает доверие пользователя, помогает лучше рассмотреть детали и может положительно влиять на конверсию. Реализация на чистом JavaScript проста и легко настраивается. При внедрении учитывайте доступность и мобильные сценарии.
Ключевые выводы
- Лупа улучшает восприятие товара и служит мостом между статичной картинкой и физическим осмотром.
- Для десктопа hover-лупа — оптимальный вариант; для мобильных нужны альтернативы.
- Следите за производительностью и используйте более качественные изображения только при необходимости.
Краткое объявление для продукта: новая лупа для изображений теперь улучшает просмотр товаров и повышает доверие покупателей за счёт детальной визуализации.
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента