|
Поговорим о...
|
|
Проблема в том, что при изменении render-image.html нарушился вывод изображений. Вернем рабочий вариант и добавим lightbox правильно.
Восстановите оригинальный render-image.html и добавьте lightbox отдельно:
1. Верните оригинальный render-image.html:
```html {{- $alt := .PlainText | safeHTML -}} {{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}} {{- $dest := .Destination -}}
{{- $isLocal := not (urls.Parse $dest).Scheme -}} {{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} {{- $startsWithSlash := hasPrefix $dest "/" -}} {{- $startsWithRelative := hasPrefix $dest "../" -}}
{{- if and $dest $isLocal -}} {{- if $startsWithSlash -}} {{/* Images under static directory */}} {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} {{- else if and $isPage (not $startsWithRelative) -}} {{/* Images that are sibling to the individual page file */}} {{ $dest = (printf "../%s" $dest) }} {{- end -}} {{- end -}}
{{- with .Title -}} <figure> <img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} /> <figcaption>{{ . }}</figcaption> </figure> {{- else -}} <img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} /> {{- end -}} ```
2. Добавьте lightbox через JavaScript в scripts.html:
```html <!-- Ваши существующие скрипты -->
<!-- Lightbox CSS --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.4/css/lightbox.min.css" />
<!-- Lightbox JS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.4/js/lightbox.min.js"></script>
<script> // Ждем полной загрузки DOM document.addEventListener('DOMContentLoaded', function() { // Находим ВСЕ изображения на странице (в основном контенте) const images = document.querySelectorAll('main img, .content img, article img'); images.forEach((img, index) => { // Пропускаем маленькие изображения (иконки) if (img.width < 50 || img.height < 50) return; // Пропускаем изображения, которые уже в ссылках if (img.parentElement.tagName === 'A') return; // Создаем ссылку для lightbox const wrapper = document.createElement('a'); wrapper.href = img.src; wrapper.setAttribute('data-lightbox', 'gallery'); wrapper.setAttribute('data-title', img.alt || ''); // Обертываем изображение img.parentNode.insertBefore(wrapper, img); wrapper.appendChild(img); // Добавляем стили для курсора img.style.cursor = 'zoom-in'; img.style.transition = 'opacity 0.2s'; // Эффект при наведении img.addEventListener('mouseenter', () => { img.style.opacity = '0.9'; }); img.addEventListener('mouseleave', () => { img.style.opacity = '1'; }); }); // Настраиваем lightbox if (typeof lightbox !== 'undefined') { lightbox.option({ 'resizeDuration': 200, 'wrapAround': true, 'showImageNumberLabel': true, 'albumLabel': 'Изображение %1 из %2', 'fadeDuration': 300, 'imageFadeDuration': 300 }); } }); </script>
<!-- Простые стили для изображений --> <style> /* Стили для всех изображений в контенте */ main img, .content img, article img { border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); max-width: 100%; height: auto; }
main img:hover, .content img:hover, article img:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.15); }
/* Убираем стандартные стили lightbox для ссылок */ a[data-lightbox] { display: inline-block; border: none !important; text-decoration: none !important; } </style> ```
Альтернативный вариант: Простой кастомный lightbox без библиотек
Если не хотите использовать lightbox2, вот чистый JavaScript вариант:
Добавьте в scripts.html:
```html <script> // Простой кастомный lightbox class SimpleLightbox { constructor() { this.modal = null; this.modalImg = null; this.currentIndex = 0; this.images = []; this.init(); } init() { // Создаем модальное окно this.createModal(); // Находим все изображения после загрузки DOM document.addEventListener('DOMContentLoaded', () => { this.attachToImages(); }); } createModal() { this.modal = document.createElement('div'); this.modal.className = 'simple-lightbox'; this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); display: none; align-items: center; justify-content: center; z-index: 9999; cursor: pointer; `; const container = document.createElement('div'); container.style.cssText = ` position: relative; max-width: 90vw; max-height: 90vh; `; this.modalImg = document.createElement('img'); this.modalImg.style.cssText = ` max-width: 100%; max-height: 85vh; border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); cursor: default; `; const closeBtn = document.createElement('button'); closeBtn.innerHTML = '×'; closeBtn.style.cssText = ` position: absolute; top: -40px; right: 0; background: none; border: none; color: white; font-size: 40px; cursor: pointer; line-height: 1; padding: 0; width: 40px; height: 40px; `; closeBtn.onclick = () => this.close(); container.appendChild(closeBtn); container.appendChild(this.modalImg); this.modal.appendChild(container); document.body.appendChild(this.modal); // Закрытие по клику на фон this.modal.onclick = (e) => { if (e.target === this.modal) this.close(); }; // Закрытие по ESC document.addEventListener('keydown', (e) => { if (e.key === 'Escape') this.close(); }); } attachToImages() { // Находим все изображения в основном контенте const content = document.querySelector('main, .content, article'); if (!content) return; this.images = Array.from(content.querySelectorAll('img')) .filter(img => img.width > 50 && img.height > 50); this.images.forEach((img, index) => { // Добавляем стили img.style.cursor = 'zoom-in'; img.style.transition = 'opacity 0.2s'; // Эффект при наведении img.addEventListener('mouseenter', () => { img.style.opacity = '0.9'; }); img.addEventListener('mouseleave', () => { img.style.opacity = '1'; }); // Обработчик клика img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.currentIndex = index; this.open(img.src, img.alt); }); }); console.log(`Найдено ${this.images.length} изображений для lightbox`); } open(src, alt) { this.modalImg.src = src; this.modalImg.alt = alt; this.modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // Анимация появления this.modal.style.animation = 'fadeIn 0.3s ease'; this.modalImg.style.animation = 'zoomIn 0.3s ease'; } close() { this.modal.style.display = 'none'; document.body.style.overflow = ''; } }
// Инициализируем lightbox когда DOM загружен document.addEventListener('DOMContentLoaded', function() { new SimpleLightbox(); // Добавляем CSS анимации const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes zoomIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } } /* Стили для изображений */ main img, .content img, article img { cursor: zoom-in; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.2s ease; } main img:hover, .content img:hover, article img:hover { opacity: 0.9; box-shadow: 0 4px 16px rgba(0,0,0,0.15); } `; document.head.appendChild(style); }); </script> ```
Если изображения все еще не показываются:
1. Проверьте консоль браузера (F12) на ошибки 2. Временно добавьте тестовое изображение в ваш пост:
```markdown  ```
1. Проверьте структуру HTML - как выглядят изображения в инспекторе элементов
Быстрая диагностика:
Добавьте в конец single.html перед {{ end }}:
```html <script> console.log('Страница загружена'); console.log('Всего изображений на странице:', document.querySelectorAll('img').length); console.log('Изображения в .content:', document.querySelectorAll('.content img').length);
// Простой тест - добавим красную рамку всем изображениям setTimeout(() => { document.querySelectorAll('.content img').forEach(img => { img.style.border = '2px solid red'; console.log('Изображение найдено:', img.src); }); }, 1000); </script> ```
Шаги для восстановления:
1. Верните оригинальный render-image.html 2. Добавьте один из JavaScript вариантов в scripts.html 3. Перезапустите Hugo: hugo server --disableFastRender --forceSyncStatic 4. Проверьте в браузере с очищенным кэшем (Ctrl+F5)
Это должно восстановить отображение изображений и добавить функциональность увеличения.
|
Понял! Нужен кастомный lightbox с полупрозрачным фоном, только для изображений в основном контенте, без внешних библиотек. Вот рабочее решение:
Обновите render-image.html чтобы добавить классы для фильтрации:
```html {{- $alt := .PlainText | safeHTML -}} {{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}} {{- $dest := .Destination -}}
{{- $isLocal := not (urls.Parse $dest).Scheme -}} {{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} {{- $startsWithSlash := hasPrefix $dest "/" -}} {{- $startsWithRelative := hasPrefix $dest "../" -}}
{{- if and $dest $isLocal -}} {{- if $startsWithSlash -}} {{/* Images under static directory */}} {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} {{- else if and $isPage (not $startsWithRelative) -}} {{/* Images that are sibling to the individual page file */}} {{ $dest = (printf "../%s" $dest) }} {{- end -}} {{- end -}}
{{- with .Title -}} <figure class="article-image"> <img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="article-image__img" /> <figcaption class="article-image__caption">{{ . }}</figcaption> </figure> {{- else -}} <img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="article-image article-image__img" /> {{- end -}} ```
Добавьте в scripts.html (перед закрывающим </body>):
```html <!-- Ваши существующие скрипты -->
<!-- Кастомный Lightbox --> <script> class ArticleLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.init(); } init() { // Создаем элементы модального окна this.createModal(); // Инициализируем после загрузки DOM if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.setup()); } else { this.setup(); } } createModal() { // Основной контейнер this.modal = document.createElement('div'); this.modal.className = 'article-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.92); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(5px); `; // Контейнер для изображения const container = document.createElement('div'); container.className = 'article-lightbox__container'; container.style.cssText = ` position: relative; max-width: 90vw; max-height: 90vh; margin: 20px; `; // Кнопка закрытия const closeBtn = document.createElement('button'); closeBtn.className = 'article-lightbox__close'; closeBtn.innerHTML = '×'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.style.cssText = ` position: absolute; top: -50px; right: 0; background: none; border: none; color: white; font-size: 40px; cursor: pointer; line-height: 1; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; z-index: 10; transition: transform 0.2s ease; `; closeBtn.addEventListener('mouseenter', () => { closeBtn.style.transform = 'scale(1.2)'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.transform = 'scale(1)'; }); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); }); // Изображение this.modalImage = document.createElement('img'); this.modalImage.className = 'article-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 80vh; margin: 0 auto; border-radius: 8px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.95); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; `; // Подпись this.caption = document.createElement('div'); this.caption.className = 'article-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: white; font-size: 16px; margin-top: 20px; padding: 10px 20px; background: rgba(255, 255, 255, 0.1); border-radius: 4px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease; `; // Собираем структуру container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal); // Закрытие по клику на фон this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } }); // Закрытие по ESC document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } }); // Навигация стрелками document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); } setup() { // Находим только изображения в основном контенте статьи const mainContent = document.querySelector('main .content, article .content, .content'); if (!mainContent) return; this.images = Array.from(mainContent.querySelectorAll('.article-image__img')) .filter(img => { // Фильтруем только достаточно большие изображения return img.naturalWidth > 100 && img.naturalHeight > 100; }); console.log(`Найдено ${this.images.length} изображений для lightbox`); this.images.forEach((img, index) => { // Добавляем курсор и эффекты img.style.cursor = 'zoom-in'; img.style.transition = 'transform 0.2s ease, opacity 0.2s ease'; // Эффект при наведении img.addEventListener('mouseenter', () => { img.style.opacity = '0.95'; img.style.transform = 'translateY(-2px)'; }); img.addEventListener('mouseleave', () => { img.style.opacity = '1'; img.style.transform = 'translateY(0)'; }); // Обработчик клика img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.currentIndex = index; this.open(img.src, img.alt || img.title || ''); }); }); } open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; // Показываем модальное окно this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible'; // Запускаем анимации с небольшой задержкой requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); }); // Блокируем прокрутку страницы document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; } close() { // Скрываем с анимацией this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.95)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)'; setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); // Разблокируем прокрутку document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 300); } isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; } next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.src, nextImg.alt || nextImg.title || ''); } } prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { // Ждем загрузки изображений setTimeout(() => { window.articleLightbox = new ArticleLightbox(); }, 100); // Добавляем CSS стили const style = document.createElement('style'); style.textContent = ` /* Стили для изображений в статьях */ .article-image__img { border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; max-width: 100%; height: auto; display: block; margin: 1.5rem auto; } .article-image__img:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); } .article-image__caption { text-align: center; font-size: 0.9rem; color: #666; margin-top: 0.5rem; font-style: italic; } /* Анимации для lightbox */ @keyframes lightboxFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes lightboxZoomIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } } @keyframes lightboxSlideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } `; document.head.appendChild(style); }); </script> ```
Если нужны навигационные стрелки (опционально):
Добавьте этот код внутрь createModal() после создания closeBtn:
```javascript // Кнопки навигации (только если больше одного изображения) const navPrev = document.createElement('button'); navPrev.className = 'article-lightbox__nav article-lightbox__nav--prev'; navPrev.innerHTML = '‹'; navPrev.setAttribute('aria-label', 'Предыдущее изображение'); navPrev.style.cssText = ` position: absolute; left: -60px; top: 50%; transform: translateY(-50%); background: rgba(255, 255, 255, 0.1); border: none; color: white; font-size: 30px; width: 50px; height: 50px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s ease, transform 0.2s ease; backdrop-filter: blur(10px); `;
const navNext = document.createElement('button'); navNext.className = 'article-lightbox__nav article-lightbox__nav--next'; navNext.innerHTML = '›'; navNext.setAttribute('aria-label', 'Следующее изображение'); navNext.style.cssText = ` position: absolute; right: -60px; top: 50%; transform: translateY(-50%); background: rgba(255, 255, 255, 0.1); border: none; color: white; font-size: 30px; width: 50px; height: 50px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s ease, transform 0.2s ease; backdrop-filter: blur(10px); `;
navPrev.addEventListener('mouseenter', () => { navPrev.style.background = 'rgba(255, 255, 255, 0.2)'; navPrev.style.transform = 'translateY(-50%) scale(1.1)'; }); navPrev.addEventListener('mouseleave', () => { navPrev.style.background = 'rgba(255, 255, 255, 0.1)'; navPrev.style.transform = 'translateY(-50%) scale(1)'; }); navPrev.addEventListener('click', (e) => { e.stopPropagation(); this.prev(); });
navNext.addEventListener('mouseenter', () => { navNext.style.background = 'rgba(255, 255, 255, 0.2)'; navNext.style.transform = 'translateY(-50%) scale(1.1)'; }); navNext.addEventListener('mouseleave', () => { navNext.style.background = 'rgba(255, 255, 255, 0.1)'; navNext.style.transform = 'translateY(-50%) scale(1)'; }); navNext.addEventListener('click', (e) => { e.stopPropagation(); this.next(); });
// Добавляем кнопки навигации container.appendChild(navPrev); container.appendChild(navNext); ```
Особенности этого решения:
1. ✅ Только для изображений в статьях (фильтрует по .article-image__img) 2. ✅ Модальное окно с полупрозрачным фоном (backdrop-filter blur) 3. ✅ Без внешних библиотек - чистый JavaScript/CSS 4. ✅ Плавные анимации открытия/закрытия 5. ✅ Закрытие по клику на фон, ESC, кнопке закрытия 6. ✅ Навигация стрелками (опционально) 7. ✅ Адаптивный дизайн - работает на мобильных
Чтобы проверить:
1. Обновите render-image.html 2. Добавьте код в scripts.html 3. Перезапустите Hugo: hugo server --disableFastRender 4. В Markdown используйте обычный синтаксис: 
Теперь при клике на изображение в статье будет открываться красивое модальное окно с полупрозрачным фоном, а не просто открываться в браузере!
|
Понял! Нужно:
1. Увеличить прозрачность фона 2. Исключить изображения на главной странице и в других местах 3. Не добавлять тень к маленьким изображениям, которые полностью помещаются 4. Исключить логотипы, иконки и другие декоративные изображения
Вот исправленная версия:
1. Обновите render-image.html:
```html {{- $alt := .PlainText | safeHTML -}} {{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}} {{- $dest := .Destination -}}
{{- $isLocal := not (urls.Parse $dest).Scheme -}} {{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} {{- $startsWithSlash := hasPrefix $dest "/" -}} {{- $startsWithRelative := hasPrefix $dest "../" -}}
{{- if and $dest $isLocal -}} {{- if $startsWithSlash -}} {{/* Images under static directory */}} {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} {{- else if and $isPage (not $startsWithRelative) -}} {{/* Images that are sibling to the individual page file */}} {{ $dest = (printf "../%s" $dest) }} {{- end -}} {{- end -}}
{{/* Определяем, нужно ли добавлять lightbox */}} {{- $addLightbox := true -}} {{- $addShadow := true -}}
{{/* Исключаем определенные страницы */}} {{- if eq .Page.Kind "home" -}} {{- $addLightbox = false -}} {{- $addShadow = false -}} {{- else if eq .Page.Kind "section" -}} {{- $addLightbox = false -}} {{- $addShadow = false -}} {{- end -}}
{{/* Определяем классы */}} {{- $imageClass := "content-image" -}} {{- if $addLightbox -}} {{- $imageClass = printf "%s %s" $imageClass "content-image--zoomable" -}} {{- end -}} {{- if $addShadow -}} {{- $imageClass = printf "%s %s" $imageClass "content-image--shadow" -}} {{- end -}}
{{- with .Title -}} <figure class="content-image__wrapper"> <img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="{{ $imageClass }}" data-lightbox="true" data-original-src="{{ $dest | safeURL }}" /> <figcaption class="content-image__caption">{{ . }}</figcaption> </figure> {{- else -}} <img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="{{ $imageClass }}" data-lightbox="{{ $addLightbox }}" data-original-src="{{ $dest | safeURL }}" /> {{- end -}} ```
2. Обновите JavaScript в scripts.html:
```html <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.init(); } init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.setup()); } else { this.setup(); } } createModal() { // Основной контейнер с БОЛЬШЕЙ прозрачностью this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); /* Увеличил прозрачность */ display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `; // Контейнер для изображения const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `; // Кнопка закрытия const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.innerHTML = '×'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; background: rgba(255, 255, 255, 0.15); border: none; color: white; font-size: 32px; cursor: pointer; line-height: 1; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 10; transition: all 0.2s ease; backdrop-filter: blur(5px); `; closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.25)'; closeBtn.style.transform = 'scale(1.1)'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; closeBtn.style.transform = 'scale(1)'; }); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); }); // Изображение this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 6px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `; // Подпись this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border-radius: 4px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `; // Собираем структуру container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal); // Закрытие по клику на фон this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } }); // Закрытие по ESC document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } }); // Навигация стрелками document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); } setup() { // Находим только изображения с data-lightbox="true" (только из контента статей) this.images = Array.from(document.querySelectorAll('img[data-lightbox="true"]')) .filter(img => { // Фильтруем по размеру: только достаточно большие изображения const minSize = 200; // минимальный размер в пикселях const isLargeEnough = img.naturalWidth > minSize && img.naturalHeight > minSize; // Проверяем, что изображение видимо и не в шапке/футере const isVisible = img.offsetWidth > 0 && img.offsetHeight > 0; const isInContent = this.isInContentArea(img); // Проверяем, не является ли изображение иконкой/логотипом const isNotIcon = !this.isIcon(img); return isLargeEnough && isVisible && isInContent && isNotIcon; }); console.log(`Найдено ${this.images.length} изображений для lightbox`); this.images.forEach((img, index) => { // Добавляем курсор и эффекты только zoomable изображениям if (img.classList.contains('content-image--zoomable')) { img.style.cursor = 'zoom-in'; img.style.transition = 'transform 0.25s ease, opacity 0.25s ease'; // Эффект при наведении img.addEventListener('mouseenter', () => { img.style.opacity = '0.92'; if (!this.isSmallImage(img)) { img.style.transform = 'translateY(-3px)'; } }); img.addEventListener('mouseleave', () => { img.style.opacity = '1'; img.style.transform = 'translateY(0)'; }); // Обработчик клика img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); }); } }); } isInContentArea(element) { // Проверяем, что элемент находится в основном контенте статьи const contentSelectors = [ 'main .content', 'article .content', '.content', 'main article', 'article', '[role="main"]' ]; for (const selector of contentSelectors) { const contentArea = document.querySelector(selector); if (contentArea && contentArea.contains(element)) { return true; } } return false; } isIcon(img) { // Проверяем, является ли изображение иконкой/логотипом const iconKeywords = ['icon', 'logo', 'avatar', 'favicon', 'sprite']; const src = img.src.toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); // Проверяем по размеру (иконки обычно маленькие) const isSmall = img.naturalWidth < 100 && img.naturalHeight < 100; // Проверяем по имени файла, alt тексту и классам const hasIconKeyword = iconKeywords.some(keyword => src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword) ); return isSmall || hasIconKeyword; } isSmallImage(img) { // Проверяем, маленькое ли изображение (полностью помещается без тени) const viewportWidth = window.innerWidth; const imageWidth = img.naturalWidth || img.width; // Если ширина изображения меньше 80% ширины контейнера const container = img.closest('.content, main, article') || document.body; const containerWidth = container.clientWidth; return imageWidth < containerWidth * 0.8; } open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; // Показываем модальное окно this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible'; // Анимация появления requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); }); // Блокируем прокрутку document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; } close() { // Анимация закрытия this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)'; setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); // Разблокируем прокрутку document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); } isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; } next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } } prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Инициализация с задержкой для загрузки изображений document.addEventListener('DOMContentLoaded', () => { // Ждем немного для полной загрузки изображений setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 300); // Добавляем CSS стили const style = document.createElement('style'); style.textContent = ` /* Базовые стили для всех изображений в контенте */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 4px; } /* Стили только для zoomable изображений (в статьях) */ .content-image--zoomable { cursor: zoom-in; transition: all 0.25s ease; } /* Тень только для больших изображений */ .content-image--shadow { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } /* Маленькие изображения (без тени) */ .content-image:not(.content-image--shadow) { box-shadow: none; } /* Эффект при наведении на большие изображения */ .content-image--zoomable.content-image--shadow:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.95; } /* Подпись к изображениям */ .content-image__caption { text-align: center; font-size: 0.875rem; color: #666; margin-top: 0.5rem; line-height: 1.4; font-style: italic; } /* Обертка для изображений с подписью */ .content-image__wrapper { margin: 1.5rem 0; text-align: center; } /* Анимации для lightbox */ @keyframes lightboxFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes lightboxImagePop { 0% { transform: scale(0.95); opacity: 0; } 70% { transform: scale(1.02); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } /* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 3px; } .content-image--shadow { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .content-image--zoomable.content-image--shadow:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .content-lightbox__close { top: 15px; right: 15px; width: 32px; height: 32px; font-size: 28px; } } /* Отключение hover на тач-устройствах */ @media (hover: none) { .content-image--zoomable:hover { transform: none !important; } } `; document.head.appendChild(style); });
// Определяем размеры изображений после загрузки window.addEventListener('load', () => { const images = document.querySelectorAll('.content-image'); images.forEach(img => { if (img.complete) { window.contentLightbox?.setup(); } else { img.addEventListener('load', () => { setTimeout(() => window.contentLightbox?.setup(), 100); }); } }); }); </script> ```
Что делает этот код:
1. Фильтрация изображений:
· ❌ Исключает главную страницу (home) · ❌ Исключает разделы (section pages) · ❌ Исключает маленькие изображения (< 200px) · ❌ Исключает иконки/логотипы (по названию и размеру) · ✅ Только для контента статей (проверяет область)
2. Управление тенями:
· ✅ Тень только для больших изображений (.content-image--shadow) · ❌ Маленькие изображения без тени · ✅ Определение "маленьких" - ширина < 80% контейнера
3. Прозрачность:
· ✅ Фон lightbox: rgba(0, 0, 0, 0.85) (более прозрачный) · ✅ Плавные анимации с cubic-bezier
4. Умная логика:
· Автоматически определяет какие изображения нужно увеличивать · Не трогает декоративные элементы · Работает только в контенте статей · Адаптивный дизайн для мобильных
Чтобы настроить:
1. Изменить прозрачность - настройте rgba(0, 0, 0, 0.85) в createModal() 2. Изменить минимальный размер - поменяйте minSize = 200 в setup() 3. Добавить исключения - в isIcon() добавьте ключевые слова
Теперь у вас будет красивый lightbox только для изображений в статьях, с увеличенной прозрачностью и умной фильтрацией!
|
Отлично! Давайте исправим эти две проблемы:
1. Вернем закругленные углы изображениям 2. Уберем курсор-лупу с изображений, которые не увеличиваются
Обновите render-image.html:
```html {{- $alt := .PlainText | safeHTML -}} {{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}} {{- $dest := .Destination -}}
{{- $isLocal := not (urls.Parse $dest).Scheme -}} {{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} {{- $startsWithSlash := hasPrefix $dest "/" -}} {{- $startsWithRelative := hasPrefix $dest "../" -}}
{{- if and $dest $isLocal -}} {{- if $startsWithSlash -}} {{/* Images under static directory */}} {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} {{- else if and $isPage (not $startsWithRelative) -}} {{/* Images that are sibling to the individual page file */}} {{ $dest = (printf "../%s" $dest) }} {{- end -}} {{- end -}}
{{/* Определяем, нужно ли добавлять lightbox */}} {{- $addLightbox := true -}} {{- $addShadow := true -}}
{{/* Исключаем определенные страницы */}} {{- if eq .Page.Kind "home" -}} {{- $addLightbox = false -}} {{- $addShadow = false -}} {{- else if eq .Page.Kind "section" -}} {{- $addLightbox = false -}} {{- $addShadow = false -}} {{- end -}}
{{/* Определяем классы */}} {{- $imageClass := "content-image" -}} {{- if $addLightbox -}} {{- $imageClass = printf "%s %s" $imageClass "content-image--zoomable" -}} {{- end -}} {{- if $addShadow -}} {{- $imageClass = printf "%s %s" $imageClass "content-image--shadow" -}} {{- end -}}
{{- with .Title -}} <figure class="content-image__wrapper"> <img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="{{ $imageClass }}" data-lightbox="{{ $addLightbox }}" data-original-src="{{ $dest | safeURL }}" /> <figcaption class="content-image__caption">{{ . }}</figcaption> </figure> {{- else -}} <img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="{{ $imageClass }}" data-lightbox="{{ $addLightbox }}" data-original-src="{{ $dest | safeURL }}" /> {{- end -}} ```
Обновите JavaScript в scripts.html:
```javascript setup() { // Находим только изображения с data-lightbox="true" (только из контента статей) this.images = Array.from(document.querySelectorAll('img[data-lightbox="true"]')) .filter(img => { // Фильтруем по размеру: только достаточно большие изображения const minSize = 200; // минимальный размер в пикселях const isLargeEnough = img.naturalWidth > minSize && img.naturalHeight > minSize; // Проверяем, что изображение видимо и не в шапке/футере const isVisible = img.offsetWidth > 0 && img.offsetHeight > 0; const isInContent = this.isInContentArea(img); // Проверяем, не является ли изображение иконкой/логотипом const isNotIcon = !this.isIcon(img); // Проверяем, полностью ли помещается изображение const isNotFullyVisible = !this.isFullyVisible(img); return isLargeEnough && isVisible && isInContent && isNotIcon && isNotFullyVisible; }); console.log(`Найдено ${this.images.length} изображений для lightbox`); // Проходим по ВСЕМ изображениям с data-lightbox="true" document.querySelectorAll('img[data-lightbox="true"]').forEach((img, index) => { // Проверяем, попадает ли это изображение в наш фильтрованный список const isZoomable = this.images.includes(img); if (isZoomable) { // Добавляем курсор-лупу и эффекты только zoomable изображениям img.style.cursor = 'zoom-in'; img.style.transition = 'transform 0.25s ease, opacity 0.25s ease'; // Добавляем класс для стилей img.classList.add('is-zoomable'); // Эффект при наведении img.addEventListener('mouseenter', () => { img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; }); img.addEventListener('mouseleave', () => { img.style.opacity = '1'; img.style.transform = 'translateY(0)'; }); // Обработчик клика img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); // Находим индекс в отфильтрованном массиве const filteredIndex = this.images.indexOf(img); if (filteredIndex !== -1) { this.currentIndex = filteredIndex; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }); } else { // Для не-zoomable изображений убираем курсор-лупу img.style.cursor = 'default'; img.classList.remove('content-image--zoomable'); img.classList.add('not-zoomable'); } }); }
// НОВЫЙ МЕТОД: Проверяет, полностью ли видно изображение isFullyVisible(img) { if (!img.parentElement) return false; // Получаем размеры изображения и его контейнера const imgWidth = img.naturalWidth || img.offsetWidth; const container = img.closest('.content, main, article, .content-image__wrapper, p') || img.parentElement; const containerWidth = container.clientWidth; // Если контейнер - это параграф, учитываем padding let containerEffectiveWidth = containerWidth; if (container.tagName === 'P') { const styles = window.getComputedStyle(container); const paddingLeft = parseFloat(styles.paddingLeft) || 0; const paddingRight = parseFloat(styles.paddingRight) || 0; containerEffectiveWidth = containerWidth - paddingLeft - paddingRight; } // Изображение считается "полностью видимым", если его ширина меньше 95% ширины контейнера // и при этом оно не больше 800px (чтобы большие изображения все равно можно было увеличить) const isWidthFullyVisible = imgWidth < containerEffectiveWidth * 0.95; const isNotTooLarge = imgWidth <= 800; // Максимальная ширина для "полной видимости" // Также проверяем высоту const imgHeight = img.naturalHeight || img.offsetHeight; const viewportHeight = window.innerHeight; const isHeightFullyVisible = imgHeight < viewportHeight * 0.8; return isWidthFullyVisible && isNotTooLarge && isHeightFullyVisible; }
// Обновите метод isSmallImage isSmallImage(img) { return this.isFullyVisible(img); } ```
Обновите CSS стили в том же scripts.html:
```css /* Базовые стили для всех изображений в контенте */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 8px; /* Закругленные углы */ }
/* Стили для zoomable изображений (которые можно увеличивать) */ .content-image.is-zoomable { cursor: zoom-in; transition: all 0.25s ease; }
/* Для не-zoomable изображений (которые полностью видны) */ .content-image.not-zoomable { cursor: default; border-radius: 8px; /* Закругленные углы сохраняются */ }
/* Стили только для zoomable изображений (в статьях) */ .content-image--zoomable.is-zoomable { cursor: zoom-in; }
/* Тень только для больших изображений */ .content-image--shadow.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); }
/* Маленькие/полностью видимые изображения (без тени) */ .content-image:not(.is-zoomable), .content-image.not-zoomable { box-shadow: none !important; }
/* Эффект при наведении на zoomable изображения */ .content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.95; }
/* Отключаем hover эффекты для не-zoomable изображений */ .content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* Подпись к изображениям */ .content-image__caption { text-align: center; font-size: 0.875rem; color: #666; margin-top: 0.5rem; line-height: 1.4; font-style: italic; }
/* Обертка для изображений с подписью */ .content-image__wrapper { margin: 1.5rem 0; text-align: center; }
/* Анимации для lightbox */ @keyframes lightboxFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes lightboxImagePop { 0% { transform: scale(0.95); opacity: 0; } 70% { transform: scale(1.02); opacity: 1; } 100% { transform: scale(1); opacity: 1; } }
/* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 6px; /* Немного меньше на мобильных */ } .content-image--shadow.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .content-lightbox__close { top: 15px; right: 15px; width: 32px; height: 32px; font-size: 28px; } /* На мобильных делаем все изображения не-zoomable если они маленькие */ .content-image { max-width: 100% !important; } }
/* Отключение hover на тач-устройствах */ @media (hover: none) { .content-image:hover { transform: none !important; } }
/* Плавная загрузка изображений */ .content-image { opacity: 0; animation: imageFadeIn 0.5s ease forwards; }
@keyframes imageFadeIn { to { opacity: 1; } } ```
Дополнительно: Если нужно сохранить старые закругления
Если у вас уже были свои стили для закругления, добавьте в конец CSS:
```css /* Сохраняем существующие стили закругления */ img.content-image { border-radius: var(--image-border-radius, 8px) !important; }
/* Переопределяем для разных случаев */ .content-image.is-zoomable { border-radius: calc(var(--image-border-radius, 8px) + 2px) !important; } ```
Что изменилось:
✅ Вернули закругленные углы:
· Все изображения теперь имеют border-radius: 8px · На мобильных немного меньше: 6px
✅ Убрали лупу с полностью видимых изображений:
· Логика определения: изображение считается "полностью видимым", если: 1. Его ширина < 95% ширины контейнера 2. Высота < 80% высоты окна 3. Ширина ≤ 800px (чтобы большие можно было увеличить) · Курсор: у таких изображений cursor: default вместо zoom-in · Эффекты hover: отключены для не-zoomable изображений
✅ Разделили логику:
· isZoomable - изображения, которые можно увеличивать · not-zoomable - полностью видимые изображения · Разные CSS классы для разных типов
Как проверить:
1. Обновите оба файла 2. Перезапустите Hugo: hugo server --disableFastRender 3. Проверьте: · У всех изображений закругленные углы · У маленьких/полностью видимых изображений нет курсора-лупы · Большие изображения все еще можно увеличивать
Теперь изображения будут выглядеть как раньше с закругленными углами, а лупа будет показываться только там, где действительно нужно увеличение!
|
Понял! Вот полный готовый код для вставки.
1. layouts/_default/_markup/render-image.html (полностью заменить):
```html {{- $alt := .PlainText | safeHTML -}} {{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}} {{- $dest := .Destination -}}
{{- $isLocal := not (urls.Parse $dest).Scheme -}} {{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}} {{- $startsWithSlash := hasPrefix $dest "/" -}} {{- $startsWithRelative := hasPrefix $dest "../" -}}
{{- if and $dest $isLocal -}} {{- if $startsWithSlash -}} {{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}} {{- else if and $isPage (not $startsWithRelative) -}} {{ $dest = (printf "../%s" $dest) }} {{- end -}} {{- end -}}
{{- $addLightbox := true -}} {{- $addShadow := true -}}
{{- if eq .Page.Kind "home" -}} {{- $addLightbox = false -}} {{- $addShadow = false -}} {{- else if eq .Page.Kind "section" -}} {{- $addLightbox = false -}} {{- $addShadow = false -}} {{- end -}}
{{- $imageClass := "content-image" -}} {{- if $addLightbox -}} {{- $imageClass = printf "%s %s" $imageClass "content-image--zoomable" -}} {{- end -}} {{- if $addShadow -}} {{- $imageClass = printf "%s %s" $imageClass "content-image--shadow" -}} {{- end -}}
{{- with .Title -}} <figure class="content-image__wrapper"> <img src="{{ $dest | safeURL }}" title="{{ . }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="{{ $imageClass }}" data-lightbox="{{ $addLightbox }}" data-original-src="{{ $dest | safeURL }}" /> <figcaption class="content-image__caption">{{ . }}</figcaption> </figure> {{- else -}} <img src="{{ $dest | safeURL }}" alt="{{ $alt }}" {{ if $lazyLoading }}loading="lazy"{{ end }} class="{{ $imageClass }}" data-lightbox="{{ $addLightbox }}" data-original-src="{{ $dest | safeURL }}" /> {{- end -}} ```
2. В layouts/partials/scripts.html добавить ПЕРЕД закрывающим </body> (или в конец файла):
```html <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.init(); } init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.setup()); } else { this.setup(); } } createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `; const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `; const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.innerHTML = '×'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; background: rgba(255, 255, 255, 0.15); border: none; color: white; font-size: 32px; cursor: pointer; line-height: 1; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 10; transition: all 0.2s ease; backdrop-filter: blur(5px); `; closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.25)'; closeBtn.style.transform = 'scale(1.1)'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; closeBtn.style.transform = 'scale(1)'; }); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); }); this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 6px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `; this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border-radius: 4px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `; container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal); this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } }); document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); } setup() { this.images = Array.from(document.querySelectorAll('img[data-lightbox="true"]')) .filter(img => { const minSize = 200; const isLargeEnough = img.naturalWidth > minSize && img.naturalHeight > minSize; const isVisible = img.offsetWidth > 0 && img.offsetHeight > 0; const isInContent = this.isInContentArea(img); const isNotIcon = !this.isIcon(img); const isNotFullyVisible = !this.isFullyVisible(img); return isLargeEnough && isVisible && isInContent && isNotIcon && isNotFullyVisible; }); document.querySelectorAll('img[data-lightbox="true"]').forEach((img) => { const isZoomable = this.images.includes(img); if (isZoomable) { img.style.cursor = 'zoom-in'; img.style.transition = 'transform 0.25s ease, opacity 0.25s ease'; img.classList.add('is-zoomable'); img.addEventListener('mouseenter', () => { img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; }); img.addEventListener('mouseleave', () => { img.style.opacity = '1'; img.style.transform = 'translateY(0)'; }); img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const filteredIndex = this.images.indexOf(img); if (filteredIndex !== -1) { this.currentIndex = filteredIndex; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }); } else { img.style.cursor = 'default'; img.classList.remove('content-image--zoomable'); img.classList.add('not-zoomable'); } }); } isInContentArea(element) { const contentSelectors = [ 'main .content', 'article .content', '.content', 'main article', 'article', '[role="main"]' ]; for (const selector of contentSelectors) { const contentArea = document.querySelector(selector); if (contentArea && contentArea.contains(element)) { return true; } } return false; } isIcon(img) { const iconKeywords = ['icon', 'logo', 'avatar', 'favicon', 'sprite']; const src = img.src.toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isSmall = img.naturalWidth < 100 && img.naturalHeight < 100; const hasIconKeyword = iconKeywords.some(keyword => src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword) ); return isSmall || hasIconKeyword; } isFullyVisible(img) { if (!img.parentElement) return false; const imgWidth = img.naturalWidth || img.offsetWidth; const container = img.closest('.content, main, article, .content-image__wrapper, p') || img.parentElement; const containerWidth = container.clientWidth; let containerEffectiveWidth = containerWidth; if (container.tagName === 'P') { const styles = window.getComputedStyle(container); const paddingLeft = parseFloat(styles.paddingLeft) || 0; const paddingRight = parseFloat(styles.paddingRight) || 0; containerEffectiveWidth = containerWidth - paddingLeft - paddingRight; } const isWidthFullyVisible = imgWidth < containerEffectiveWidth * 0.95; const isNotTooLarge = imgWidth <= 800; const imgHeight = img.naturalHeight || img.offsetHeight; const viewportHeight = window.innerHeight; const isHeightFullyVisible = imgHeight < viewportHeight * 0.8; return isWidthFullyVisible && isNotTooLarge && isHeightFullyVisible; } open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible'; requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); }); document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; } close() { this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)'; setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); } isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; } next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } } prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 300); const style = document.createElement('style'); style.textContent = ` .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 8px; } .content-image.is-zoomable { cursor: zoom-in; transition: all 0.25s ease; } .content-image.not-zoomable { cursor: default; border-radius: 8px; } .content-image--zoomable.is-zoomable { cursor: zoom-in; } .content-image--shadow.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .content-image:not(.is-zoomable), .content-image.not-zoomable { box-shadow: none !important; } .content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.95; } .content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; } .content-image__caption { text-align: center; font-size: 0.875rem; color: #666; margin-top: 0.5rem; line-height: 1.4; font-style: italic; } .content-image__wrapper { margin: 1.5rem 0; text-align: center; } @keyframes lightboxFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes lightboxImagePop { 0% { transform: scale(0.95); opacity: 0; } 70% { transform: scale(1.02); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 6px; } .content-image--shadow.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .content-lightbox__close { top: 15px; right: 15px; width: 32px; height: 32px; font-size: 28px; } } @media (hover: none) { .content-image:hover { transform: none !important; } } .content-image { opacity: 0; animation: imageFadeIn 0.5s ease forwards; } @keyframes imageFadeIn { to { opacity: 1; } } `; document.head.appendChild(style); });
window.addEventListener('load', () => { const images = document.querySelectorAll('.content-image'); images.forEach(img => { if (img.complete) { window.contentLightbox?.setup(); } else { img.addEventListener('load', () => { setTimeout(() => window.contentLightbox?.setup(), 100); }); } }); }); </script> ```
Как установить:
1. Заменить полностью файл layouts/_default/_markup/render-image.html 2. Добавить JavaScript код в layouts/partials/scripts.html (перед </body> или в конец) 3. Перезапустить Hugo:
```bash hugo server --disableFastRender ```
Что делает:
✅ Lightbox с полупрозрачным фоном (85% прозрачности) ✅ Только для изображений в статьях (не на главной) ✅ Закругленные углы у всех изображений (8px) ✅ Лупа только там, где нужно - убирает курсор с полностью видимых изображений ✅ Плавные анимации открытия/закрытия ✅ Навигация стрелками ← → и ESC для закрытия ✅ Адаптивный дизайн для мобильных
Теперь изображения будут с закругленными углами, а лупа появится только на тех, которые действительно нужно увеличивать!
|