diff --git a/packages/lightbox-gallery/README.md b/packages/lightbox-gallery/README.md new file mode 100644 index 0000000..a317149 --- /dev/null +++ b/packages/lightbox-gallery/README.md @@ -0,0 +1,98 @@ +# lightbox-gallery + +## Требования + +- [Swiper](https://github.com/nolimits4web/swiper) версии ^11.0.0 + +## Подключение и настройка + +### HTML + +```html +
+ ... + ... + ... +
+``` + +### Стили + +```scss +@use '@advdominion/lightbox-gallery/styles.css'; +``` + +### Минимальные настройки + +```js +import { gallery } from '@advdominion/lightbox-gallery'; + +gallery(document.querySelector('#list'), { + icons: { + close: `/icons.svg#close`, + }, +}); +``` + +### Все настройки + +```js +import { gallery } from '@advdominion/lightbox-gallery'; + +gallery(document.querySelector('#list'), { + single: false, + swiper: { + speed: 300, + pagination: { + el: '.swiper-pagination', + }, + navigation: { + prevEl: '.swiper-button-prev', + nextEl: '.swiper-button-next', + }, + }, + pagination: ` +
+ `, + navigation: ` +
+
+ `, + animation: { + show: { + duration: 500, + easing: 'linear', + }, + hide: { + duration: 500, + easing: 'linear', + }, + }, + icons: { + close: `/icons.svg#close`, + }, +}); +``` + +### Одиночный элемент + +```js +import { gallery } from '@advdominion/lightbox-gallery'; + +gallery(document.querySelector('#list > a:first-child'), { + single: true, + animation: { + show: { + duration: 500, + easing: 'linear', + }, + hide: { + duration: 500, + easing: 'linear', + }, + }, + icons: { + close: `/icons.svg#close`, + }, +}); +``` diff --git a/packages/lightbox-gallery/index.js b/packages/lightbox-gallery/index.js new file mode 100644 index 0000000..1755b56 --- /dev/null +++ b/packages/lightbox-gallery/index.js @@ -0,0 +1,424 @@ +/* globals GalleryYTPlayer, YT, GalleryVKPlayer, VK */ + +import { getScrollbarWidth } from '@advdominion/get-scrollbar-width'; + +const getYTPlayer = () => { + const script = document.createElement('script'); + script.src = 'https://www.youtube.com/player_api'; + script.async = true; + document.body.append(script); + }, + getVKPlayer = () => { + const script = document.createElement('script'); + script.src = 'https://vk.com/js/api/videoplayer.js'; + script.async = true; + document.body.append(script); + }, + playVideo = (slide) => { + const youtube = slide.querySelector('.advdominion-lg__video_youtube'), + vk = slide.querySelector('.advdominion-lg__video_vk'), + rt = slide.querySelector('.advdominion-lg__video_rt'); + + if (youtube) { + if (slide.YTPlayer) { + if (slide.classList.contains('swiper-slide-active')) { + slide.YTPlayer.playVideo(); + } + } else if (window.YT) { + if (GalleryYTPlayer.state) { + slide.YTPlayer = new YT.Player(youtube, { + videoId: youtube.dataset.id, + events: { + onReady(event) { + if (slide.classList.contains('swiper-slide-active')) { + event.target.playVideo(); + } + }, + }, + }); + } else { + GalleryYTPlayer.addListener(() => { + playVideo(slide); + }); + } + } else { + getYTPlayer(); + GalleryYTPlayer.addListener(() => { + playVideo(slide); + }); + } + } else if (vk) { + if (slide.VKPlayer) { + if (slide.classList.contains('swiper-slide-active')) { + slide.VKPlayer.play(); + } + } else if (window.VK?.VideoPlayer) { + if (GalleryVKPlayer.state) { + slide.VKPlayer = VK.VideoPlayer(vk); + slide.VKPlayer.on('inited', () => { + if (slide.classList.contains('swiper-slide-active')) { + slide.VKPlayer.play(); + } + }); + } else { + GalleryVKPlayer.addListener(() => { + playVideo(slide); + }); + } + } else { + getVKPlayer(); + GalleryVKPlayer.addListener(() => { + playVideo(slide); + }); + } + } else if (rt) { + const videoId = /embed\/(\w+)/.exec(rt.getAttribute('src'))?.[1], + checkInterval = setInterval(() => { + if (window.GalleryRTPlayer) { + if (window.GalleryRTPlayer.has(videoId)) { + clearInterval(checkInterval); + if (slide.classList.contains('swiper-slide-active')) { + rt.contentWindow.postMessage( + JSON.stringify({ + type: 'player:play', + data: {}, + }), + '*', + ); + } + } + } else { + clearInterval(checkInterval); + } + }, 100); + slide.checkInterval = checkInterval; + } + }, + pauseVideo = (slide) => { + const youtube = slide.querySelector('.advdominion-lg__video_youtube'), + vk = slide.querySelector('.advdominion-lg__video_vk'), + rt = slide.querySelector('.advdominion-lg__video_rt'); + + if (youtube && slide.YTPlayer) { + slide.YTPlayer.pauseVideo(); + } else if (vk && slide.VKPlayer) { + slide.VKPlayer.pause(); + } else if (rt) { + rt.contentWindow.postMessage( + JSON.stringify({ + type: 'player:pause', + data: {}, + }), + '*', + ); + } + }, + setVideoSize = (gallery) => { + const items = gallery.querySelectorAll('.advdominion-lg__video-wrapper'); + items[0].style.cssText = ''; + let height = (items[0].getBoundingClientRect().width * 9) / 16, + width = ''; + if (height > items[0].getBoundingClientRect().height) { + height = items[0].getBoundingClientRect().height; + width = `${(height * 16) / 9}px`; + } + for (const item of items) { + item.style.cssText = ` + height: ${height}px; + width: ${width}; + `; + } + }, + hide = async (duration = 500, easing = 'linear') => { + const gallery = document.body.querySelector('.advdominion-lg'); + + await gallery.animate([{ opacity: 1 }, { opacity: 0 }], { + duration, + easing, + }).finished; + + // Очищаем интервалы проверки для видео + for (const slide of gallery.querySelectorAll('.advdominion-lg__item_video')) { + if (slide.checkInterval) { + clearInterval(slide.checkInterval); + } + } + + gallery.remove(); + delete window.GalleryRTPlayer; + + document.body.style.cssText = ` + overflow: ''; + margin-right: ''; + `; + }, + show = async (duration = 500, easing = 'linear') => { + const gallery = document.body.querySelector('.advdominion-lg'); + + document.body.style.cssText = ` + overflow: hidden; + margin-right: ${getScrollbarWidth()}px + `; + + await gallery.animate([{ opacity: 0 }, { opacity: 1 }], { + duration, + easing, + }).finished; + }, + init = async (items = [], options = {}, index = 0) => { + const [{ default: Swiper }, { Navigation, Pagination }] = await Promise.all([ + import('swiper'), + import('swiper/modules'), + ]), + isVideoInGallery = { + yt: false, + vk: false, + rt: false, + }; + items = items.map(({ url: item, caption }) => { + if (/youtube\.com\/watch\?v=/.test(item)) { + isVideoInGallery.yt = true; + return ` +
+
+
+ Загрузка YouTube-плеера... +
+
+ ${caption ? `
${caption}
` : ''} +
+ `; + } else if (/(?:vkvideo\.ru|vk\.com)\/video/.test(item)) { + isVideoInGallery.vk = true; + const videoId = /(?:vkvideo\.ru|vk\.com)\/video(-?\d+)_(\d+)/.exec(item); + if (videoId === null) { + return ` +
+
+
+ Ошибка при загрузке VK-плеера +
+
+ ${caption ? `
${caption}
` : ''} +
+ `; + } + return ` +
+
+ +
+ ${caption ? `
${caption}
` : ''} +
+ `; + } else if (/rutube\.ru/.test(item)) { + isVideoInGallery.rt = true; + const videoId = /video\/(\w+)/.exec(item); + if (videoId === null) { + return ` +
+
+
+ Ошибка при загрузке RuTube-плеера +
+
+ ${caption ? `
${caption}
` : ''} +
+ `; + } + return ` +
+
+ +
+ ${caption ? `
${caption}
` : ''} +
+ `; + } + return ` +
+ + ${caption ? `
${caption}
` : ''} +
+ `; + }); + + if (isVideoInGallery.yt && !window.GalleryYTPlayer) { + window.GalleryYTPlayer = { + state: false, + get init() { + return this.state; + }, + set init(val) { + this.state = val; + for (const i of this.callback) { + i(val); + } + }, + callback: [], + addListener(callback) { + this.callback.push(callback); + }, + }; + + window.onYouTubeIframeAPIReady = () => { + window.GalleryYTPlayer.init = true; + }; + } + + if (isVideoInGallery.vk && !window.GalleryVKPlayer) { + window.GalleryVKPlayer = { + state: false, + get init() { + return this.state; + }, + set init(val) { + this.state = val; + for (const i of this.callback) { + i(val); + } + }, + callback: [], + addListener(callback) { + this.callback.push(callback); + }, + }; + + const checkInterval = setInterval(() => { + if (window.VK?.VideoPlayer) { + clearInterval(checkInterval); + window.GalleryVKPlayer.init = true; + } + }, 100); + } + + if (isVideoInGallery.rt) { + window.GalleryRTPlayer = new Set(); + window.addEventListener('message', (event) => { + if (event.origin.includes('rutube.ru') && typeof event.data === 'string') { + const message = JSON.parse(event.data); + if (message.type === 'player:ready') { + window.GalleryRTPlayer.add(message.data.videoId); + } + } + }); + } + + document.body.insertAdjacentHTML( + 'beforeend', + ` +
+ +
+
+ ${items.join('')} +
+ ${options.pagination || ''} + ${options.navigation || ''} +
+
+ `, + ); + + const gallery = document.body.querySelector('.advdominion-lg'); + + gallery.querySelector('.advdominion-lg__container').style.cssText = ` + visibility: hidden; + `; + + await show(options?.animation?.show?.duration, options?.animation?.show?.easing); + + const swiper = new Swiper(gallery.querySelector('.advdominion-lg__container'), { + init: false, + initialSlide: index, + ...options.swiper, + modules: [...new Set([Navigation, Pagination, ...(options.swiper?.modules || [])])], + }); + + swiper.on('init', function init() { + gallery.querySelector('.advdominion-lg__container').style.cssText = ` + visibility: ''; + `; + + if (this.slides[this.activeIndex].classList.contains('advdominion-lg__item_video')) { + playVideo(this.slides[this.activeIndex]); + } + + if (isVideoInGallery.yt || isVideoInGallery.vk || isVideoInGallery.rt) { + setVideoSize(gallery); + } + }); + + swiper.on('slideChange', function init() { + setTimeout(() => { + for (const [index, slide] of Object.entries(this.slides)) { + if (slide.classList.contains('advdominion-lg__item_video')) { + if (Number(index) === this.activeIndex) { + playVideo(slide); + } else { + pauseVideo(slide); + } + } + } + }, 0); + }); + + swiper.on('resize', () => { + if (isVideoInGallery.yt || isVideoInGallery.vk || isVideoInGallery.rt) { + setVideoSize(gallery); + } + }); + + swiper.init(); + + const closeHandler = () => { + hide(options?.animation?.hide?.duration, options?.animation?.hide?.easing); + }, + keydownHandler = (e) => { + if (e.key === 'Escape') { + closeHandler(); + document.body.removeEventListener('keydown', keydownHandler); + } + }; + + gallery.querySelector('.advdominion-lg__close').addEventListener('click', closeHandler); + document.body.addEventListener('keydown', keydownHandler); + }; + +export const gallery = (container, options) => { + const items = []; + if (options.single) { + const element = container.children[0]; + items.push({ + url: element.href, + caption: document.querySelector(element.dataset.galleryCaption)?.textContent, + }); + container.addEventListener('click', (event) => { + event.preventDefault(); + init(items, options); + }); + } else { + for (const [index, element] of [...container.children].entries()) { + items.push({ + url: element.href, + caption: document.querySelector(element.dataset.galleryCaption)?.textContent, + }); + element.addEventListener('click', (event) => { + event.preventDefault(); + init(items, options, index); + }); + } + } +}; diff --git a/packages/lightbox-gallery/package.json b/packages/lightbox-gallery/package.json new file mode 100644 index 0000000..b329d2b --- /dev/null +++ b/packages/lightbox-gallery/package.json @@ -0,0 +1,20 @@ +{ + "name": "@advdominion/lightbox-gallery", + "version": "4.0.1", + "type": "module", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://gitea.optiweb.ru/public/lightbox-gallery.git" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@advdominion/get-scrollbar-width": "^2.0.0" + }, + "peerDependencies": { + "swiper": "^12.0.0" + } +} diff --git a/packages/lightbox-gallery/styles.css b/packages/lightbox-gallery/styles.css new file mode 100644 index 0000000..197bac7 --- /dev/null +++ b/packages/lightbox-gallery/styles.css @@ -0,0 +1,75 @@ +.advdominion-lg { + background-color: white; + height: 100%; + left: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 999; +} + +.advdominion-lg__close { + position: absolute; + right: 0; + top: 0; + z-index: 2; +} + +.advdominion-lg__close-icon { + fill: currentcolor; + height: 25px; + width: 25px; +} + +.advdominion-lg__container.swiper { + height: 100%; +} + +.advdominion-lg__wrapper.swiper-wrapper { +} + +.advdominion-lg__item.swiper-slide { + align-items: center; + display: flex; + flex-direction: column; + gap: 25px; + justify-content: center; +} + +.advdominion-lg__item_video.swiper-slide { +} + +.advdominion-lg__image { + max-height: 100%; + max-width: 100%; +} + +.advdominion-lg__video-wrapper { + height: 100%; + position: relative; + width: 100%; +} + +.advdominion-lg__video { + border: none; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} + +div.advdominion-lg__video { + align-items: center; + display: flex; + justify-content: center; +} + +.advdominion-lg__video_youtube { +} + +.advdominion-lg__video_vk { +} + +.advdominion-lg__caption { +}