Videoslider

100%
<div class="cell">
    <section class="grid-container o-section">
        <div class="grid-x grid-margin-x">
            <div class="vs-container" data-href="/assets/json/videos.json">
                <aside class="vs-sidebar">
                    <h2>Short News</h2>
                    <p>Kurze Video Beiträge zu aktuellen Themen</p>
                </aside>
                <div class="vs-filters" role="region" aria-label="Filter-Leiste">
                    <ul class="vs-context-menu__list">
                        <li class="vs-context-menu__item">
                            <button class="vs-filter-btn vs-context-menu__link active" data-filter="all" aria-label="Filter: Alle" aria-pressed="true">Alle Beiträge
              </button>
                        </li>
                        <li class="vs-context-menu__item">
                            <button class="vs-filter-btn vs-context-menu__link" data-filter="459" aria-label="Filter: Gastro" aria-pressed="false">Gastro
              </button>
                        </li>
                        <li class="vs-context-menu__item">
                            <button class="vs-filter-btn vs-context-menu__link" data-filter="458" aria-label="Filter: Hildesheim" aria-pressed="false">Hildesheim
              </button>
                        </li>
                    </ul>
                </div>
                <div class="vs-slider-area">
                    <div class="vs-class-slider-grid-wrapper vs-slider-grid swiper">
                        <div class="swiper-wrapper">
                            <f:for each="{items}" as="video" iteration="i">
                                <div class="swiper-slide vs-slide" data-slide-index="{i.index}" data-open-modal="{i.index}">
                                    <div class="vs-thumbnail vs-thumbnail-index-{i.index}">
                                        <img src="{video.resolvedThumbnail}" alt="{video.title}">
                                        <span class="vs-play-icon">â–º</span>
                                        <div class="vs-thumb-overlay">
                                            <div class="vs-thumb-text">
                                                <span class="c-badge c-badge--user"><strong>HAZ+</strong></span>
                                                <span class="vs-category" data-id="{video.category.id}">{video.category.title}</span>
                                                <h2>{video.title}</h2>
                                            </div>
                                            <span>{video.shortdesc}</span>
                                        </div>
                                    </div>
                                </div>
                            </f:for>
                        </div>
                        <div class="swiper-button-prev vs-nav-arrow vs-nav-prev" aria-label="Vorheriges Slide"></div>
                        <div class="swiper-button-next vs-nav-arrow vs-nav-next" aria-label="Nächstes Slide"></div>
                    </div>
                    <div class="vs-video-modal vs-slider-grid close">
                        <div class="vs-modal-content">
                            <div class="vs-modal-video-wrapper">
                                <div class="vs-modal-video-inner"></div>
                            </div>
                            <div class="vs-modal-text">
                                <div class="vs-modal-title vs-thumb-text">
                                    <span class="c-badge c-badge--user"><strong>HAZ+</strong></span>
                                    <span class="vs-category">{currentCategory}</span>
                                    <h2>{currentTitle}</h2>
                                </div>
                                <p class="vs-modal-desc">{currentDescription}</p>
                                <a href="" class="vs-modal-cta vs-cta-btn">Mehr erfahren</a>
                                <button class="vs-close-modal" aria-label="Schließen">×</button>
                            </div>
                            <button class="vs-slider-nav vs-modal-nav vs-modal-prev" aria-label="Vorheriges Video">&lt;</button>
                            <button class="vs-slider-nav vs-modal-nav vs-modal-next" aria-label="Nächstes Video">&gt;</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
</div>
<div class="cell">
  <section class="grid-container o-section">
    <div class="grid-x grid-margin-x">
      <div class="vs-container" data-href="/assets/json/videos.json">
        <aside class="vs-sidebar">
          <h2>Short News</h2>
          <p>Kurze Video Beiträge zu aktuellen Themen</p>
        </aside>
        <div class="vs-filters" role="region" aria-label="Filter-Leiste">
          <ul class="vs-context-menu__list">
            <li class="vs-context-menu__item">
              <button class="vs-filter-btn vs-context-menu__link active" data-filter="all" aria-label="Filter: Alle"
                      aria-pressed="true">Alle Beiträge
              </button>
            </li>
            <li class="vs-context-menu__item">
              <button class="vs-filter-btn vs-context-menu__link" data-filter="459" aria-label="Filter: Gastro"
                      aria-pressed="false">Gastro
              </button>
            </li>
            <li class="vs-context-menu__item">
              <button class="vs-filter-btn vs-context-menu__link" data-filter="458" aria-label="Filter: Hildesheim"
                      aria-pressed="false">Hildesheim
              </button>
            </li>
          </ul>
        </div>
        <div class="vs-slider-area">
          <div class="vs-class-slider-grid-wrapper vs-slider-grid swiper">
            <div class="swiper-wrapper">
              <f:for each="{items}" as="video" iteration="i">
                <div class="swiper-slide vs-slide" data-slide-index="{i.index}" data-open-modal="{i.index}">
                  <div class="vs-thumbnail vs-thumbnail-index-{i.index}">
                    <img src="{video.resolvedThumbnail}" alt="{video.title}">
                    <span class="vs-play-icon">â–º</span>
                    <div class="vs-thumb-overlay">
                      <div class="vs-thumb-text">
                        <span class="c-badge c-badge--user"><strong>HAZ+</strong></span>
                        <span class="vs-category" data-id="{video.category.id}">{video.category.title}</span>
                        <h2>{video.title}</h2>
                      </div>
                      <span>{video.shortdesc}</span>
                    </div>
                  </div>
                </div>
              </f:for>
            </div>
            <div class="swiper-button-prev vs-nav-arrow vs-nav-prev" aria-label="Vorheriges Slide"></div>
            <div class="swiper-button-next vs-nav-arrow vs-nav-next" aria-label="Nächstes Slide"></div>
          </div>
          <div class="vs-video-modal vs-slider-grid close">
            <div class="vs-modal-content">
              <div class="vs-modal-video-wrapper">
                <div class="vs-modal-video-inner"></div>
              </div>
              <div class="vs-modal-text">
                <div class="vs-modal-title vs-thumb-text">
                  <span class="c-badge c-badge--user"><strong>HAZ+</strong></span>
                  <span class="vs-category">{currentCategory}</span>
                  <h2>{currentTitle}</h2>
                </div>
                <p class="vs-modal-desc">{currentDescription}</p>
                <a href="" class="vs-modal-cta vs-cta-btn">Mehr erfahren</a>
                <button class="vs-close-modal" aria-label="Schließen">×</button>
              </div>
              <button class="vs-slider-nav vs-modal-nav vs-modal-prev" aria-label="Vorheriges Video">&lt;</button>
              <button class="vs-slider-nav vs-modal-nav vs-modal-next" aria-label="Nächstes Video">&gt;</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
</div>
/* No context defined for this component. */
  • Content:
    import Swiper from 'swiper/swiper-bundle';
    
    class Videoslider {
      /**
       *
       * @param {HTMLElement} element
       */
      constructor(element) {
        this.swiperContainer = element.querySelector('.vs-class-slider-grid-wrapper.swiper');
        this.swiperWrapper = this.swiperContainer.querySelector('.swiper-wrapper');
        this.swiperPrev = this.swiperContainer.querySelector('.vs-nav-prev');
        this.swiperNext = this.swiperContainer.querySelector('.vs-nav-next');
        this.current = 0;
    
        this.container = element;
        this.container.classList.add('loading');
    
        this.videos = [];
        this.filteredVideos = [];
    
        this.modal = element.querySelector('.vs-video-modal');
        this.titleEl = this.modal.querySelector('.vs-modal-title');
        this.descEl = this.modal.querySelector('.vs-modal-desc');
        this.ctaEl = this.modal.querySelector('.vs-modal-cta');
        this.videoWrapper = this.modal.querySelector('.vs-modal-video-wrapper');
        this.videoWrapperInner = this.videoWrapper.querySelector('.vs-modal-video-inner');
    
        this.registerModalEvents();
        this.registerFilterEvents(element);
        // Globale Referenz für Event-Delegation
        window.videoslider = this;
      }
    
      initialize(response) {
        response
          // eslint-disable-next-line no-shadow
          .then((response) => response.json())
          // eslint-disable-next-line no-shadow
          .then((response) => {
            this.videos = response;
            this.filteredVideos = this.videos;
            this.renderSlides(this.videos);
          })
          .catch((error) => console.error('Fehler beim Laden der JSON-Daten:', error));
      }
    
      renderSlides(items) {
        this.filteredVideos = items;
        this.swiperWrapper.innerHTML = '';
        items.forEach((video, index) => {
          const slide = this.createSlide(video, index);
          this.swiperWrapper.appendChild(slide);
        });
        if (!this.swiper) {
          this.swiper = new Swiper(this.swiperContainer, {
            slidesPerView: 'auto',
            centeredSlides: true,
            spaceBetween: 16,
            autoHeight: true,
            loop: true,
            navigation: {
              nextEl: '.vs-nav-next',
              prevEl: '.vs-nav-prev',
            },
            breakpoints: {
              768: {
                slidesPerView: 'auto',
                centeredSlides: false,
              },
            },
          });
          this.swiper.on('click', (swiper, event) => {
            if (!event || typeof event.target?.closest !== 'function') return;
    
            const slide = event.target.closest('.swiper-slide');
            if (!slide || !slide.dataset.slideIndex) return;
    
            const index = parseInt(slide.dataset.slideIndex, 10);
            if (!Number.isNaN(index)) {
              this.openModal(index);
            }
          });
        } else {
          this.swiper.update();
        }
        this.container.classList.remove('loading');
      }
    
      openModal(index) {
        const video = this.filteredVideos[index];
        if (!video) return;
        this.current = index;
        this.videoWrapperInner.innerHTML = video.videocode;
    
        this.titleEl.innerHTML = `
            <span class="c-badge c-badge--user"><strong>HAZ+</strong></span>
            <span>${video.category?.title ?? ''}</span>
            <h2>${video.title}</h2>
          `;
    
        this.descEl.innerHTML = video.content;
        const link = video.resolvedLink || video.cta;
        if (link) {
          this.ctaEl.href = link;
          this.ctaEl.style.display = '';
        } else {
          this.ctaEl.style.display = 'none';
        }
        // Show modal
        this.modal.classList.remove('close');
        this.modal.classList.add('open');
        this.swiperPrev.style.display = 'none';
        this.swiperNext.style.display = 'none';
      }
    
      closeModal() {
        this.videoWrapperInner.innerHTML = '';
        this.modal.classList.remove('open');
        this.modal.classList.add('close');
        this.swiperPrev.style.display = 'flex';
        this.swiperNext.style.display = 'flex';
      }
    
      registerModalEvents() {
        const modalPrev = this.modal.querySelector('.vs-modal-prev');
        const modalNext = this.modal.querySelector('.vs-modal-next');
        const modalClose = this.modal.querySelector('.vs-close-modal');
    
        modalPrev.addEventListener('click', () => {
          this.closeModal();
          // eslint-disable-next-line max-len
          const prevIndex = (this.current - 1 + this.filteredVideos.length) % this.filteredVideos.length;
          this.openModal(prevIndex);
        });
        modalNext.addEventListener('click', () => {
          this.closeModal();
          const nextIndex = (this.current + 1) % this.filteredVideos.length;
          this.openModal(nextIndex);
        });
        modalClose.addEventListener('click', () => {
          this.closeModal();
        });
      }
    
      registerFilterEvents(element) {
        const filterBtns = element.querySelectorAll('.vs-filter-btn');
        const allButton = element.querySelector('.vs-filter-btn[data-filter="all"]');
    
        filterBtns.forEach((btn) => btn.addEventListener('click', () => {
          if (btn.dataset.filter === 'all') {
            filterBtns.forEach((b) => b.classList.remove('active'));
            btn.classList.add('active');
            this.renderSlides(this.videos);
            return;
          }
    
          btn.classList.toggle('active');
          allButton.classList.remove('active');
          const act = Array.from(filterBtns)
            .filter((b) => b.classList.contains('active') && b.dataset.filter !== 'all')
            .map((b) => b.dataset.filter);
          let slides;
          if (act.length === 0) {
            filterBtns[0].classList.add('active');
            slides = this.videos;
          } else {
            slides = this.videos.filter((video) => {
              if (!video.category || !video.category.id) return false;
              return act.includes(String(video.category.id));
            });
          }
          this.renderSlides(slides);
        }));
      }
    
      // eslint-disable-next-line class-methods-use-this
      createSlide(video, index) {
        const slide = document.createElement('div');
        slide.dataset.slideIndex = index;
        slide.className = 'swiper-slide vs-slide';
        slide.innerHTML = `
        <div class="vs-thumbnail vs-thumbnail-index-${index}">
          <img src="${video.resolvedThumbnail}" alt="${video.title}" style="width:100%;height:100%;object-fit:cover;">
          <span class="vs-play-icon">&#9658;</span>
          <div class="vs-thumb-overlay">
            <div class="vs-thumb-text">
              <span class="c-badge c-badge--user"><strong>HAZ+</strong></span>
              <span class="vs-category" data-id="${video.category?.id ?? ''}">${video.category?.title ?? ''}</span>
              <h2>${video.title}</h2>
            </div>
            <span>${video.shortdesc}</span>
          </div>
        </div>
      `;
        return slide;
      }
    }
    
    export default Videoslider;
    
  • URL: /components/raw/videoslider/videoslider.js
  • Filesystem Path: src/patterns/20-components/videoslider/videoslider.js
  • Size: 6.6 KB

There are no notes for this item.