ColaManga 瀏覽增強

隱藏廣告內容,提昇瀏覽體驗。自訂背景顏色,圖片大小調整。當圖片載入失敗時,自動重新載入圖片。提供熱鍵功能:[← 上一頁]、[下一頁 →]、[↑ 自動上滾動]、[↓ 自動下滾動]。當用戶滾動到頁面底部時,自動跳轉到下一頁。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         ColaManga 瀏覽增強
// @name:zh-TW   ColaManga 瀏覽增強
// @name:zh-CN   ColaManga 浏览增强
// @name:en      ColaManga Browsing Enhance
// @version      2025.09.21-Beta
// @author       Canaan HS
// @description       隱藏廣告內容,提昇瀏覽體驗。自訂背景顏色,圖片大小調整。當圖片載入失敗時,自動重新載入圖片。提供熱鍵功能:[← 上一頁]、[下一頁 →]、[↑ 自動上滾動]、[↓ 自動下滾動]。當用戶滾動到頁面底部時,自動跳轉到下一頁。
// @description:zh-TW 隱藏廣告內容,提昇瀏覽體驗。自訂背景顏色,圖片大小調整。當圖片載入失敗時,自動重新載入圖片。提供熱鍵功能:[← 上一頁]、[下一頁 →]、[↑ 自動上滾動]、[↓ 自動下滾動]。當用戶滾動到頁面底部時,自動跳轉到下一頁。
// @description:zh-CN 隐藏广告内容,提昇浏览体验。自定义背景颜色,调整图片大小。当图片载入失败时,自动重新载入图片。提供快捷键功能:[← 上一页]、[下一页 →]、[↑ 自动上滚动]、[↓ 自动下滚动]。当用户滚动到页面底部时,自动跳转到下一页。
// @description:en    Hide advertisement content, enhance browsing experience. Customize background color, adjust image size. Automatically reload images when they fail to load. Provide shortcut key functionalities: [← Previous Page], [Next Page →], [↑ Auto Scroll Up], [↓ Auto Scroll Down]. Automatically jump to the next page when users scroll to the bottom of the page.

// @match        *://www.colamanga.com/manga-*/
// @match        *://www.colamanga.com/manga-*/*/*.html
// @icon         https://www.colamanga.com/favicon.png

// @license      MPL-2.0
// @namespace    https://greasyfork.org/users/989635
// @supportURL   https://github.com/Canaan-HS/MonkeyScript/issues

// @require      https://update.greasyfork.org/scripts/487608/1661432/SyntaxLite_min.js

// @grant        GM_setValue
// @grant        GM_getValue

// @run-at       document-start
// ==/UserScript==

(function () {
    /* 臨時的自定義 (當 Enable = false 時, 其餘的設置將無效) */
    const Config = {
        BGColor: {
            Enable: true,
            Color: "#595959",
        },
        AutoTurnPage: { // 自動翻頁
            Enable: true,
            Mode: 3, // 1 = 快速 | 2 = 一般無盡 | 3 = 優化無盡
        },
        RegisterHotkey: { // 快捷功能
            Enable: true,
            Function: { // 移動端不適用以下配置
                TurnPage: true, // 翻頁
                AutoScroll: true, // 自動滾動
                KeepScroll: true, // 換頁繼續滾動
                ManualScroll: false, // 手動滾動啟用時, 將會變成點擊一次, 根據視點翻一頁 且 自動滾動會無效
            }
        }
    };
    const Control = {
        ScrollPixels: 2,
        WaitPicture: 1e3,
        BlockListener: new Set(["auxclick", "mousedown", "pointerup", "pointerdown", "dState", "touchstart", "unhandledrejection"]),
        IdList: {
            Title: "CME_Title",
            Iframe: "CME_Iframe",
            Block: "CME_Block-Ads",
            Menu: "CME_Menu-Style",
            Image: "CME_Image-Style",
            Scroll: "CME_Scroll-Hidden",
            ChildS: "CME_Child-Scroll-Hidden"
        }
    };
    const Param = {
        Body: null,
        ContentsPage: null,
        HomePage: null,
        PreviousLink: null,
        NextLink: null,
        MangaList: null,
        BottomStrip: null,
        Up_scroll: false,
        Down_scroll: false,
        IsFinalPage: false,
        IsMangaPage: Lib.$url.endsWith("html"),
        IsMainPage: window.self === window.parent
    };
    (async () => {
        if (!Param.IsMangaPage) return;
        Lib.addStyle(`
        html {pointer-events: none !important;}
        div[style*='position'] {display: none !important;}
        .mh_wrap a,
        .mh_readend a,
        span.mh_btn:not(.contact),
        #${Control.IdList.Iframe} {
            pointer-events: auto !important;
        }
    `, Control.IdList.Block);
        const OriginListener = EventTarget.prototype.addEventListener;
        const Block = Control.BlockListener;
        EventTarget.prototype.addEventListener = new Proxy(OriginListener, {
            apply(target, thisArg, args) {
                const [type, listener, options] = args;
                if (Block.has(type)) return;
                return target.apply(thisArg, args);
            }
        });
        const iframe = `iframe:not(#${Control.IdList.Iframe})`;
        const AdCleanup = () => {
            Lib.$qa(iframe).forEach(ad => ad.remove());
            Lib.body?.$qa("script").forEach(ad => ad.remove());
            requestIdleCallback(AdCleanup, {
                timeout: 300
            });
        };
        AdCleanup();
    })();
    const Tools = (() => {
        const idWhiteList = new Set(Object.values(Control.IdList));
        const storage = (key, value = null) => {
            return value != null ? Lib.session(key, {
                value: value
            }) : Lib.session(key);
        };
        const topDetected = Lib.$throttle(() => {
            Param.Up_scroll = Lib.sY == 0 ? (storage("scroll", false), false) : true;
        }, 1e3);
        const isTheBottom = () => Lib.sY + Lib.iH >= document.documentElement.scrollHeight;
        const detectSkip = Config.RegisterHotkey.Function.KeepScroll && Config.AutoTurnPage.Mode === 1;
        const bottomDetected = Lib.$throttle(() => {
            if (detectSkip) return;
            Param.Down_scroll = isTheBottom() ? (storage("scroll", false), false) : true;
        }, 1e3);
        return {
            storage: storage,
            getSet: () => {
                return Lib.getV("Style", {
                    BG_Color: "#595959",
                    Img_Bw: "auto",
                    Img_Mw: "100%"
                });
            },
            getNodes(root) {
                const nodes = [];
                function task(root2) {
                    const tree = document.createTreeWalker(root2, NodeFilter.SHOW_ELEMENT, {
                        acceptNode: node => {
                            if (idWhiteList.has(node.id)) {
                                return NodeFilter.FILTER_REJECT;
                            }
                            return NodeFilter.FILTER_ACCEPT;
                        }
                    });
                    while (tree.nextNode()) {
                        nodes.push(tree.currentNode);
                    }
                }
                task(root.head);
                task(root.body);
                return nodes;
            },
            autoScroll(move) {
                requestAnimationFrame(() => {
                    if (Param.Up_scroll && move < 0) {
                        window.scrollBy(0, move);
                        topDetected();
                        this.autoScroll(move);
                    } else if (Param.Down_scroll && move > 0) {
                        window.scrollBy(0, move);
                        bottomDetected();
                        this.autoScroll(move);
                    }
                });
            },
            manualScroll(move) {
                window.scrollBy({
                    left: 0,
                    top: move,
                    behavior: "smooth"
                });
            },
            isFinalPage(link) {
                Param.IsFinalPage = link.startsWith("javascript");
                return Param.IsFinalPage;
            },
            visibleObjects: object => object.filter(img => img.height > 0 || img.src),
            lastObject: object => {
                const len = object.length;
                if (len <= 5) return object[0];
                if (len <= 10) return object.at(-2) ?? object[0];
                return object.at(-3) ?? object[0];
            },
            detectionValue(object) {
                return this.visibleObjects(object).length >= Math.floor(object.length * .5);
            }
        };
    })();
    const Style = (() => {
        const $Set = Tools.getSet();
        return {
            async backgroundStyle(Color = Config.BGColor.Color) {
                Param.Body.style.cssText = `
                background: ${Color} !important;
            `;
                document.documentElement.style.cssText = `
                overflow: visible !important;
            `;
            },
            async pictureStyle() {
                if (Lib.platform === "Desktop") {
                    Lib.addStyle(`
                    .mh_comicpic img {
                        margin: auto;
                        display: block;
                        cursor: pointer;
                        vertical-align: top;
                        width: ${$Set.Img_Bw};
                        max-width: ${$Set.Img_Mw};
                    }
                `, Control.IdList.Image);
                }
                setTimeout(() => {
                    const click = new MouseEvent("click", {
                        bubbles: true,
                        cancelable: true
                    });
                    const observer = new IntersectionObserver(observed => {
                        observed.forEach(entry => {
                            if (entry.isIntersecting) {
                                entry.target.dispatchEvent(click);
                            }
                        });
                    }, {
                        threshold: .3
                    });
                    Param.MangaList.$qa("span.mh_btn:not(.contact):not(.read_page_link)").forEach(reloadBtn => observer.observe(reloadBtn));
                }, Control.WaitPicture);
            },
            async menuStyle() { }
        };
    })();
    const Hotkey = async () => {
        let jumpState = false;
        if (Lib.platform === "Desktop") {
            const {
                TurnPage,
                AutoScroll,
                KeepScroll,
                ManualScroll
            } = Config.RegisterHotkey.Function;
            if (Param.IsMainPage && KeepScroll && AutoScroll && !ManualScroll) {
                Param.Down_scroll = Tools.storage("scroll");
                Param.Down_scroll && Tools.autoScroll(Control.ScrollPixels);
            }
            const UP_ScrollSpeed = -Control.ScrollPixels;
            const CanScroll = AutoScroll || ManualScroll;
            Lib.onEvent(window, "keydown", event => {
                const key = event.key;
                if (key === "ArrowLeft" && TurnPage && !jumpState) {
                    event.stopImmediatePropagation();
                    jumpState = !Tools.isFinalPage(Param.PreviousLink);
                    location.assign(Param.PreviousLink);
                } else if (key === "ArrowRight" && TurnPage && !jumpState) {
                    event.stopImmediatePropagation();
                    jumpState = !Tools.isFinalPage(Param.NextLink);
                    location.assign(Param.NextLink);
                } else if (key === "ArrowUp" && CanScroll) {
                    event.stopImmediatePropagation();
                    event.preventDefault();
                    if (ManualScroll) {
                        Tools.manualScroll(-Lib.iH);
                    } else {
                        if (Param.Up_scroll) {
                            Param.Up_scroll = false;
                        } else if (!Param.Up_scroll || Param.Down_scroll) {
                            Param.Down_scroll = false;
                            Param.Up_scroll = true;
                            Tools.autoScroll(UP_ScrollSpeed);
                        }
                    }
                } else if (key === "ArrowDown" && CanScroll) {
                    event.stopImmediatePropagation();
                    event.preventDefault();
                    if (ManualScroll) {
                        Tools.manualScroll(Lib.iH);
                    } else {
                        if (Param.Down_scroll) {
                            Param.Down_scroll = false;
                            Tools.storage("scroll", false);
                        } else if (Param.Up_scroll || !Param.Down_scroll) {
                            Param.Up_scroll = false;
                            Param.Down_scroll = true;
                            Tools.storage("scroll", true);
                            Tools.autoScroll(Control.ScrollPixels);
                        }
                    }
                }
            }, {
                capture: true
            });
        } else if (Lib.platform === "Mobile") {
            let startX, startY, moveX, moveY;
            const sidelineX = Lib.iW * .3;
            const sidelineY = Lib.iH / 4 * .3;
            Lib.onEvent(window, "touchstart", event => {
                startX = event.touches[0].clientX;
                startY = event.touches[0].clientY;
            }, {
                passive: true
            });
            Lib.onEvent(window, "touchmove", Lib.$debounce(event => {
                moveY = event.touches[0].clientY - startY;
                if (Math.abs(moveY) < sidelineY) {
                    moveX = event.touches[0].clientX - startX;
                    if (moveX > sidelineX && !jumpState) {
                        jumpState = !Tools.isFinalPage(Param.PreviousLink);
                        location.assign(Param.PreviousLink);
                    } else if (moveX < -sidelineX && !jumpState) {
                        jumpState = !Tools.isFinalPage(Param.NextLink);
                        location.assign(Param.NextLink);
                    }
                }
            }, 60), {
                passive: true
            });
        }
    };
    const PageTurn = async () => {
        const turnMode = Config.AutoTurnPage.Mode;
        const optimized = turnMode === 3;
        async function unlimited() {
            Lib.addStyle(`
            .mh_wrap, .mh_readend, .mh_footpager,
            .fed-foot-info, #imgvalidation2022 {display: none;}
            body {
                margin: 0;
                padding: 0;
            }
            #${Control.IdList.Iframe} {
                margin: 0;
                padding: 0;
                width: 100%;
                height: 110vh;
                border: none;
            }
        `, Control.IdList.Scroll);
            const stylelRules = Lib.$q(`#${Control.IdList.Scroll}`).sheet.cssRules;
            if (Param.IsMainPage) {
                let size = 0;
                Lib.onEvent(window, "message", event => {
                    const data = event.data;
                    if (data && data.length > 0) {
                        const {
                            Title,
                            PreviousUrl,
                            CurrentUrl,
                            NextUrl,
                            Resize,
                            SizeSet,
                            SizeRecord
                        } = data[0];
                        if (Resize) {
                            if (size > SizeRecord) size -= SizeRecord;
                            size += Resize;
                            stylelRules[2].style.height = `${size}px`;
                        } else if (SizeSet) stylelRules[2].style.height = `${SizeSet}px`; else if (Title && NextUrl && PreviousUrl && CurrentUrl) {
                            document.title = Title;
                            Param.NextLink = NextUrl;
                            Param.PreviousLink = PreviousUrl;
                            history.pushState(null, null, CurrentUrl);
                        }
                    }
                });
            } else {
                Lib.addStyle(`
                html {
                    overflow: hidden !important;
                    overflow-x: hidden !important;
                    scrollbar-width: none !important;
                    -ms-overflow-style: none !important;
                }
                html::-webkit-scrollbar {
                    display: none !important;
                }
            `, Control.IdList.ChildS);
                let mainWindow = window;
                Lib.onEvent(window, "message", event => {
                    while (mainWindow.parent !== mainWindow) {
                        mainWindow = mainWindow.parent;
                    }
                    mainWindow.postMessage(event.data, Lib.$origin);
                });
            }
            const iframe = Lib.createElement("iframe", {
                id: Control.IdList.Iframe,
                src: Param.NextLink
            });
            (async () => {
                let img, Observer, quantity = 0;
                const observerNext = new IntersectionObserver(observed => {
                    observed.forEach(entry => {
                        const rect = entry.boundingClientRect;
                        const isPastTarget = rect.bottom < 0;
                        const isIntersecting = entry.isIntersecting;
                        if ((isIntersecting || isPastTarget) && Tools.detectionValue(img)) {
                            observerNext.disconnect();
                            Observer.disconnect();
                            turnPage();
                        }
                    });
                }, {
                    threshold: [0, .1, .5],
                    rootMargin: "0px 0px 200px 0px"
                });
                setTimeout(() => {
                    img = Param.MangaList.$qa("img");
                    if (img.length <= 5) {
                        turnPage();
                        return;
                    }
                    const lastImg = Tools.lastObject(Tools.visibleObjects(img));
                    lastImg instanceof Element && observerNext.observe(lastImg);
                    Lib.$observer(Param.MangaList, () => {
                        const visible = Tools.visibleObjects(img);
                        const vlen = visible.length;
                        if (vlen > quantity) {
                            quantity = vlen;
                            const lastImg2 = Tools.lastObject(visible);
                            if (lastImg2 instanceof Element) {
                                observerNext.disconnect();
                                observerNext.observe(lastImg2);
                            }
                        }
                    }, {
                        debounce: 100,
                        attributeFilter: ["src"]
                    }, observer => {
                        Observer = observer.ob;
                    });
                }, Control.WaitPicture);
            })();
            let turned = false;
            function turnPage() {
                if (turned) return;
                turned = true;
                let currentHeight = 0;
                const resizeObserver = new ResizeObserver(() => {
                    if (!Param.MangaList.isConnected) {
                        resizeObserver.disconnect();
                        return;
                    }
                    const newHeight = Param.MangaList.offsetHeight;
                    if (newHeight > currentHeight) {
                        window.parent.postMessage([{
                            Resize: newHeight,
                            SizeRecord: currentHeight
                        }], Lib.$origin);
                        currentHeight = newHeight;
                    }
                });
                if (Tools.isFinalPage(Param.NextLink)) {
                    if (optimized) {
                        window.parent.postMessage([{
                            SizeSet: Param.MangaList.offsetHeight + 245
                        }], Lib.$origin);
                    }
                    stylelRules[0].style.display = "block";
                    return;
                }
                waitLoad();
                Param.Body.appendChild(iframe);
                resizeObserver.observe(Param.MangaList);
                function waitLoad() {
                    let iframeWindow, currentUrl, content, allImg;
                    const failed = () => {
                        iframe.offAll();
                        iframe.src = "";
                        setTimeout(() => {
                            iframe.src = Param.NextLink;
                            waitLoad();
                        });
                    };
                    const success = () => {
                        iframe.offAll();
                        iframeWindow = iframe.contentWindow;
                        currentUrl = iframeWindow.location.href;
                        if (currentUrl !== Param.NextLink) {
                            failed();
                            return;
                        }
                        content = iframeWindow.document;
                        content.body.style.overflow = "hidden";
                        Lib.log(currentUrl, {
                            group: "無盡翻頁"
                        });
                        allImg = content.$qa("#mangalist img");
                        const urlUpdate = new IntersectionObserver(observed => {
                            observed.forEach(entry => {
                                if (entry.isIntersecting) {
                                    urlUpdate.disconnect();
                                    const PageLink = content.body.$qa("div.mh_readend ul a");
                                    window.parent.postMessage([{
                                        Title: content.title,
                                        CurrentUrl: currentUrl,
                                        PreviousUrl: PageLink[0]?.href,
                                        NextUrl: PageLink[2]?.href
                                    }], Lib.$origin);
                                }
                            });
                        }, {
                            threshold: 0
                        });
                        allImg.forEach(img => urlUpdate.observe(img));
                        if (optimized) {
                            Lib.$q("title").id = Control.IdList.Title;
                            const adapt = Lib.platform === "Desktop" ? .5 : .7;
                            const releaseMemory = new IntersectionObserver(observed => {
                                observed.forEach(entry => {
                                    if (entry.isIntersecting) {
                                        const targetImg = entry.target;
                                        const ratio = Math.min(adapt, Lib.iH * adapt / targetImg.clientHeight);
                                        if (entry.intersectionRatio >= ratio) {
                                            releaseMemory.disconnect();
                                            Tools.getNodes(document).forEach(node => {
                                                node.remove();
                                            });
                                            targetImg.scrollIntoView();
                                        }
                                    }
                                });
                            }, {
                                threshold: [0, .5, 1]
                            });
                            allImg.forEach(img => releaseMemory.observe(img));
                        }
                    };
                    iframe.on("load", success);
                    iframe.on("error", failed);
                }
            }
        }
        switch (turnMode) {
            case 2:
            case 3:
                unlimited();
                break;

            default:
                setTimeout(() => {
                    const img = Param.MangaList.$qa("img");
                    if (!Tools.isFinalPage(Param.NextLink)) {
                        const observerNext = new IntersectionObserver(observed => {
                            observed.forEach(entry => {
                                if (entry.isIntersecting && Tools.detectionValue(img)) {
                                    observerNext.disconnect();
                                    location.assign(Param.NextLink);
                                }
                            });
                        }, {
                            threshold: 1
                        });
                        observerNext.observe(Param.BottomStrip);
                    }
                }, Control.WaitPicture);
        }
    };
    function Main(raf = void 0) {
        async function mangaPageInit(callback) {
            Lib.waitEl(["body", "div.mh_readtitle", "div.mh_headpager", "div.mh_readend", "#mangalist"], null, {
                raf: raf,
                throttle: 30,
                timeout: 10,
                visibility: Param.IsMainPage,
                timeoutResult: true
            }).then(([Body, Title, HeadPager, Readend, Manga]) => {
                Param.Body = Body;
                const HomeLink = Title.$qa("a");
                Param.ContentsPage = HomeLink[0].href;
                Param.HomePage = HomeLink[1].href;
                try {
                    const PageLink = Readend.$qa("ul a");
                    Param.PreviousLink = PageLink[0].href;
                    Param.NextLink = PageLink[2].href;
                } catch {
                    const PageLink = HeadPager.$qa("a.mh_btn:not(.mh_bgcolor)");
                    Param.PreviousLink = PageLink[0].href;
                    Param.NextLink = PageLink[1].href;
                }
                Param.MangaList = Manga;
                Param.BottomStrip = Readend.$q(".endtip2");
                if ([Param.Body, Param.ContentsPage, Param.HomePage, Param.PreviousLink, Param.NextLink, Param.MangaList, Param.BottomStrip].every(Check => Check)) callback(true); else callback(false);
            });
        }
        async function contentsPageInit() {
            Lib.waitEl([".all_data_list", ".website-display-all"], ([list, display]) => {
                if (list.style.height === "auto") return;
                display.click();
            }, {
                raf: raf
            });
        }
        try {
            if (Param.IsMangaPage) {
                mangaPageInit(state => {
                    if (state) {
                        Style.pictureStyle();
                        Config.BGColor.Enable && Style.backgroundStyle();
                        Config.AutoTurnPage.Enable && PageTurn();
                        Config.RegisterHotkey.Enable && Hotkey();
                    } else {
                        Lib.log("Manga Page Init Error").error;
                        setTimeout(() => Main(true), 1e3);
                    }
                });
            } else contentsPageInit();
        } catch (error) {
            Lib.log(error).error;
        }
    }
    Main();
})();