Carousel Image Preloader

Intelligently preload images in carousels for faster navigation

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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

})();