vvjs-1.0.1/js/vvjs.js

js/vvjs.js
/**
 * @file
 * Views Vanilla Javascript Slideshow.
 *
 * Filename:     vvjs.js
 * Website:      https://www.flashwebcenter.com
 * Developer:    Alaa Haddad https://www.alaahaddad.com.
 */
((Drupal, drupalSettings, once) => {
  'use strict';

  function debounce(func, delay) {
    let timer;
    return function() {
      clearTimeout(timer);
      timer = setTimeout(func, delay);
    };
  }
  const playIcon = `
    <svg class="svg-play" xmlns="http://www.w3.org/2000/svg" viewBox="80 -880 800 800" fill="currentColor">
      <path d="m380-300 280-180-280-180v360ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"></path>
    </svg>`;
  const pauseIcon = `
    <svg class="svg-pause" xmlns="http://www.w3.org/2000/svg" viewBox="80 -880 800 800" fill="currentColor"><path d="M360-320h80v-320h-80v320Zm160 0h80v-320h-80v320ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"></path></svg>`;
  Drupal.behaviors.VVJSlideshow = {
    attach(context) {
      const slideshows = once('vvjSlideshow', '.vvjs-inner', context);
      if (!slideshows.length) {
        return;
      }
      slideshows.forEach((slideshowInner) => {
        const slideshow = slideshowInner.querySelector('.vvjs-items');
        const slides = slideshow.querySelectorAll('.vvjs-item');
        const announcer = slideshowInner.querySelector('.announcer');
        const progressBar = slideshowInner.querySelector('.progressbar');
        const playPauseButton = slideshowInner.querySelector('.play-pause-button');
        const nextButton = slideshowInner.querySelector('.next-arrow');
        const prevButton = slideshowInner.querySelector('.prev-arrow');
        const dots = slideshowInner.querySelectorAll('.dots-numbers-button');
        const slideTime = parseInt(slideshowInner.dataset.time, 10) || 5000;
        const showProgress = slideshowInner.dataset.showSlideProgress === 'true';
        const totalSlides = slides.length;
        let slideIndex = 1;
        let isPaused = slideshowInner.dataset.static === 'true';
        let autoSlideIntervalId = null;
        let progressIntervalId = null;
        let slideStartTime = Date.now();
        let isVisible = true;
        let previousVisibility = false;
        const updateSlideVisibility = () => {
          slides.forEach((slide, index) => {
            const isActive = index + 1 === slideIndex;
            slide.style.display = isActive ? 'block' : 'none';
            slide.setAttribute('aria-hidden', !isActive);
            slide.toggleAttribute('inert', !isActive);
            slide.classList.toggle('active', isActive);
            slide.querySelectorAll('a, button, input').forEach(el => {
              el.setAttribute('tabindex', isActive ? '0' : '-1');
            });
          });
          updateDots();
          announceSlide();
          adjustHeight();
          startProgressBar();
        };
        const adjustHeight = () => {
          const currentSlide = slides[slideIndex - 1];
          if (!currentSlide) return;

          const computedStyle = window.getComputedStyle(slideshow);
          const slideRect = currentSlide.getBoundingClientRect();
          const contentHeight = slideRect.height;

          const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
          const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
          const marginTop = parseFloat(computedStyle.marginTop) || 0;
          const marginBottom = parseFloat(computedStyle.marginBottom) || 0;
          const borderTop = parseFloat(computedStyle.borderTopWidth) || 0;
          const borderBottom = parseFloat(computedStyle.borderBottomWidth) || 0;

          const totalHeight = contentHeight + paddingTop + paddingBottom + borderTop + borderBottom;
          slideshow.style.height = `${totalHeight}px`;
        };

        const announceSlide = () => {
          announcer.textContent = `Slide ${slideIndex} of ${totalSlides}`;
        };
        const updateDots = () => {
          dots.forEach((dot, index) => {
            dot.classList.toggle('active', index + 1 === slideIndex);
            dot.setAttribute('aria-selected', index + 1 === slideIndex);
          });
          const currentSlide = slideshowInner.querySelector('.current-slide');
          if (currentSlide) {
            currentSlide.textContent = slideIndex;
          }
        };
        const startAutoSlide = () => {
          stopAutoSlide();
          if (slideTime > 0 && !isPaused && isVisible) {
            autoSlideIntervalId = setInterval(nextSlide, slideTime);
            startProgressBar();
          }
        };
        const stopAutoSlide = () => {
          clearInterval(autoSlideIntervalId);
          clearInterval(progressIntervalId);
        };
        const nextSlide = () => {
          slideIndex = (slideIndex % totalSlides) + 1;
          updateSlideVisibility();
        };
        const prevSlide = () => {
          slideIndex = (slideIndex === 1) ? totalSlides : slideIndex - 1;
          updateSlideVisibility();
        };
        const togglePlayPause = () => {
          isPaused = !isPaused;
          playPauseButton.innerHTML = isPaused ? playIcon : pauseIcon;
          playPauseButton.setAttribute('aria-label', isPaused ? 'Play slideshow' : 'Pause slideshow');
          isPaused ? stopAutoSlide() : startAutoSlide();
        };
        const handleDotClick = (index) => {
          slideIndex = index + 1;
          updateSlideVisibility();
          startAutoSlide();
        };
        const startProgressBar = () => {
          if (!showProgress || !progressBar) return;
          clearInterval(progressIntervalId);
          slideStartTime = Date.now();
          progressIntervalId = setInterval(() => {
            const elapsed = Date.now() - slideStartTime;
            const progress = Math.min(100, (elapsed / slideTime) * 100);
            progressBar.style.setProperty('--progress', `${progress}%`);
            progressBar.setAttribute('aria-valuenow', Math.round(progress));
            if (progress >= 100) clearInterval(progressIntervalId);
          }, 50);
        };
        const applyReducedMotion = () => {
          if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
            isPaused = true;
            stopAutoSlide();
            slideshow.classList.add('reduced-motion');
            playPauseButton.innerHTML = playIcon;
          }
        };
        const observer = new IntersectionObserver((entries) => {
          const entry = entries[0];
          isVisible = entry.isIntersecting;
          if (isVisible && !isPaused) {
            startAutoSlide();
          } else {
            stopAutoSlide();
          }
        }, {
          threshold: 0.5
        });
        observer.observe(slideshowInner);
        // Event Listeners.
        playPauseButton?.addEventListener('click', togglePlayPause);
        nextButton?.addEventListener('click', () => {
          nextSlide();
          startAutoSlide();
        });
        prevButton?.addEventListener('click', () => {
          prevSlide();
          startAutoSlide();
        });
        dots.forEach((dot, index) => dot.addEventListener('click', () => handleDotClick(index)));
        slideshow.addEventListener('mouseover', stopAutoSlide);
        slideshow.addEventListener('mouseout', startAutoSlide);
        slideshow.addEventListener('touchstart', (e) => {
          slideshow._touchStartX = e.touches[0].clientX;
        });
        slideshow.addEventListener('touchend', (e) => {
          const delta = e.changedTouches[0].clientX - slideshow._touchStartX;
          delta > 50 ? prevSlide() : delta < -50 ? nextSlide() : null;
          startAutoSlide();
        });
        document.addEventListener('keydown', (e) => {
          // Skip handling Spacebar if the focused element is an input,
          // textarea, or contenteditable.
          if (
            e.target.closest('input, textarea, [contenteditable="true"]')
          ) {
            return;
          }

          if (e.key === 'ArrowRight') {
            nextSlide();
          } else if (e.key === 'ArrowLeft') {
            prevSlide();
          } else if (e.key === ' ') {
            e.preventDefault();
            togglePlayPause();
          }
        });
        function handleSlideshowVisibilityChange() {
          if (document.hidden) {
            stopAutoSlide();
          } else {
            isVisible = isMostlyVisible(slideshowInner);
            if (isVisible && !isPaused) {
              startAutoSlide();
            }
          }
        }
        const isMostlyVisible = (element) => {
          const rect = element.getBoundingClientRect();
          const visibleHeight = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0);
          return visibleHeight / rect.height > 0.2;
        };

        function handleVisibility() {
          const currentlyVisible = isMostlyVisible(slideshowInner);
          if (currentlyVisible !== previousVisibility) {
            previousVisibility = currentlyVisible;
            if (currentlyVisible && !isPaused) {
              startAutoSlide();
            } else {
              stopAutoSlide();
            }
          }
        }

        function setupListeners() {
          document.addEventListener('scroll', debounce(handleVisibility, 200));
          window.addEventListener('resize', debounce(() => {
            adjustHeight();
            handleVisibility();
          }, 200));
          document.addEventListener('visibilitychange', handleSlideshowVisibilityChange);
        }
        // Initialize
        applyReducedMotion();
        updateSlideVisibility();
        startAutoSlide();
        setupListeners();
      });
    }
  };
})(Drupal, drupalSettings, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc