chrome 插件之 tampermonkey

Tampermonkey

这个插件类似 Firefox 上的 GreaseMonkey,插件地址为:https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo

插件使用帮助文档:https://tampermonkey.net/documentation.php

页面加载初始化操作

下面是在页面显示之前执行的脚本,其中的部分注释说明:

  1. @name就是当前脚本的名字
  2. @run-at可以控制脚本在页面不同阶段中注入进来
  3. @match的说明可查看 Google 浏览器路径匹配规则文档
  4. @grant授权是为当前的脚本授权一些高级的 API 方法调用,如window.close()方法
// ==UserScript==// @name         document start// @namespace    http://tampermonkey.net/// @version      0.1// @author       test.yu// @match        http*://*/*// @run-at       document-start// @grant        none// ==/UserScript==(function() {    // 在页面载入前,先将整个页面完全置为不可见,并且隐藏滚动条    // 避免看到页面原来的布局被改写的页面渲染过程    document.head.insertAdjacentHTML('beforeend', '<style id="tampermonkey-hide-body">body {visibility: hidden; overflow: hidden;} aside {display: none;}</style>');    var fn = function() {        document.head.querySelector('#tampermonkey-hide-body').remove();    };    // 有些页面有js问题,不能触发下面的 DOMContentLoaded 事件,所以加了一个补救的 setTimeout 方法恢复页面    var timeId = setTimeout(fn, 3000);    document.addEventListener('DOMContentLoaded', function(){        clearTimeout(timeId);        setTimeout(fn, 50);    }, false);})();

简单的操作 DOM 的全局方法

document增加了一个$方法,返回的内容与document.querySelectorAll(selector)方法返回的一样,只是原生的方法返回的是NodeList,而document.$(selector)方法返回的是一个强化版的Array,这个返回对象添加了一些 DOM 的操作方法,方法使用类似jquery的API,jquery是添加在window对象上的,而这个$是添加在document对象上的。

// ==UserScript==// @name         d.js// @namespace    http://tampermonkey.net/// @version      0.2// @author       test.yu// @match        http*://*/*// @run-at       document-start// @grant        none// ==/UserScript==(function() {    var element = {        siblings: function() {            return [...this.parentNode.children].filter(c => c.nodeType == 1 && c != this);        },        attr: function(attributes) {            if (this.nodeType === 3 || this.nodeType === 8 || this.nodeType === 2) {                return this;            }            if (typeof attributes === 'string') {                return this.getAttribute(attributes);            } else {                for (var key in attributes) {                    this.setAttribute(key, attributes[key]);                }                return this;            }        },        css: function(styles) {            if (this.nodeType === 1) {                for (var key in styles) {                    this.style[key] = styles[key];                }            }            return this;        },        parents: function() {            var matched = [],                elem = this;            while (elem) {                if (elem.nodeType === 9) {                    break;                }                if (elem.nodeType === 1) {                    if (matched.indexOf(elem) === -1) {                        matched.push(elem);                    }                }                elem = elem.parentNode;            }            return matched;        },        hasClass: function(selector) {            var classes = " " + (this.getAttribute("class") || "") + " ",                className = " " + selector + " ";            if (this.nodeType === 1 && classes.indexOf(className) > -1) {                return true;            }            return false;        },        addClass: function(selector) {            if (!element.hasClass.apply(this, arguments)) {                var classes = this.getAttribute("class") || "",                    className = classes + " " + selector + " ";                if (this.nodeType === 1) {                    this.setAttribute('class', className.trim());                }            }            return this;        },        removeClass: function(selector) {            if (element.hasClass.apply(this, arguments)) {                var classes = " " + (this.getAttribute("class") || "") + " ",                    className = " " + selector + " ";                if (this.nodeType === 1) {                    while (classes.indexOf(className) > -1) {                        classes = classes.replace(className, " ");                    }                    this.setAttribute('class', classes.trim());                }            }            return this;        },        hide: function() {            this.style.display = 'none';            return this;        },        show: function() {            this.style.display = 'block';            return this;        },        after: function(elem) {            if (this.parentNode) {                this.parentNode.insertBefore(elem, this.nextSibling);            }            return this;        },        before: function(elem) {            if (this.parentNode) {                this.parentNode.insertBefore(elem, this);            }            return this;        },        empty: function() {            if (this.nodeType === 1) {                this.textContent = "";            }            return this;        },        html: function() {            if (this.nodeType === 1) {                if (arguments.length) {                    this.innerHTML = arguments[0];                } else {                    return this.innerHTML;                }            }            return this;        },        prepend: function(nodes) {            if (nodes) {                if (nodes instanceof Array) {                    nodes.forEach(elem => this.insertBefore(elem, this.firstElementChild));                } else {                    $(nodes).forEach(elem => this.insertBefore(elem, this.firstElementChild));                }            }            return this;        },        append: function(nodes) {            if (nodes) {                if (nodes instanceof Array) {                    nodes.forEach(elem => this.appendChild(elem));                } else {                    $(nodes).forEach(elem => this.appendChild(elem));                }            }            return this;        }    };    // transform object of Node/NodeList/Array to enhanced Array instance    var $ = function(array) {        var attr = {};        if (!arguments.length) {            array = [];        } else if (array instanceof Node) {            array = [array];        } else if (array instanceof NodeList) {            array = [...array];        } else if (array instanceof HTMLCollection) {            array = [...array];        }        ['filter', 'map', 'slice', 'sort'].forEach(function(method) {            attr[method] = {                value: function() {                    var array = Array.prototype[method].apply(this, arguments);                    return $(array);                },                enumerable: false            };        });        ("blur focus click mouseover mouseenter mouseleave " +            "change select submit keydown keypress keyup").split(" ").forEach(function(event) {            attr[event] = {                value: function() {                    this.forEach(elem => {                        if (arguments.length) {                            [...arguments].forEach((fn) => {                                elem.addEventListener(event, fn, false);                            });                        } else {                            elem[event]();                        }                    });                    return this;                },                enumerable: false            };        });        attr.first = {            value: function() {                var array = Array.prototype.slice.call(this, 0, 1);                return $(array);            },            enumerable: false        };        attr.last = {            value: function() {                var array = Array.prototype.slice.call(this, this.length - 1, this.length);                return $(array);            },            enumerable: false        };        attr.remove = {            value: function() {                this.forEach(function(elem) {                    if (elem.nodeType === 1) {                        elem.remove();                    }                });                return this;            },            enumerable: false        };        attr.siblings = {            value: function() {                var array = [];                this.forEach(function(elem) {                    array = array.concat(element.siblings.apply(elem, arguments));                });                return $(array);            },            enumerable: false        };        attr.parent = {            value: function() {                var array = [];                this.forEach(elem => {                    if (elem) {                        array.push(elem.parentNode);                    } else {                        array.push(null);                    }                });                return $(array);            },            enumerable: false        };        attr.parents = {            value: function() {                var array = [];                this.forEach(function(elem) {                    element.parents.apply(elem, arguments).forEach(function(p) {                        if (array.indexOf(p) === -1) {                            array.push(p);                        }                    });                });                return $(array);            },            enumerable: false        };        attr.closest = {            value: function(selector) {                var array = [];                this.forEach(elem => array.push(elem.closest(selector)));                return $(array);            },            enumerable: false        };        attr.hasClass = {            value: function() {                for (var i = 0; i < this.length; i++) {                    if (element.hasClass.apply(this[i], arguments)) {                        return true;                    }                }                return false;            },            enumerable: false        };        attr.addClass = {            value: function() {                this.forEach(elem => element.addClass.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.removeClass = {            value: function() {                this.forEach(elem => element.removeClass.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.attr = {            value: function() {                if (arguments.length) {                    if (typeof arguments[0] === 'string') {                        var values = [];                        this.forEach(elem => values.push(element.attr.apply(elem, arguments)));                        if (values.length === 1) {                            return values[0];                        }                        return values;                    } else {                        this.forEach(elem => element.attr.apply(elem, arguments));                    }                }                return this;            },            enumerable: false        };        attr.css = {            value: function() {                this.forEach(elem => element.css.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.hide = {            value: function() {                this.forEach(elem => element.hide.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.show = {            value: function() {                this.forEach(elem => element.show.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.after = {            value: function() {                this.first().forEach(elem => element.after.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.before = {            value: function() {                this.first().forEach(elem => element.before.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.empty = {            value: function() {                this.forEach(elem => element.empty.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.html = {            value: function() {                if (arguments.length) {                    this.forEach(elem => element.html.apply(elem, arguments));                } else {                    return this.map(elem => element.html.apply(elem, arguments));                }                return this;            },            enumerable: false        };        attr.prepend = {            value: function() {                this.first().forEach(elem => element.prepend.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.append = {            value: function() {                this.first().forEach(elem => element.append.apply(elem, arguments));                return this;            },            enumerable: false        };        attr.querySelectorAll = {            value: function(selector) {                if (typeof selector === 'string') {                    var array = [];                    this.forEach(function(elem) {                        var subs = elem.querySelectorAll(selector);                        array.push(...subs);                    });                    return $(array);                } else {                    return $(selector);                }            },            enumerable: false        };        attr.tee = {            value: function() {                console.log(...this);                return this;            },            enumerable: false        };        attr.readable = {            value: function() {                if (this.length === 0) return;                var parents = this.parents();                this.forEach(function(elem) {                    while (elem && elem.tagName.toUpperCase() !== 'BODY') {                        var $elem = $(elem);                        $elem.css({                            padding: 0,                            margin: '0 auto',                            display: 'block',                            width: '100%',                            maxWidth: '1024px',                            position: 'static'                        });                        $elem.siblings().filter(function(elem) {                            if (elem.tagName.toUpperCase() === 'LINK') {                                return false;                            }                            if (parents.indexOf(elem) > -1) {                                return false;                            }                            return true;                        }).remove();                        elem = elem.parentNode;                    }                });                $(this).css({                    padding: '15px',                    boxSizing: 'border-box'                });                document.head.querySelectorAll('script').$.remove();                $(document.head).append(document.body.querySelectorAll('link'));                document.body.style.height = document.documentElement.scrollHeight + 'px';                return this;            },            enumerable: false        };        return Object.create(array, attr);    };    Object.defineProperty(Document.prototype, '$', {        get() {            return function() {                if (arguments.length === 0) {                    return $(document);                } else if (!(arguments[0] instanceof Object)) {                    return $(document.querySelectorAll.apply(document, arguments));                } else {                    return $(arguments[0]);                }            };        },        enumerable: false    });    Object.defineProperty(NodeList.prototype, '$', {        get() {            return $(this);        },        enumerable: false    });})();

使用示例

一些文章页面会使用article标签,如果只想看到文章本体部分内容,删除页面上多余的元素,可以运行如下脚本:

// 传入想在页面上保留的部分元素的 CSS 选择器document.$('article').readable();

Alt+C 复制选择的文本

下面这段脚本增加了几个快捷键:

  1. Alt+CCtrl+C
  2. Alt+RCtrl+R
  3. Alt+WCtrl+W
// ==UserScript==// @name         copy// @namespace    http://tampermonkey.net/// @version      0.1// @description  shortcuts for copy selection using alt+c// @author       test.yu// @match        http*://*/*// @grant        window.close// @grant        GM_setClipboard// ==/UserScript==(function() {    'use strict';    function se(d) {        return d.selection ? d.selection.createRange().text : d.getSelection();    };    document.addEventListener('keydown', function(e) {        if (e.altKey) {            e.preventDefault();            if (e.which === 67) {                var s = se(document);                for (var i = 0; i < frames.length && !s; i++) {                    s = se(frames[i].document);                }                console.log("copy", s.toString());                GM_setClipboard(s.toString(), {type: 'text', mimetype: 'text/plain'});            } else if (e.which === 82) {                // console.log('reload window');                window.location.reload();            } else if (e.which == 87) {                // console.log('close window');                window.close();            }        }    });})();

VIM 移动快捷键

  1. Shift+G 页尾
  2. gg 页首
  3. Ctrl+D 向下翻页,取代原来的收藏功能
  4. Ctrl+U 向上翻页,取代原来的源码查看功能
  5. j 向下键
  6. k 向上键
// ==UserScript==// @name         vim.js// @namespace    http://tampermonkey.net/// @version      0.2// @description  emulation HOME/END/UP/DOWN shortcuts of vim// @author       test.yu// @match        http*://*/*// @grant        none// ==/UserScript==(function() {    var stack = {        timeId: 0,        e: [],        push: function(k) {            if (this.full()) {                stack.clear();            }            this.e.push(k);            if (this.timeId) {                clearTimeout(this.timeId);                this.timeId = 0;            }            this.timeId = setTimeout(() => {                this.clear()            }, 300);        },        dump: function() {            return this.e.join('');        },        clear: function() {            this.e.length = 0;        },        full: function() {            return this.e.length === 2;        }    };    document.addEventListener('keydown', function(e) {        if (document.querySelectorAll('input:focus, textarea:focus, select:focus').length) return;        if (e.shiftKey) {            var height = document.documentElement.scrollHeight;            if (e.which === 71) {                // console.log('G');                window.scrollBy({                    top: height                });            } else if (e.which === 72) {                // console.log('H');                window.scroll({                    top: 0                });            } else if (e.which === 77) {                // console.log("M");                window.scroll({                    top: height / 2                });            }        } else if (e.ctrlKey) {            var page = Math.floor(window.innerHeight / 50) * 50;            if (e.which === 68) {                // console.log('D');                // default action is bookmark                e.preventDefault();                window.scrollBy({                    top: page                });            } else if (e.which === 85) {                // console.log('U');                // default action is source code                e.preventDefault();                window.scrollBy({                    top: -page                });            }        } else {            // console.log(e.key, e.which, stack.e);            stack.push(e.key);            // check combination key shortcuts firstly            if (stack.full()) {                var keys = stack.dump();                // console.log(keys);                if (keys === 'gg') {                    window.scroll({                        top: 0                    });                    return;                }            }            // check key shortcuts for J/K            if (e.which === 74) {                // console.log('J');                // chrome default scroll 40px using arrow keys                window.scrollBy({                    top: 50                });            } else if (e.which === 75) {                // console.log('K');                window.scrollBy({                    top: -50                });            }        }    });})();

References

  1. Tampermonkey 帮助文件