PageSourceSearch

https://www.met.ru/local/templates/met/js/blocks/lightbox/lightbox.js

js met.ru collected 2026-10-02 06:40:08 UTC 9,898 bytes, 322 lines download raw bytes

1class EventEmitter {
2    constructor() {
3        this.listeners = {};
4    }
5
6    /**
7     * Слушать событие
8     * @param {String} eventName Название события
9     * @param {Function} fn Callback
10     */
11    on(eventName, fn = null) {
12        if (!fn) {
13            throw new Error('EventEmitter: callback is not provided')
14        }
15
16        this.listeners[eventName] = this.listeners[eventName] || [];
17        this.listeners[eventName].push({
18            fn
19        });
20    }
21
22    /**
23     * Слушать событие один раз
24     * @param {String} eventName Название события
25     * @param {Function} fn Callback
26     */
27    once(eventName, fn = null) {
28        if (!fn) {
29            throw new Error('EventEmitter: callback is not provided')
30        }
31
32        this.listeners[eventName] = this.listeners[eventName] || [];
33        this.listeners[eventName].push({
34            fn,
35            once: true
36        });
37    }
38
39    /**
40     * Вызвать событие. После вызова будут запущены все обработчики этого события с переданными параметрами
41     * @param {String} eventName Название события
42     * @param  {...any} data Любые параметры
43     */
44    emit(eventName, ...data) {
45        if (this.listeners[eventName]) {
46            this.listeners[eventName].forEach(listener => {
47                listener.fn(...data);
48
49                if (listener.once) {
50                    this.off(eventName, listener.fn);
51                }
52            });
53        }
54    }
55
56    /**
57     * Убрать все обработчики с события
58     * @param {String} eventName Название события
59     * @param {Function} fn Callback
60     */
61    off(eventName, fn = null) {
62        if (fn) {
63            this.listeners[eventName] = this.listeners[eventName]
64                .filter(listener => listener.fn !== fn);
65        } else {
66            this.listeners[eventName] = [];
67        }
68    }
69}
70
71const elementParamKey = Symbol();
72const initKey = Symbol();
73
74/**
75 * Get component instance from dom-element
76 * @param {Element} element
77 * @returns {Component}
78 */
79function getComponent(element) {
80    return element[elementParamKey];
81}
82
83window.getComponent = getComponent
84
85class Component extends EventEmitter {
86    static defaultParams = {
87        componentAttribute: 'data-component',
88        autobindExclude: [
89            'constructor'
90        ]
91    }
92
93    /**
94     * Для создания компонента используется этот метод
95     * @param {Element} el Корневой dom-элемент
96     * @param {Object} params Параметры
97     * @returns {Component}
98     */
99    static create(el, params) {
100        const component = new this(el, params);
101        component[initKey]();
102
103        if (component.init) {
104            component.init(el, params);
105        }
106
107        return component;
108    }
109
110    /**
111     * Ссылка на getComponent
112     */
113    static getComponent(element) {
114        return getComponent(element);
115    }
116
117    /**
118     * @param {Element} el
119     * @param {Object} params
120     */
121    constructor(el, params) {
122        super();
123
124        this.root = el;
125        this.rootEl = this.root;
126        this.userParams = params;
127
128        this.params = this._initParams(this.userParams);
129        this.namespace = this.root.getAttribute(this.params.componentAttribute);
130        this.root[elementParamKey] = this;
131        this._bindMethods();
132    }
133
134    /**
135     * Соединение всех параметров
136     * @param {Object} params
137     * @returns
138     */
139    _initParams(params) {
140        return {
141            ...Component.defaultParams,
142            ...this.defaultParams,
143            ...this._getParamsFromElemenet(this.root),
144            ...params
145        }
146    }
147
148    /**
149     * Достать параметры из json в разметке
150     * @param {Element} element
151     * @returns {Object}
152     */
153    _getParamsFromElemenet(element) {
154        try {
155            return JSON.parse(element.getAttribute('data-component-params'));
156        } catch (e) {
157            throw new Error('Invalid component params json')
158        }
159    }
160
161    /**
162     * Автоматическая привязка контекста к методам компонента
163     * Метод не привязывается, если он указан в массиве autobindExclude в параметрах компонента
164     */
165    _bindMethods() {
166        Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEac
166h((property) => {
167            if (
168                typeof this[property] == 'function'
169                && property !== 'constructor'
170                && !this.params.autobindExclude.includes(property)
171            ) {
172                this[property] = this[property].bind(this);
173            }
174        });
175    }
176
177    /**
178     * Поиск элемента в контексте компонента или в заданном контексте
179     * @param {String} name Название элемента
180     * @param {Element} context
181     * @returns {Element}
182     */
183    $(name, context) {
184        context = context || this.root;
185        return context.querySelector(this.getSelector(name, true));
186    }
187
188    /**
189     * Поиск элементов в контексте компонента или в заданном контексте
190     * @param {String} name Название элемента
191     * @param {Element} context
192     * @returns {Array}
193     */
194    $$(name, context) {
195        context = context || this.root;
196
197        return [...context.querySelectorAll(this.getSelector(name, true))];
198    }
199
200    /**
201     * Получить селектор с учетом контекста компонента
202     * @param {String} name Название элемента
203     * @param {Boolean} brackets Вовзращать ли селектор с квадратными скобками
204     * @returns {String}
205     */
206    getSelector(name, brackets = false) {
207        return name
208            ? `${brackets ? '[' : ''}data-${this.namespace}-${name}${brackets ? ']' : ''}`
209            : `${brackets ? '[' : ''}data-${this.namespace}${brackets ? ']' : ''}`;
210    }
211}
212
213class Lightbox extends Component {
214    constructor(el, params) {
215        super(el, params);
216        this.init();
217    }
218    init() {
219        this.itemEls = this.$$('item');
220        this.openBtns = this.$$('open');
221
222        this.itemEls.forEach(el => {
223            el.addEventListener('click', (e) => this._onClick(e, 'item'));
224        });
225
226        this.openBtns.forEach(el => {
227            el.addEventListener('click', (e) => this._onClick(e, 'open'));
228        });
229    }
230
231    async _onClick(e, selectorType) {
232        e.preventDefault();
233
234        const target = e.target;
235
236        if (!this.lightbox) {
237            this._initLightbox();
238        }
239
240        const slideIndex = target.getAttribute(this.getSelector(selectorType));
241        this.lightbox.open(parseInt(slideIndex));
242    }
243
244    _initLightbox() {
245        this.lightbox = new FsLightbox();
246        this.lightbox.props.sources = this._getSources();
247
248        this.lightbox.props.onOpen = () => {
249            this.isOpened = true;
250            this._initOnSlideChange();
251        }
252        this.lightbox.props.onClose = () => {
253            this.isOpened = false;
254        }
255        document.addEventListener('keydown', (e) => {
256            e = e || window.event;
257            if (e.keyCode == '37' || e.keyCode == '39') {
258                this._onSlideChange();
259            }
260        });
261    }
262
263    _initOnSlideChange() {
264        if (!this.observer) {
265            this.observer = new MutationObserver((mutationList) => {
266                for (const mutation of mutationList) {
267                    if (mutation.type === "attributes") {
268                        if (
269                            this.lightbox.elements.container.getAttribute('class') === 'fslightbox-container fslightbox-full-dimension fslightbox-fade-in-strong'
270                            && this.isOpened
271                        ) {
272                            this._onSlideChange();
273                        }
274                    }
275                }
276            });
277
278            this.observer.observe(this.lightbox.elements.container, { attributes: true });
279
280            ['.fslightbox-slide-btn-container-previous', '.fslightbox-slide-btn-container-next'].forEach(cls => {
281                this.lightbox.elements.container.querySelector(cls).addEventListener('click', () => {
282                    this._onSlideChange();
283                });
284            });
285        }
286    }
287
288    _onSlideChange() {
289        [...this.lightbox.elements.container.querySelectorAll('video, iframe')].forEac
289h(el => {
290            if (el.tagName.toLowerCase() === 'video') {
291                el.pause();
292            } else if (el.tagName.toLowerCase() === 'iframe') {
293                // пауза видео для youtube
294                el.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}', '*');
295
296                // пауза видео для rutube
297                el.contentWindow.postMessage(JSON.stringify({
298                    type: 'player:pause',
299                    data: {}
300                }), '*');
301
302                // пауза видео для vimeo
303                el.contentWindow.postMessage('{"method":"pause"}', '*');
304
305                // пауза видео для vk
306                el.setAttribute('src', el.getAttribute('src'));
307            }
308        });
309    }
310
311    _getSources() {
312        return this.itemEls.map(el => {
313            let src = el.getAttribute(this.getSelector('src'));
314
315            if(src[0] === '#') {
316                src = document.querySelector(src);
317            }
318
319            return src;
320        });
321    }
322}

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.