Unfix Fixed Elements

Reverses ill-conceived element fixing on sites like Medium.com

目前為 2019-05-17 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Unfix Fixed Elements
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Reverses ill-conceived element fixing on sites like Medium.com
// @author       alienfucker
// @match        *://*/*
// @grant        none
// @run-at       document_start
// ==/UserScript==

(function () {
    'use strict';
    const className = "anti-fixing"; // Odds of colliding with another class must be low

    class FixedWatcher {
        constructor() {
            this.watcher = new MutationObserver(this.onMutation.bind(this));
            this.elementTypes = ["div", "header", "footer", "nav"];
            this.awaitingTick = false;
            this.top = [];
            this.bottom = [];
        }

        start() {
            this.trackAll();
            this.watcher.observe(document, {
                childList: true,
                attributes: true,
                subtree: true,
                attributeFilter: ["class", "style"],
                attributeOldValue: true
            });
            window.addEventListener("scroll", this.onScroll.bind(this));
        }
        onScroll(){
            if(this.awaitingTick) return;
            this.awaitingTick = true;
            window.requestAnimationFrame(() => {
                const max = document.body.offsetHeight - window.innerHeight;

                for(const item of this.top){
                    item.className = item.el.className;
                    if(window.scrollY === 0){
                        item.el.classList.remove(className);
                    }else if(!item.el.classList.contains(className)){
                        item.el.classList.add(className);
                    }
                }

                for(const item of this.bottom){
                    item.className = item.el.className;
                    if(window.scrollY === max){
                        item.el.classList.remove(className);
                    }else if(!item.el.classList.contains(className)){
                        item.el.classList.add(className);
                    }
                }
                this.awaitingTick = false;
            })
        }
        onMutation(mutations) {
            for (let mutation of mutations) {
                if (mutation.type === "childList") {
                    for(let node of mutation.removedNodes)
                        this.untrack(node)
                    for (let node of mutation.addedNodes) {
                        if (node.nodeType !== Node.ELEMENT_NODE) continue;

                        if (this.elementTypes.findIndex(selector => node.matches(selector)) !== -1) this.track(node);
                        node.querySelectorAll(this.elementTypes.join(",")).forEach(el => this.track(el));
                    }
                } else if (mutation.type === "attributes") {
                    if (this.friendlyMutation(mutation)) continue;


                    if (this.elementTypes.findIndex(selector => mutation.target.matches(selector)) !== -1) {
                        this.track(mutation.target);
                    }
                }
            }

        }

        friendlyMutation(mutation){ // Mutation came from us
            if(mutation.attributeName === "class"){
                if(this.top.findIndex(({el, className}) => el === mutation.target && className === mutation.oldValue) !== -1) return true;
                if(this.bottom.findIndex(({el, className}) => el === mutation.target && className === mutation.oldValue) !== -1) return true;
            }
            return false;
        }
        untrack(_el){
            let i = this.top.findIndex(({el}) => el.isSameNode(_el) || _el.contains(el));
            if(i !== -1) return !!this.top.splice(i, 1);
            i = this.bottom.findIndex(({el}) => el.isSameNode(_el) || _el.contains(el));
            if(i !== -1) return !!this.bottom.splice(i, 1);
            return false;
        }
        trackAll(){
            const els = document.querySelectorAll(this.elementTypes.join(","));
            for(const el of els)
                this.track(el);
        }
        isAutoBottom(style){
            if(style.bottom === "auto") return true;

            const height = parseInt(style.height, 10);
            const bottom = parseInt(style.bottom, 10);
            const top = parseInt(style.top, 10);
            return Math.abs((bottom + height) - window.innerHeight + top) < 2
        }
        isAutoTop(style){
            if(style.top === "auto") return true;

            const height = parseInt(style.height, 10);
            const bottom = parseInt(style.bottom, 10);
            const top = parseInt(style.top, 10);
            return Math.abs(top - (window.innerHeight - height) + bottom) < 2;
        }
        track(el){

            const style = window.getComputedStyle(el);

            if (style.position === "fixed") {
                if((style.top === "0px" || style.top.indexOf("-") === 0) && this.isAutoBottom(style) && this.top.findIndex(({el: _el}) => _el === el) === -1){
                    this.top.push({el, className: el.className});
                    this.onScroll();
                }else if((style.bottom === "0px" || style.bottom.indexOf("-") === 0) && this.isAutoTop(style) && this.bottom.findIndex(({el: _el}) => _el === el) === -1){
                    this.bottom.push({el, className: el.className});
                    this.onScroll();
                }
            }
        }
        unFix(el) {
            if (el.offsetHeight !== window.innerHeight && !el.classList.contains(className)) {
                el.classList.add(className);
            }
        }

        stop() {
            this.watcher.disconnect();
            window.removeEventListener("scroll", this.onScroll);
        }

        restore() {
            let els = document.querySelectorAll("." + className);
            for (let el of els) {
                el.classList.remove(className);
            }
        }

    }

    document.documentElement.appendChild((() => {
        let el = document.createElement("style");
        el.setAttribute("type", "text/css");
        el.appendChild(document.createTextNode(`.${className}{ display: none !important }`));
        //el.appendChild(document.createTextNode(`.${className}{ position: static !important }`));
        return el;
    })())

    const fixer = new FixedWatcher();
    fixer.start();

    // Make globally accessible, for debugging purposes
    window.fixer = fixer;
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址