Tampermonkey
这个插件类似 Firefox 上的 GreaseMonkey,插件地址为:https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo。
插件使用帮助文档:https://tampermonkey.net/documentation.php。
页面加载初始化操作
下面是在页面显示之前执行的脚本,其中的部分注释说明:
@name就是当前脚本的名字@run-at可以控制脚本在页面不同阶段中注入进来@match的说明可查看 Google 浏览器路径匹配规则文档@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 复制选择的文本
下面这段脚本增加了几个快捷键:
Alt+C同Ctrl+CAlt+R同Ctrl+RAlt+W同Ctrl+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() {; 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 移动快捷键
Shift+G页尾gg页首Ctrl+D向下翻页,取代原来的收藏功能Ctrl+U向上翻页,取代原来的源码查看功能j向下键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 }); } } });})(); |