Reverses ill-conceived element fixing on sites like Medium.com
目前為
// ==UserScript==
// @name Unfix Fixed Elements
// @namespace http://tampermonkey.net/
// @version 0.4
// @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.onScroll();
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 el of this.top){
if(window.scrollY === 0){
el.classList.remove(className);
}else if(!el.classList.contains(className)){
el.classList.add(className);
}
}
for(const el of this.bottom){
if(window.scrollY === max){
el.classList.remove(className);
}else if(!el.classList.contains(className)){
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) ? console.log('untracking', node, mutation) : "";
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.indexOf(mutation.target) !== -1) return true;
if(this.bottom.indexOf(mutation.target) !== -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){
const height = parseInt(style.height, 10);
const bottom = parseInt(style.bottom, 10);
const top = parseInt(style.top, 10);
return style.bottom === "auto" || Math.abs((bottom + height) - window.innerHeight + top) < 2
}
isAutoTop(style){
const height = parseInt(style.height, 10);
const bottom = parseInt(style.bottom, 10);
const top = parseInt(style.top, 10);
return style.top === "auto" || 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.indexOf(el) === -1){
this.top.push(el);
}else if((style.bottom === "0px" || style.bottom.indexOf("-") === 0) && this.isAutoTop(style) && this.bottom.indexOf(el) === -1){
this.bottom.push(el);
}
}
}
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;
})()