Carousel Image Preloader

Intelligently preload images in carousels for faster navigation

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Carousel Image Preloader
// @namespace    Q2Fyb3VzZWwgSW1hZ2UgUHJlbG9hZGVy
// @version      1.1
// @description  Intelligently preload images in carousels for faster navigation
// @author       smed79
// @license      GPLv3
// @icon         https://i25.servimg.com/u/f25/11/94/21/24/imgloa10.png
// @match        *://*/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  const PRELOAD_BUFFER = 5;
  const PRELOAD_DELAY = 150; // Reduced delay for snappier preloading
  
  // Track what we've already done to save CPU and Network
  const processedCarousels = new WeakSet();
  const preloadedUrls = new Set();

  const carouselPatterns = /carousel|slider|swiper|glide|slick|splide|owl|gallery|slideshow|lightbox/i;

  function isCarouselContainer(el) {
    if (!el.className && !el.id) return false;
    return carouselPatterns.test(el.className) || carouselPatterns.test(el.id);
  }

  function getCarouselImages(container) {
    const images = Array.from(container.querySelectorAll('img'));
    const bgImages = Array.from(container.querySelectorAll('[style*="background-image"]'));
    return [...images, ...bgImages];
  }

  // Lightweight heuristic for visibility instead of getComputedStyle
  function getVisibleImageIndex(images) {
    const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
    
    for (let i = 0; i < images.length; i++) {
      const rect = images[i].getBoundingClientRect();
      // If it has width/height and is within the horizontal viewport bounds
      if (rect.width > 0 && rect.right > 0 && rect.left < viewportWidth) {
        return i;
      }
    }
    return 0; // Default to first if none found
  }

  function preloadImage(img) {
    let src = '';
    
    if (img.tagName === 'IMG') {
      src = img.dataset.src || img.src;
      // Override native lazy loading so it actually loads when we want it to
      if (img.hasAttribute('loading')) {
          img.setAttribute('loading', 'eager');
      }
    } else if (img.style && img.style.backgroundImage) {
      const match = img.style.backgroundImage.match(/url\(['"]?([^'")]+)['"]?\)/);
      if (match) src = match[1];
    }

    if (src && !src.includes('data:') && !preloadedUrls.has(src)) {
      preloadedUrls.add(src);
      
      // Tell the browser to prioritize this image
      const link = document.createElement('link');
      link.rel = 'preload';
      link.as = 'image';
      link.href = src;
      document.head.appendChild(link);
      
      // Memory fetch fallback
      const newImg = new Image();
      newImg.src = src;
    }
  }

  function preloadAdjacentImages(images, currentIndex) {
    const start = Math.max(0, currentIndex - PRELOAD_BUFFER);
    const end = Math.min(images.length, currentIndex + PRELOAD_BUFFER + 1);
    
    for (let i = start; i < end; i++) {
      preloadImage(images[i]);
    }
  }

  function monitorCarousel(container) {
    if (processedCarousels.has(container)) return;
    
    const images = getCarouselImages(container);
    if (images.length < 2) return;
    
    processedCarousels.add(container);
    
    let lastIndex = getVisibleImageIndex(images);
    preloadAdjacentImages(images, lastIndex);
    
    // Efficient event delegation - only trigger preload after interaction ends
    let debounceTimer;
    const handleInteraction = () => {
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => {
        const currentIndex = getVisibleImageIndex(images);
        if (currentIndex !== lastIndex) {
          lastIndex = currentIndex;
          preloadAdjacentImages(images, currentIndex);
        }
      }, PRELOAD_DELAY);
    };

    // Click & Touch events (Passive for better scroll performance)
    container.addEventListener('click', handleInteraction, { passive: true });
    container.addEventListener('touchend', handleInteraction, { passive: true });
    
    // Keyboard events
    container.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') handleInteraction();
    }, { passive: true });
  }

  function scanForCarousels() {
    // Only search common wrapper elements to save CPU
    const containers = document.querySelectorAll('div[class], section[class], div[id], section[id]');
    for (const container of containers) {
      if (isCarouselContainer(container)) {
        monitorCarousel(container);
      }
    }
  }

  // Initial Scan
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', scanForCarousels);
  } else {
    scanForCarousels();
  }

  // Efficient observer for dynamically loaded websites (Replaces the setInterval)
  let domTimeout;
  const observer = new MutationObserver((mutations) => {
    const hasNewNodes = mutations.some(m => m.addedNodes.length > 0);
    if (hasNewNodes) {
      clearTimeout(domTimeout);
      domTimeout = setTimeout(scanForCarousels, 1000); // Debounce massive DOM changes
    }
  });

  observer.observe(document.documentElement || document.body, {
    childList: true,
    subtree: true
  });

})();