Rakuten Hover Zoom (overlay safe)

楽天でマウスホバー時に大きい画像を表示(楽天のレイヤー対策)

// ==UserScript==
// @name         Rakuten Hover Zoom (overlay safe)
// @namespace    https://example.com/
// @version      1.1
// @description  楽天でマウスホバー時に大きい画像を表示(楽天のレイヤー対策)
// @match        https://item.rakuten.co.jp/*
// @match        https://search.rakuten.co.jp/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // ===== プレビュー用要素作成 =====
    const preview = document.createElement('div');
    Object.assign(preview.style, {
        position: 'fixed',
        pointerEvents: 'none',   // ← レイヤーに干渉されない
        zIndex: '2147483647',    // ← 最前面(楽天のレイヤーより上)
        border: '1px solid #ccc',
        background: '#fff',
        padding: '4px',
        display: 'none',
        boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
    });
    const img = document.createElement('img');
    Object.assign(img.style, {
        maxWidth: '500px',   // サイズ調整
        maxHeight: '500px'
    });
    preview.appendChild(img);
    document.body.appendChild(preview);

    // ===== イベント =====
    const thumbSelector = 'img.image--x5mNi, img[src*="tshop.r10s.jp"]';

    document.addEventListener('mouseover', e => {
        const t = e.target;
        if (t.matches(thumbSelector)) {
            // fitin=275:275 などを削ってオリジナルサイズURLを推測
            let bigUrl = t.src.replace(/\?.*$/, '');
            img.src = bigUrl;
            preview.style.display = 'block';
        }
    });

    document.addEventListener('mousemove', e => {
        if (preview.style.display === 'block') {
            preview.style.top = (e.clientY + 20) + 'px';
            preview.style.left = (e.clientX + 20) + 'px';
        }
    });

    document.addEventListener('mouseout', e => {
        if (e.target.matches(thumbSelector)) {
            preview.style.display = 'none';
        }
    });

    // ===== レイヤーの pointer-events を無効化(任意) =====
    // もし楽天側の拡大レイヤーが残って邪魔な場合
    const observer = new MutationObserver(muts => {
        muts.forEach(m => {
            m.addedNodes.forEach(node => {
                if (node.nodeType === 1 && node.matches('.some-rakuten-overlay-class')) {
                    node.style.pointerEvents = 'none';
                }
            });
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();
// ===== 邪魔な楽天ボタンを透過 =====
function disableRakutenOverlayButton() {
    document.querySelectorAll(
        'button.button--3YE3u' // 質問のHTMLから特定
    ).forEach(btn => {
        // クリック等は残したいなら pointer-events を none
        btn.style.pointerEvents = 'none';
        btn.style.opacity = '0'; // 完全に見えなくする
    });
}

// ページロード後と、動的追加に対応
disableRakutenOverlayButton();
const obs = new MutationObserver(disableRakutenOverlayButton);
obs.observe(document.body, { childList: true, subtree: true });