Post

Image download4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// ==UserScript==
// @name         Image Download4
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Add a download icon to images larger than 400px when hovered.
// @author       Your Name
// @match        *://*/*
// @run-at document-idle
// @grant        GM_download
// ==/UserScript==

// bug: icon position

(function() {
    'use strict';

    // Create the download icon
    const icon = document.createElement('div');
    icon.textContent = '\u2B73'; // A simple arrow icon
    icon.style.position = 'absolute';
    icon.style.width = '24px';
    icon.style.height = '24px';
    icon.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
    icon.style.color = 'white';
    icon.style.fontSize = '18px';
    icon.style.textAlign = 'center';
    icon.style.lineHeight = '24px';
    icon.style.borderRadius = '50%';
    icon.style.cursor = 'pointer';
    icon.style.display = 'none';
    icon.style.zIndex = '10000';
    document.body.appendChild(icon);

    let currentImg = null;
    let iconVisible = false;

    // Function to check image size
    function isLargeImage(img) {
        return img.naturalWidth > 400 && img.naturalHeight > 400;
    }

    function getPosition (element) {
        var x = 0;
        var y = 0;
        // 搭配上面的示意圖可比較輕鬆理解為何要這麼計算
        while ( element ) {
            x += element.offsetLeft - element.scrollLeft + element.clientLeft;
            y += element.offsetTop - element.scrollLeft + element.clientTop;
            element = element.offsetParent;
        }

        return { x: x, y: y };
    }

    // get document coordinates of the element
    function getCoords(elem) {
        let box = elem.getBoundingClientRect();

        return {
            top: box.top + window.pageYOffset,
            right: box.right + window.pageXOffset,
            bottom: box.bottom + window.pageYOffset,
            left: box.left + window.pageXOffset
        };
    }

    // Mouse move event on images
    document.addEventListener('mouseover', (e) => {
        const img = e.target;
        if (img.tagName === 'IMG' && isLargeImage(img) && !iconVisible) {
            console.log('move');
            /*let position = getPosition(img);
            icon.style.left = `${position.x+100}px`;
            icon.style.top = `${position.y}px`;*/
            let position = getCoords(img);
            icon.style.left = `${position.left+24}px`;
            icon.style.top = `${position.bottom-24}px`;
            //const rect = img.getBoundingClientRect();
            //icon.style.left = `${rect.right - 30}px`;
            //icon.style.top = `${rect.bottom - 30}px`;
            icon.style.display = 'block';
            currentImg = img;
            iconVisible = true;
        }
    });

    //detect if URL has changed
    //  work on twitter
    window.navigation.addEventListener("navigate", (e) => {
        //same as above
        const img = e.target;
        if (img.tagName === 'IMG' && isLargeImage(img) && !iconVisible) {
            console.log('move');
            /*let position = getPosition(img);
            icon.style.left = `${position.x+100}px`;
            icon.style.top = `${position.y}px`;*/
            let position = getCoords(img);
            icon.style.left = `${position.left+24}px`;
            icon.style.top = `${position.bottom-24}px`;
            //const rect = img.getBoundingClientRect();
            //icon.style.left = `${rect.right - 30}px`;
            //icon.style.top = `${rect.bottom - 30}px`;
            icon.style.display = 'block';
            currentImg = img;
            iconVisible = true;
        }
    })

    document.addEventListener('mouseout', (e) => {
        if (e.target === currentImg && e.relatedTarget !== icon) {
            console.log('out');
            icon.style.display = 'none';
            currentImg = null;
            iconVisible = false;
        }
    });

    // Download image on icon click
    icon.addEventListener('click', () => {
        if (currentImg) {
            const imageUrl = currentImg.src;
            const fileName = imageUrl.split('/').pop().split('?')[0];
            GM_download(imageUrl, fileName + '.jpg');
        }
    });
})();

我可以幫您融合這兩個事件監聽器,因為它們的內部邏輯是相同的。以下是融合後的程式碼,使用一個共用的函數來處理圖片滑鼠事件的邏輯:

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定義共用的處理函數
function handleImageHover(img) {
    if (img.tagName === 'IMG' && isLargeImage(img) && !iconVisible) {
        console.log('move');
        let position = getCoords(img);
        icon.style.left = `${position.left + 24}px`;
        icon.style.top = `${position.bottom - 24}px`;
        icon.style.display = 'block';
        currentImg = img;
        iconVisible = true;
    }
}

// Mouse move event on images
document.addEventListener('mouseover', (e) => {
    handleImageHover(e.target);
});

// Detect if URL has changed (works on Twitter)
window.navigation.addEventListener("navigate", (e) => {
    handleImageHover(e.target);
});

融合後的改進點:

  1. 提取了重複的邏輯到 handleImageHover 函數,避免程式碼重複

  2. 修正了模板字符串語法(使用正確的 ${} 包裹)

  3. 保持了原有的功能不變,只是組織方式更優雅

  4. 更容易維護和修改(只需改一個地方的邏輯)

注意事項:

  • 假設 isLargeImage、icon、currentImg 和 getCoords 等變數/函數已在其他地方定義

  • 如果您需要不同的偏移量(比如 mouseover 用 +24,而 navigate 用其他值),可以在函數中添加參數來處理


twitter 剛開始用是好的。但滾輪在往下捲到底,他自動載入之後,我的這個程式會失效

15 web pages

你的問題描述了在使用 Twitter 時,你的 JavaScript 程式在一開始運作正常,但當頁面滾動到底部並觸發 Twitter 的無限滾動(infinite scroll)自動載入新內容後,程式就失效了。這是一個常見的問題,因為 Twitter 的無限滾動會動態新增 DOM 元素,而你的程式可能只對初始載入的元素綁定了事件監聽器,沒有處理後續動態載入的內容。

根據你之前提供的程式碼,我假設你的程式是在監聽圖片的 mouseover 事件,並在特定條件下顯示一個圖標(icon)。當 Twitter 載入新內容時,新加入的圖片沒有被你的程式監聽,因此事件不會觸發。以下是可能的解決方案:


解決方法

1. 使用 MutationObserver 監聽 DOM 變化

Twitter 的無限滾動會動態添加新的 DOM 元素(例如新的圖片)。你可以使用 MutationObserver 來監測 DOM 的變化,並在新圖片加入時重新綁定事件。

以下是改進後的程式碼範例:

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 定義共用的處理函數
function handleImageHover(img) {
    if (img.tagName === 'IMG' && isLargeImage(img) && !iconVisible) {
        console.log('move');
        let position = getCoords(img);
        icon.style.left = `${position.left + 24}px`;
        icon.style.top = `${position.bottom - 24}px`;
        icon.style.display = 'block';
        currentImg = img;
        iconVisible = true;
    }
}

// 綁定事件到現有的圖片
function bindEventsToImages() {
    document.querySelectorAll('img').forEach(img => {
        img.addEventListener('mouseover', () => handleImageHover(img));
    });
}

// 初始綁定
bindEventsToImages();

// 使用 MutationObserver 監聽 DOM 變化
const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        if (mutation.addedNodes.length) {
            // 當有新節點加入時,重新綁定事件
            bindEventsToImages();
        }
    });
});

// 監聽 document.body 的子節點變化
observer.observe(document.body, {
    childList: true,
    subtree: true
});

// 對於 navigation 事件(URL 變化)
window.navigation.addEventListener("navigate", (e) => {
    handleImageHover(e.target);
});

說明:

  • MutationObserver 會監聽整個頁面的 DOM 變化。

  • 當 Twitter 載入新內容(例如新的圖片)時,bindEventsToImages 會重新執行,為新加入的圖片綁定 mouseover 事件。

  • 這樣即使頁面滾動到底部並載入新內容,你的程式也能繼續運作。


2. 事件委派(Event Delegation)

另一個更簡單的解決方法是使用事件委派。不是直接將事件綁定到每個圖片,而是將事件監聽器綁定到一個不會變動的父元素(例如 document),然後檢查事件目標是否為圖片。

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定義共用的處理函數
function handleImageHover(img) {
    if (img.tagName === 'IMG' && isLargeImage(img) && !iconVisible) {
        console.log('move');
        let position = getCoords(img);
        icon.style.left = `${position.left + 24}px`;
        icon.style.top = `${position.bottom - 24}px`;
        icon.style.display = 'block';
        currentImg = img;
        iconVisible = true;
    }
}

// 使用事件委派監聽所有 mouseover 事件
document.addEventListener('mouseover', (e) => {
    handleImageHover(e.target);
});

// 對於 navigation 事件(URL 變化)
window.navigation.addEventListener("navigate", (e) => {
    handleImageHover(e.target);
});

說明:

  • 事件委派利用事件冒泡機制,將監聽器放在 document 上。

  • 無論 Twitter 何時載入新圖片,只要觸發 mouseover 事件,程式都能捕捉到並處理。

  • 這比 MutationObserver 更輕量,因為不需要持續監測 DOM 變化。


3. 檢查失效的原因

如果上述方法仍無法解決問題,可能是因為:

  • 全局變數問題:檢查 icon、currentImg 和 iconVisible 是否在 Twitter 載入新內容後被重置或變得不可用。

  • Twitter 的 JavaScript 衝突:Twitter 可能在載入新內容時執行了自己的腳本,覆蓋了你的樣式或事件。你可以用瀏覽器的開發者工具(F12)檢查 icon 的樣式是否被改變。

你可以添加以下除錯程式碼來確認:

javascript

1
2
3
4
5
6
document.addEventListener('mouseover', (e) => {
    console.log('Target:', e.target);
    console.log('Icon Visible:', iconVisible);
    console.log('Icon Element:', icon);
    handleImageHover(e.target);
});

建議

  • 首選方案:使用「事件委派」(方法 2),因為它簡單高效,適用於動態內容。

  • 備用方案:如果需要更精確控制新載入的元素,可以使用 MutationObserver(方法 1)。

  • 測試:在 Twitter 上測試時,滾動到底部並等待新內容載入,確認圖標是否仍能正確顯示。

如果還有其他問題或需要進一步協助,請提供更多細節(例如完整的程式碼或失效時的具體行為),我會幫你調整!


你的程式碼整體運作良好,但你提到了一個問題:有時圖標(icon)會卡在某個圖片上不消失。這個問題可能與 mouseout 事件的觸發條件或邏輯有關。我檢查了你的程式碼,發現以下幾個可能的原因及解決方案:


問題分析

  1. mouseout 事件的條件問題:

    • 你在 mouseout 事件中檢查了 e.target === currentImg && e.relatedTarget !== icon,這意味著只有當滑鼠從 currentImg 移出且移到的目標不是 icon 時,才會隱藏圖標。

    • 如果滑鼠移動到一個不符合條件的元素(例如某些覆蓋層、子元素或非預期的目標),icon 不會隱藏,就會出現「卡住」的情況。

  2. 動態內容影響:

    • 在 Twitter 或其他動態網站上,圖片可能會因為頁面滾動或重新渲染而改變位置或結構,這可能導致 currentImg 的參考失效,或者 mouseout 事件未正確觸發。
  3. iconVisible 狀態未正確重置:

    • 如果某些情況下 iconVisible 沒有被設為 false,下次滑鼠移入新圖片時,handleImageHover 的條件 !iconVisible 會阻止圖標更新位置。

解決方案

以下是修復「圖標卡住」問題的改進版本程式碼,並針對 Twitter 的無限滾動問題使用事件委派:

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// ==UserScript==
// @name         Image Download4
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Add a download icon to images larger than 400px when hovered.
// @author       Your Name
// @match        *://*/*
// @run-at       document-idle
// @grant        GM_download
// ==/UserScript==

(function() {
    'use strict';

    // Create the download icon
    const icon = document.createElement('div');
    icon.textContent = '\u2B73'; // A simple arrow icon
    icon.style.position = 'absolute';
    icon.style.width = '24px';
    icon.style.height = '24px';
    icon.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
    icon.style.color = 'white';
    icon.style.fontSize = '18px';
    icon.style.textAlign = 'center';
    icon.style.lineHeight = '24px';
    icon.style.borderRadius = '50%';
    icon.style.cursor = 'pointer';
    icon.style.display = 'none';
    icon.style.zIndex = '10000';
    document.body.appendChild(icon);

    let currentImg = null;
    let iconVisible = false;

    // Function to check image size
    function isLargeImage(img) {
        return img.naturalWidth > 400 && img.naturalHeight > 400;
    }

    // Get document coordinates of the element
    function getCoords(elem) {
        let box = elem.getBoundingClientRect();
        return {
            top: box.top + window.pageYOffset,
            right: box.right + window.pageXOffset,
            bottom: box.bottom + window.pageYOffset,
            left: box.left + window.pageXOffset
        };
    }

    function handleImageHover(img) {
        if (img.tagName === 'IMG' && isLargeImage(img)) {
            // 如果已有圖標顯示,先隱藏並重置
            if (iconVisible && currentImg !== img) {
                icon.style.display = 'none';
                iconVisible = false;
            }

            if (!iconVisible) {
                let position = getCoords(img);
                icon.style.left = `${position.left + 24}px`;
                icon.style.top = `${position.bottom - 24}px`;
                icon.style.display = 'block';
                currentImg = img;
                iconVisible = true;
            }
        }
    }

    // Hide icon when leaving image or icon
    function hideIconIfNeeded(e) {
        const fromElement = e.target;
        const toElement = e.relatedTarget;

        // 如果離開的是當前圖片或圖標,且目標不再是圖片或圖標
        if ((fromElement === currentImg || fromElement === icon) &&
            toElement !== icon && toElement !== currentImg) {
            icon.style.display = 'none';
            currentImg = null;
            iconVisible = false;
        }
    }

    // Mouseover event delegation
    document.addEventListener('mouseover', (e) => {
        const img = e.target;
        handleImageHover(img);
    });

    // Mouseout event delegation
    document.addEventListener('mouseout', (e) => {
        hideIconIfNeeded(e);
    });

    // Handle navigation (URL change) for Twitter compatibility
    window.navigation?.addEventListener("navigate", (e) => {
        const img = e.target;
        handleImageHover(img);
    });

    // Download image on icon click
    icon.addEventListener('click', () => {
        if (currentImg) {
            const imageUrl = currentImg.src;
            const fileName = imageUrl.split('/').pop().split('?')[0];
            GM_download(imageUrl, fileName + '.jpg');
        }
    });
})();

改進點

  1. 修復圖標卡住問題:

    • 在 handleImageHover 中,新增了檢查:如果已有圖標顯示且當前圖片不是新目標圖片,先隱藏圖標並重置狀態。

    • 在 mouseout 事件中,改進了隱藏邏輯,檢查是否從圖片或圖標移出,並確保目標不再是圖片或圖標時才隱藏。

  2. 事件委派:

    • 使用 document.addEventListener(‘mouseover’, …) 和 document.addEventListener(‘mouseout’, …) 處理所有圖片的事件,即使 Twitter 動態載入新內容也能正常運作。
  3. 移除無用函數:

    • 移除了未使用的 getPosition 函數,保持程式碼簡潔。
  4. Twitter 兼容性:

    • 保留了 navigate 事件的處理,並添加了可選鏈運算符 ?. 以防止某些瀏覽器不支持 window.navigation 時報錯。
--

Comments powered by Disqus.