ZoomiJS: документация на русском, лицензия MIT (делай что хочешь, даже в коммерции), автор на связи в РФ. Zoomi делает одну вещь: открывает <img> в модалке с зумом и навигацией
Довольно спорный момент так как например код js нет комментариев, и получается что нужно догадаться что за функционал тут и для чего нужно. Опять это не критика и не хейт, просто сама библиотека неплохая если воспримете меня правильно вот некоторые моменты.
🔴 Критические / баги
1. _unbindEvents вызывается после destroy — self.modal уже null
// destroy():
self.modal = null;
// ...
self._unbindEvents(); // <-- здесь self.modal уже null!
// В _unbindEvents:
if (self._handlers.keydown) self.modal.removeEventListener(...) // TypeError
_unbindEvents() нужно вызывать до обнуления ссылок.
2. navigate() — анимация через translateX = 100 работает некорректно
self.translateX = direction * 100; // пиксели? проценты?
self._updateImageTransform(); // translate(100px, 0px) scale(1)
Это сдвинет изображение на 100 пикселей, а не «за экран». Анимация перехода будет почти незаметна. Нужно либо 100vw, либо анимация через CSS-классы.
3. open() — self.modalImg.src устанавливается внутри onLoad нового Image(), но onLoad вызывается сразу при .complete, игнорируя реальную проверку
if (self._preloadImages[src] && self._preloadImages[src].complete) {
onLoad(); // вызов без установки modalImg.src заранее
} else {
var tempImg = new Image();
tempImg.onload = onLoad;
tempImg.src = src; // src задаётся ПОСЛЕ привязки onload — ок
}
Внутри onLoad есть self.modalImg.src = src — это нормально. Но при .complete нет проверки naturalWidth > 0 (изображение могло загрузиться с ошибкой и тоже .complete === true).
🟡 Средние проблемы
4. Утечка памяти в _preloadImages
Кеш предзагруженных изображений никогда не очищается — ни при navigate, ни при close, ни при смене галереи. При большом количестве изображений это накапливается.
5. _transformRAF не отменяется при destroy
// В destroy() нет:
if (self._transformRAF) cancelAnimationFrame(self._transformRAF);
RAF-колбэк выполнится после уничтожения объекта и попытается обратиться к self.modalImg — null.
6. updateOptions с options.selector содержит баг
if (options.selector && options.selector !== self.options.selector) self._parseSelectors();
К моменту проверки self.options уже обновлён через extend, поэтому options.selector !== self.options.selector всегда false. Нужно сохранять старый селектор до extend.
7. _bindMouseEvents — swipe через мышь имеет конфликт с drag
self._handlers.mouseDown = function(e) {
if (self.scale > 1) {
self._startDrag(...);
} else if (self.galleryImages.length > 1) {
self._startSwipe(...);
}
};
Если scale > 1 — swipe недоступен. Но пользователь может захотеть листать галерею при зуме, свайпнув достаточно далеко. Логика взаимоисключающая без явной причины.
🟢 Мелкие / стиль
8. _updateNavButtons — кнопки всегда disabled = false при length > 1
if (self._prevBtn) self._prevBtn.disabled = self.galleryImages.length <= 1;
При length > 1 кнопки будут disabled = false — это правильно, но выглядит как неинтуитивная логика. Лучше явно: self._prevBtn.disabled = false.
9. Строки интерфейса захардкожены на русском
self.modal.setAttribute('aria-label', 'Просмотр изображения');
self._closeBtn.setAttribute('aria-label', 'Закрыть модальное окно');
// ...
self.modalCaption.textContent = 'Ошибка загрузки изображения';
Это должно быть в DEFAULTS для поддержки i18n.
10. debounce — нет немедленного вызова (leading edge)
Для resize задержка 150ms — нормально. Но функция не экспортируется и не переиспользуется. Можно оставить, просто пометить как утилиту.
11. Отсутствует passive: true на touchstart/touchmove на document
На самом modalImg стоит { passive: false } — ок, нужен preventDefault. Но на document для mousemove/mouseup passive не указан, хотя там preventDefault не вызывается.
Самое важное к исправлению: порядок вызовов в destroy(), анимация в navigate(), баг в updateOptions, и очистка _preloadImages.