Carousel Image Preloader

Intelligently preload images in carousels for faster navigation

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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
  });

})();