PageSourceSearch

https://predanie.ru/js/image-lightbox.js?v=v5.3.147

js predanie.ru collected 2026-10-01 10:24:29 UTC 6,489 bytes, 160 lines download raw bytes

1/**
2 * image-lightbox.js
3 * Лёгкий lightbox без зависимостей для изображений в richtext.
4 * Поведение:
5 * - клик по картинке в .fond-richtext открывает оверлей
6 * - повторный клик по картинке в оверлее переключает зум
7 * - ESC или клик по фону закрывает
8 */
9(function () {
10  'use strict';
11
12  function ensureLightbox() {
13    var existing = document.getElementById('imageLightbox');
14    if (existing) return existing;
15
16    var el = document.createElement('div');
17    el.id = 'imageLightbox';
18    el.className = 'image-lightbox';
19    el.innerHTML =
20      '<div class="image-lightbox__backdrop" data-action="close"></div>' +
21      '<div class="image-lightbox__dialog" role="dialog" aria-modal="true">' +
22      '  <button class="image-lightbox__close" type="button" aria-label="Закрыть" data-action="close">×</button>' +
23      '  <div class="image-lightbox__figure">' +
24      '    <img class="image-lightbox__img" alt="" />' +
25      '    <div class="image-lightbox__caption" hidden></div>' +
26      '  </div>' +
27      '</div>';
28
29    document.body.appendChild(el);
30
31    // Встраиваем стили, чтобы не плодить отдельный CSS-файл
32    var style = document.createElement('style');
33    style.textContent =
34      '.image-lightbox{position:fixed;inset:0;z-index:30000;display:none}' +
35      '.image-lightbox.is-open{display:block}' +
36      '.image-lightbox__backdrop{position:absolute;inset:0;background:rgba(0,0,0,.72)}' +
37      '.image-lightbox__dialog{position:relative;max-width:min(1100px,calc(100% - 32px));max-height:calc(100% - 32px);margin:16px auto;display:flex;align-items:center;justify-content:center}' +
38      '.image-lightbox__figure{display:flex;flex-direction:column;align-items:center;gap:12px;max-width:100%;max-height:calc(100vh - 64px)}' +
39      '.image-lightbox__img{max-width:100%;max-height:calc(100vh - 64px);border-radius:12px;box-shadow:0 18px 60px rgba(0,0,0,.45);cursor:zoom-in;transform-origin:center;transition:transform .18s ease}' +
40      '.image-lightbox__img.is-zoomed{transform:scale(1.8);cursor:zoom-out}' +
41      '.image-lightbox__caption{max-width:min(840px,calc(100vw - 64px));padding:10px 14px;border-radius:14px;background:rgba(17,17,17,.42);color:rgba(255,255,255,.92);font-size:14px;line-height:1.5;text-align:center;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}' +
42      '.image-lightbox__close{position:absolute;top:10px;right:10px;width:40px;height:40px;border-radius:999px;border:1px solid rgba(255,255,255,.25);background:rgba(0,0,0,.35);color:#fff;font-size:22px;line-height:1;display:flex;align-items:center;justify-content:center}' +
43      '.image-lightbox__close:hover{background:rgba(0,0,0,.55)}';
44    document.head.appendChild(style);
45
46    // Закрытие по клику
47    el.addEventListener('click', function (e) {
48      var t = e.target;
49      if (!t) return;
50      if (t.getAttribute && t.getAttribute('data-action') === 'close') {
51        close(el);
52        return;
53      }
54
55      // Клик по пустому месту вокруг изображения (внутри диалога) тоже закрывает.
56      // При этом клик по самой картинке оставляем под зум.
57      if (t.classList && t.classList.contains('image-lightbox__img')) return;
58      if (t.closest && t.closest('.image-lightbox__dialog')) {
59        close(el);
60      }
61    });
62
63    // Зум по клику на картинке
64    var img = el.querySelector('.image-lightbox__img');
65    if (img) {
66      img.addEventListener('click', function (e) {
67        e.preventDefault();
68        img.classList.toggle('is-zoomed');
69      });
70    }
71
72    // ESC
73    document.addEventListener('keydown', function (e) {
74      if (e.key === 'Escape' && el.classList.contains('is-open')) {
75        close(el);
76      }
77    });
78
79    return el;
80  }
81
82  function open(src, alt) {
83    var lb = ensureLightbox();
84    var img = lb.querySelector('.image-lightbox__img');
85    var captionEl = lb.querySelector('.image-lightbox__caption');
86    if (!img) return;
87    img.classList.remove('is-zoomed');
88    img.src = src;
89    img.alt = alt || '';
90    if (captionEl) {
91      var caption = arguments.length > 2 ? arguments[2] : '';
92      captionEl.textContent = caption || '';
93      captionEl.hidden = !caption;
94    }
95    lb.classList.add('is-open');
96    document.body.style.overflow = 'hidden';
97  }
98
99  function close(lb) {
100    var img = lb.querySelector('.image-lightbox__img');
101    var captionEl = lb.querySelector('.image-lightbox__caption');
102    if (img) {
103      img.classList.remove('is-zoomed');
104      img.src = '';
105    }
106    if (captionEl) {
107      captionEl.textContent = '';
108      captionEl.hidden = true;
109    }
110    lb.classList.remove('is-open');
111    document.body.style.overflow = '';
112  }
113
114  function getCaption(img) {
115    if (!img) return '';
116    var explicitCaption = img.getAttribute('data-lightbox-caption');
117    if (explicitCaption) return explicitCaption.trim();
118
119    var figure = img.closest ? img.closest('figure') : null;
120    if (figure) {
121      var figcaption = figure.querySelector('figcaption');
122      if (figcaption && figcaption.textContent) {
123        return figcaption.textContent.trim();
124      }
125    }
126
127    var title = img.getAttribute('title');
128    if (title) return title.trim();
129
130    var alt = img.getAttribute('alt');
131    return alt ? alt.trim() : '';
132  }
133
134  function shouldHandle(img) {
135    if (!img) return false;
136    if (!img.src) return false;
137    // если это уже картинка внутри lightbox
138    if (img.closest && img.closest('#imageLightbox')) return false;
139    // либо изображения в richtext, либо явная пометка
140    if (img.classList && img.classList.contains('js-lightbox-img')) return true;
141    if (!(img.closest && (img.closest('.fond-richtext') || img.closest('.reading-prose')))) return false;
142    return true;
143  }
144
145  function init() {
146    document.addEventListener('click', function (e) {
147      var target = e.target;
148      if (!(target && target.tagName === 'IMG')) return;
149      if (!shouldHandle(target)) return;
150      e.preventDefault();
151      open(target.src, target.alt, getCaption(target));
152    });
153  }
154
155  if (document.readyState === 'loading') {
156    document.addEventListener('DOMContentLoaded', init);
157  } else {
158    init();
159  }
160})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.