// ============================================================================ // Stylescape | Image Compare Slider // ============================================================================ // Interactive before/after image comparison slider with drag support. // Supports data-ss-image-compare attributes for declarative configuration. // ============================================================================ /** * Configuration options for ImageCompareSlider */ export interface ImageCompareSliderOptions { /** Initial slider position (0-100 percentage) */ initialPosition?: number; /** CSS class for the slider handle */ sliderClass?: string; /** CSS class for the overlay image */ overlayClass?: string; /** Enable touch support */ touch?: boolean; /** Show labels for before/after */ showLabels?: boolean; /** Before image label text */ beforeLabel?: string; /** After image label text */ afterLabel?: string; /** Callback when slider position changes */ onChange?: (position: number) => void; } /** * Interactive image comparison slider for before/after images. * Supports mouse and touch interactions. * * @example JavaScript * ```typescript * const container = document.querySelector(".image__compare") * const slider = new ImageCompareSlider(container, { * initialPosition: 50, * showLabels: true, * onChange: (pos) => console.log(`Position: ${pos}%`) * }) * ``` * * @example HTML with data-ss * ```html *
* After * Before *
*
* ``` * * @example Static initialization for all sliders * ```typescript * ImageCompareSlider.initAll(".image__compare") * ``` */ export class ImageCompareSlider { /** The container element for the slider */ private container: HTMLElement; /** The overlay (before) image element */ private overlay: HTMLImageElement; /** The base (after) image element */ private baseImage: HTMLImageElement; /** The draggable slider handle element */ private slider: HTMLElement; /** Whether the slider is currently being dragged */ private isActive: boolean = false; /** * Creates a new ImageCompareSlider instance. * * @param container - The container element holding both images and slider */ constructor(container: HTMLElement) { this.container = container; this.slider = container.querySelector( ".image__compare--slider", ) as HTMLElement; this.overlay = container.querySelector( ".image__compare--overlay", ) as HTMLImageElement; this.baseImage = container.querySelector( "img.image__compare--image:not(.image__compare--overlay)", ) as HTMLImageElement; if ( !this.container || !this.slider || !this.overlay || !this.baseImage ) { console.warn( `ImageCompareSlider skipped: required elements not found in`, container, ); return; } // Initialize brightness checks this.checkAndInject(this.baseImage); this.checkAndInject(this.overlay); this.initEvents(); this.slideMove(this.container.offsetWidth / 2); } /** * Checks image brightness and injects dark mode indicators if needed. * * @param image - The image element to check */ private checkAndInject(image: HTMLImageElement): void { const side = image.dataset.darkSide; if (!side) return; const inject = () => { this.isImageBright(image) .then((isBright) => { if (!isBright) return; const el = document.createElement("div"); el.className = `dark--${side}`; this.slider.appendChild(el); // Ok rengini değiştir const arrow = this.slider.querySelector( `.arrow--${side}`, ) as HTMLElement; if (arrow) { arrow.style.borderColor = "var(--color_text_primary)"; } }) .catch((err) => { console.warn("Brightness check failed:", err); }); }; if (image.complete && image.naturalWidth > 0) { inject(); } else { image.onload = () => { if (image.naturalWidth > 0) inject(); }; } } /** * Analyzes image brightness using canvas pixel sampling. * * @param image - The image element to analyze * @returns Promise resolving to true if image is bright (avg > 160) */ private isImageBright(image: HTMLImageElement): Promise { return new Promise((resolve) => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); if (!ctx) return resolve(false); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; ctx.drawImage(image, 0, 0); const data = ctx.getImageData( 0, 0, canvas.width, canvas.height, ).data; let r = 0, g = 0, b = 0, count = 0; const step = 4 * 20; for (let i = 0; i < data.length; i += step) { r += data[i]; g += data[i + 1]; b += data[i + 2]; count++; } const avg = (r + g + b) / (3 * count); resolve(avg > 160); }); } /** * Initializes mouse and touch event listeners for drag interaction. */ private initEvents(): void { this.slider.addEventListener( "mousedown", () => (this.isActive = true), ); window.addEventListener("mouseup", () => (this.isActive = false)); window.addEventListener("mousemove", (e) => { if (this.isActive) this.slideMove(e.clientX); }); this.slider.addEventListener( "touchstart", () => (this.isActive = true), ); window.addEventListener("touchend", () => (this.isActive = false)); window.addEventListener("touchmove", (e) => { if (this.isActive) this.slideMove(e.touches[0].clientX); }); } /** * Moves the slider and adjusts overlay width based on position. * * @param x - The x-coordinate (client position) to move to */ private slideMove(x: number): void { const bounds = this.container.getBoundingClientRect(); let pos = x - bounds.left; pos = Math.max(0, Math.min(pos, bounds.width)); this.overlay.style.width = `${pos}px`; this.slider.style.left = `${pos}px`; } /** * Static factory method to initialize all image compare sliders on the page. * * @param selector - CSS selector for container elements (default: ".image__compare") */ public static initAll(selector: string = ".image__compare"): void { const containers = document.querySelectorAll(selector); containers.forEach((container) => { new ImageCompareSlider(container); }); } }