油猴子从入门到喵喵喵喵(实例:9/9 完结)

【本文为付费内容,如您尚未付费请点此】

【返回目录】 | 【上一章 油猴子用什么写】

油猴子的基本格式

校对完成,更新时间:2020-07-23 11:22:25

首先记住它会分为两部分:

  • 上面以注释形式出现的元数据
  • 下面要注入页面中的脚本代码

在各种编辑器中这两部分都会以不同的颜色进行显示,所以很容易区分,我们先来研究元数据。

元数据,这个名词又很新鲜,英文 meta。就是对脚本的一些基本信息的说明,或者你叫它头部信息什么的,只要能够顺畅的交流,叫什么名字其实并不太重要。而且好多翻译也确实做不到信达雅,反倒让人增加迷惑。所以不要太在意这些,小老鼠自己也完全搞不清楚这些名词,反正会写就完事儿了。

那么起到说明性的作用,就是说明书?!那就是不用读系列……

并不是哦!虽然也许不书写元数据部分在某些平台下它也是可以使用的。但元数据信息真的是非常重要。这就好像做好我们本职的工作这是份内的事情,而处理好人际关系,似乎并不在这个本职工作之内,但是大家都懂的,如果人际关系处理不好,那你的本职工作也很难顺利完成。所以在工作前先和各部门打好招呼,我要做什么事情了,大家要给予支持,然后相互协调,避免冲突。这些话虽然很套路,但是总还是要说一下的。

居然是套路,那应该就有模板。你在脚本管理器里去新建一个脚本,它就会自动填写上一些基本的元数据。比如我在暴力猴中新建一个脚本,得到的内容是下面这个样子的:

// ==UserScript==
// @name        New script - meta.appinn.net
// @namespace   Violentmonkey Scripts
// @match       https://meta.appinn.net/t/topic/17705
// @grant       none
// @version     1.0
// @author      -
// @description 2020/7/20 上午7:22:46
// ==/UserScript==

虽然全是英文,但也不用紧张,咱们要看的主要是格式。反正程序员一般都有强迫症,所以代码里你会看到许多整整齐齐的格式,很舒适的。

这里的第 1 行和第 9 行就是个固定格式,表示元数据的开始和结束,并且说明这是用户脚本。对,这又是一个我必须要给你们讲一下,但是你们并不一定需要记住的问题。反正每一次新建脚本模板都给好了,要不是需要给你们讲,我早都忘了这两行的事情……

每一行用双斜线开头,是表示这是注释不参与运行,这就避免了某些不兼容的情况下,这些元数据对脚本产生影响。

然后中间几行,前面有一个以 @ 开头的单词,然后一些空格,后面写了些内容。前面的单词是说这一行记录了什么信息,后面的内容就是对应的信息。

那么现在看第 2 行,@name 显然是说名字,空格后面的内容就是这个脚本的名字,我们可以根据自己的需求进行修改。

理解这一条后面的就都好理解了,因为书写的格式是一样的。现在的问题就是,我要知道都可以去设置哪些数据。对这个事情总是要有一个规范的,要不然像小老鼠这么碎嘴子的,能在上面写个长篇小说,固然会把该说的都说清楚了,但是脚本管理器表示宝宝并看不懂,不就鸡对鸭讲了么……(咦,好像哪里不太对的亚子

可用的元数据

在告诉大家有哪些元数据的字段(标签)可用之前要先做一些简要的说明:

  • 我们并不需要把所有可用的字段全写上,只要写自己用得上的那部分就好;
  • 有些字段是可以多次出现的,但有些就只能出现一次,这些我会进行标注,大家根据字段所表达的意思去理解为什么是这样;
  • 不同的脚本管理器,对元数据的支持也不相同,但基础部分大家基本是一致的。而且你即便写上了这个脚本管理器无法理解的字段,也不会产生负面影响。

如果你以前看过一些相关的内容,那么很容易理解我将要做什么,就是把这些元数据的字段罗列出来,挨个给大家解释。差不多等于把文档复制过来并翻译一遍。

但如果我真的那么做,我估计连篇累牍的内容能立刻劝退 1/3 的小伙伴。讲还是要讲的,我们就稍微换一个角度,对这些元数据做一个粗略的归类。

必须有的元数据

这也不是绝对的必须,不写也不是说完全不行。但如果你要去写元数据,我觉得应该没有人会拒绝把这几个项目写上去。

@name

脚本总要有个名字吧。要不以后怎么称呼它?嘿,那个脚本……

但这里存在一个误解,就是把它看得过分重要。其实比较准确的理解是这里大概相当于昵称。就是用来让人便于识别和称呼的,它也会用来显示在脚本列表等地方,让我们能够快速的了解这是哪一个脚本。

@namespace

命名空间,这名词又让人迷惑。而且上面已经有一个名字了,那就更让人迷惑。那你把它理解为用户(脚本) ID 好了。

我们和上面对照一下,昵称是用来称呼的,ID 是用来识别它就是它的。这就好像在微信里,我们可以经常的去修改昵称,但是微信号轻易是不能修改的。

在文档中的描述是:用 @name@namespace 的组合来识别脚本,但在不同的脚本管理器下可能有所区别。总之这两个内容设置好之后,尽量就不要修改了。尤其是 @namespace,如果修改肯定要引发问题。

自然也很容易理解,这两者不要和其他的脚本冲突。两个学生具有相同的名字或者相同的学号,就会让人很难分辨,然后引发错误。

@version

版本号,基本格式就是: 1.0.0 这样。第 1 位表示大版本,第 2 位表示小功能的增加,第 3 位表示细节的修补。

但是对于版本号也没有那么严格的要求,按着这个格式去书写就可以了,至于数字代表什么,或者你随便去写,都没所谓。比如现在浏览器的大版本号(第一位)就是随着时间固定的增长。而我自己写东西一般第 1 位就永远是 0,因为我一直认为自己写的东西不够完善,不足以发出一个正式的版本。

但是新版本的版本号一定要比旧版本的大,因为这是判断升级的一个依据。如果脚本管理器发现,线上的版本比当前已安装的版本版本号更高,那么这个脚本就可以进行升级。

@author

作者,这后面写上你的大名,愿意加上邮箱,也没人管你。或者加上一堆形容词。反正就是写清楚脚本的作者是谁,你一定不会希望别人用了这个脚本,却不知道它是你写的,那就真的变得默默无闻了。

@description

对于这个脚本的描述,它会显示在应用市场的简介中,就是脚本名字下面最先显示出来的那部分。在脚本列表中,显示的脚本信息也对应着这里的内容。

这里进行一些补充说明。上面这 4 个都只能出现一次哦,很容易理解的吧,我们总不能给一个脚本弄出两个名字来,两个版本号就更加荒唐了。如果有多个作者,那就都写在那一行里就好。对,一行。就算是描述也要写成一行,不要用回车换行。(别抬杠,抬杠就兼容性,不一定所有脚本管理器都支持特殊的格式,所以用最基本的格式最不容易出错)

@name@description 因为是描述性的内容,所以支持多语言,就是你可以为每一种语言单独设置一份名字和描述,具体格式如下:

// @name                My first user script.
// @name:zh-CN          我的第一个脚本
// @name:en-US          Meow~
// @description         dms is a sunshine boy.
// @description:zh-CN   小老鼠帅帅哒~
// @description:en-US   Delicacies

描述中建议写上脚本的创作日期和更新日期,这样方便自己日后回顾。如果你有强迫症,觉得在描述中混入这样的信息,让自己不舒服,也完全可以自己创造两个字段来标记,反正脚本管理器不理解就会把这些字段跳过去,但是自己看代码的时候是有意义的。

@match

这是描述这个脚本要在哪些网址下运行。如果和后面的网址匹配就运行。

但大多数时候我们的脚本并不只运行在某一个确定的网址下,而是希望在某个网站上都起作用,或者这个网站的某个分类下都起作用,这时候就要用到通配符。

网址会被分为三部分:协议、域名、路径。

https://www.appinn.com/auto-expand-full-text/

这样的一个网址,其中 https 就是协议,那我们知道还有 http 的协议。

www.appinn.com 这部分就是域名,表示的这是哪一个网站。

auto-expand-full-text/这部分,就是在域名后面那条斜线之后的部分,用来表达这个网站中的哪个页面,这就是路径。

然后对于每一部分,我们都可以完整准确的去写出它的内容,或者使用星号代替一定的字符。

如果在协议部分使用星号,那么代表的是:http 或者 https ,对,只有这两种协议,并不包含其他。

如果在域名部分只有星号,那么它匹配任何网站。如果域名的子域是星号(比如上面域名的 www 部分),那就表示这个域名下的任何子域(www 可以被换成任何内容)。

如果是路径位置的星号,它可以匹配 0 到任意多个字符。

*://*/*

所以像上面这样写就表示匹配任何协议下的任何网站的任何路径,就是在所有能运行的页面全都运行这个脚本。

*://www.appinn.com/*

上面这样写表示在这个网站下的所有页面都运行。

*://www.appinn.com/posts/*

上面这样写表示在这个网站下的 posts 目录下的页面中全都运行。

这个规则并不难掌握,因为它没有搞得很复杂。几种简单的情况,稍微熟悉一下就可以很好的掌握了。但这时候我们就会遇到一个很普通又很棘手的问题,都有很多网站,我们在域名部分写出下面两种形式都可以正常的访问:

www.appinn.com
appinn.com

就是加不加 www 都可以。但这就很难使用通配符进行表达。

*://*.appinn.com/*

这样前面不是 www 的也会被匹配,并不符合我们的预期。而且注意在星号后面多了一个点,所以这种书写方式并没有办法匹配到完全不加 www 的情况。

有同学可能说,反正星号可以代替各种字符,那我就这样写:

*://*appinn.com/*

先不考虑这个写法是不是真的符合规则。就算可以正常使用,那么会不会匹配到下面的网址:

https://meow-appinn.com/

这显然就完全是另外一个网站了,所以这样写的疏漏太大,是完全不可取的。正确的方法是,索性写两条规则好了,就像下面这样:

@match *://www.appinn.com/*
@match *://appinn.com/*

所以,@match 可以出现多条。把你需要适配的情况写完全,一条规则一行,就像上面这样书写就好。

可选的地址信息

写不写都可以,写上会更完善一些。

@homepage, @homepageURL, @website, @source

这是个选择题,4 选 1,因为表达的意思是一样的。由此可见,对于脚本的元数据,大家创造了很多规范,也并不是那么十分统一。还有就是随着规范的不断更新,又要去兼容以前的规范,导致多种规范并行。

这个字段设置的是脚本的主页,就是官方网站的意思。但很多脚本也并没有主页,于是就放上作者自己的网站博客等。有人说这就是免费的广告位,谁会放弃呢?(我的脚本好像大部分都没有写,亏了亏了

这个信息会显示在脚本市场或者脚本列表中的主页信息上,一般只要点击就会直接跳转到对应的网站。

然后如果在 @namespace 中填写的直接是一个网址,也会被视为这个脚本的主页。

@icon, @iconURL, @defaulticon

三选一,设置脚本的图标,放上图标图片的网址。这时候你可能需要一个好用的图床,这确实挺麻烦,所以好多脚本就并没有设置。

当然如果设置了会显得很好看,也很专业的样子。

@icon64, @icon64URL

表示 64×64 像素尺寸的图标,和上面一样给出一个图片的地址。和上面的区别是这代表更高清晰度的图标。但一般来说我们也不怎么会用上,而且也不是所有脚本管理器都支持。如果设置图标,就设置上面的字段就够了,这个就作为了解一下,又是你可以不用,但我不能不说的内容。

@supportURL

支持页地址,如果用户有问题去什么地方找你进行反馈,那么把这个地址放在这里。脚本市场和脚本列表中对应的位置会进行显示。

@downloadURL

脚本的下载地址,就是你的脚本文件在网络中的访问地址。脚本管理器会通过访问这个网址来更新脚本。

但一般也没有人写,因为如果是通过网络地址安装的脚本,脚本管理器会自动为这个脚本添加它的下载地址,以备日后进行更新。我们上传到脚本市场的内容,即便没有填写这些信息,也会被添加/修改为市场的地址。这可以让用户更方便稳定的获得脚本更新。

@updateURL

脚本的更新地址。首先这个不是所有的脚本管理器都支持,很多脚本管理器直接通过上面的下载地址判断是否需要更新。

脚本管理器通过访问这个地址,获取当前最新版本的版本号,然后得出一个是否有更新的结果。所以只是借此进行判断,而不是通过这个网址进行更新。

大部分情况下无需填写此信息。只不过是既然这些网址都出现了,索性把它们放在一起讲一下。

为了避免疑惑,也方便大家出去装 x,所以我把这个没什么用的东西也解释一下。这个更新地址里放什么呢,也放你的脚本,就和上面的 @downloadURL 是一样的。然后脚本管理器访问 @updateURL 来获取脚本的版本号和更新日期,来判断是否需要更新。如果需要,那么通过 @downloadURL 的地址来下载更新。

好像这样复杂的设计,非常莫名其妙,因为这两个网址可以完全一样,并且会正常的工作,搞成两个,这不是多此一举么?那现在我们来设想一个特殊的情况:我的脚本非常复杂,所以体积很大,下载一次需要好几分钟那种。然后用户又有强迫症,希望保持脚本的及时更新,那每一次检查更新需要花费的时间就很长。然后我很懒惰,三年才更新一次脚本,三年里用户的脚本管理器每天都在检查更新,这是怎样的资源浪费啊!但把检查更新和实际进行更新这两个网址分开,就意味着我可以在 @updateURL 这里放一个只包含脚本元数据的版本,这样检查的速度就非常快了,反正也就是对照一下元数据,看看是否需要更新嘛。如果发现确实有更新的需要,再去下载地址,下载那个体积巨大的完整版本。

这么一解释就会发现这个设计是合理的,但为什么现在又不需要呢?下载地址完全可以让脚本管理器自动去解决,你从哪里安装的这个脚本,它就把这个地址自动的记录下来,然后随时从这个地址去检查更新,这样无论是作者还是用户都会更加方便。至于检查更新的时候,脚本管理器可以只下载前面的一段数据进行比较,反正元数据都写在最前面,我读取到版本号就好了,后面的内容不下载。就是通过脚本管理器的增强和优化,让这些以前有意义的设计变得没有什么必要了。

出于强迫症或者一致性的考虑,主页字段我会选择使用 @homepageURL 这个名称,图标字段用 @iconURL。当然如果新建脚本的时候给出的模板已经写了,那我也懒得去修改,直接填上内容就好。

需要时才会出现的内容

以下内容仅在你需要进行这项设置的时候才书写它。否则就没有必要让它们露面了。

@exclude-match

这个和我们前面讲到的 @match 书写和使用都是一样的,但表达的意思是不要在这些网站上去运行脚本。可以认为 @match 是一个白名单,而 @exclude-match 则是一个黑名单。

如果我们只打算匹配几个确定的网站,那么使用白名单(@match)肯定最方便;而如果要匹配几乎所有的网站,只有极个别的网站要排除掉,这时候就要配合使用。先用白名单(@match)匹配所有网站(*://*/*),再用黑名单(@exclude-match)将特定的网站排除掉。

@match 是必须有的,@exclude-match 则根据情况去进行设置。这两项在(针对单个)脚本的设置中,也会有相应的选项,这是供用户进行设置的,用户的设置可以覆盖脚本自带的设置,并且不会受到脚本更新的影响。

@include / @exclude

@match / @exclude-match 差不多,建议使用 @match/ @exclude-match 而不是 @include/@exclude 因为匹配规则更安全,更严格。

你可以把这认为是旧的方式,可以用,但不推荐,然后一些脚本管理器可能不支持 @exclude-match ,那就只好用 @exclude 了。

@require

引入的库。

写脚本就是为了给自己带来便利,那如果每一个功能都自己写,确实挺不容易的。要是把别人写好的工具库引入进来,然后再做一些简单的调用,那就方便多了。

这个字段后面应该写一个 js 文件的地址,就是你要引入的那个库。然后脚本管理器就会自动的把它插入到页面之中。其实我们通过自己书写脚本来实现这个功能也不算很麻烦,但是能少写几行代码总是很开心的。

因为可能需要引入多个库,所以这个字段可以书写多个。

@resource

这个字段用来将一些在脚本中会使用的资源进行预加载。就是打开页面以后,它就会开始下载这些资源,这样等到我们需要使用的时候,就不需要再去等待那个下载的时间了。

具体的使用需要配合接口,这个我们在后面会讲到,这里只先演示它的书写:

// @resource logo https://img3.appinn.net/static/wp-content/uploads/appinn190.png

后面的内容首先是我们给这个资源取的名称,用来在脚本中调用。然后是这个资源的地址,可以是图片或者某些文件的网址。

当然我们需要引入的资源可能不止一个,所以这个字段也可以书写多个。

@grant

上面说到了接口,就是脚本管理器给我们脚本提供的一些便利,和更高的权限,让我们能够更好的完成工作。就好像可爱的女仆打算为小老鼠做饭,她就要跟管家申请使用厨房,管家允许她使用厨房,并且有权使用厨房中的各种食材,厨房里的厨师们也要听从她的指挥来进行辅助。那么烹饪这件事情就变得简单而畅快起来了。

当然如果你需要这些权限,这些辅助,要先和管家明确的提出申请,这样管家才知道如何安排才能够更好的帮助到你。@grant 这个字段就是用来申请接口的权限,说明我要使用这个接口,然后才可以在脚本中进行使用,它的书写格式是这样的:

// @grant GM_getValue
// @grant GM_setValue

在它后面写上要使用的接口名称就可以了,每一行只能申请一个接口,如果需要多个接口,那就像上面这样书写多行。

@run-at

这个字段是用来设定我们的脚本在什么时候注入到页面之中,有如下几个值可供选择:

  • document-end 这是 Violentmonkey 的默认值,就是当页面加载完成之后,再注入我们的脚本。一般来说这个时机是不错的,页面加载完成以后不会再发生大的变化了,然后我们对它进行修改,就不容易出现混乱,或者被后续的改变而覆盖。但如果某些页面使用了懒加载技术,那么可能虽然已经宣称页面加载结束,其实还在进行着各种加载工作,这时候就要具体问题具体处理了;
  • document-start 这表示让我们的脚本尽可能早的被注入,但这并不能够保证我们的脚本就在其他所有脚本之前运行。
  • document-idle 这是 Tampermonkey 的默认值。这个时机可以简单粗暴的认为是在页面加载完成之前的那一瞬间。虽然在页面加载完成之后去注入是一个不错的时机,但是可能有许多脚本都在等待着这个时机运行,我们不想和其他脚本争抢,所以就走一个 VIP 通道,当页面加载完成,但还没有对外宣布的时候,就是还没有到 document-end 这个时间点的时候,注入我们的脚本。然后再对外宣布页面加载完成,即到达 document-end 这个时间点。
  • document-body 如果存在 <body> 元素,则执行脚本。Tampermonkey 支持此选项,Violentmonkey 中则未提及。
  • context-menu 当在浏览器的右键菜单中点击该脚本的项目,才进行脚本的注入。这个方法最大限度的保证了脚本不被使用就不被加载。Tampermonkey 支持此选项(但未来可能改变,并且文档中没有说如何设置右键菜单),Violentmonkey 中则未提及。

@noframes

这个字段后面不需要写任何的内容。如果没有写这个字段,则脚本默认是在页面和页面里的所有的框架中运行(如果这个框架的网址也符合我们设定的条件的话)。

添加上这个标记,则只在页面中运行,而忽略掉所有的框架。

如果你没有听懂这是在说什么,就在发现某个页面中这个脚本被运行了多次,不符合你预期时加上这个。

一些不是所有脚本管理器都支持的标记

其实上面已经讲到一些,比如 @icon64@updateURL,以及上面运行时机中的一些选项。后面这些则是对于新手几乎是完全没有用的,即便是老手也很少需要用到它们,或者在用到的时候需要十分慎重,因为要认真的考虑兼容性和可能引发的其他问题。

@connect

后面是书写一个域名,用来声明我们会在脚本中使用到这个域名中的资源。这可以用来解决跨域问题。

这个功能我没有使用过,而且我也希望所有作者都能够谨慎使用此功能。虽然在前端上禁止跨域是一件非常烦人的事情,但这个限制在安全层面考虑还是非常有必要的。现在工具可以给我们更高的权限来突破这个限制,但是“能力越大,责任越大”,我们也有必要十分谨慎的对待这个强大的权限。

此字段可书写多个,Violentmonkey 似乎并不支持。

@nocompat

标记不兼容的浏览器,因为每个浏览器在技术上总是有着细微的差别,所以导致有些脚本只能在特定的浏览器上去运行。我们通过这个标记来告诉脚本管理器,当前的脚本不兼容哪些浏览器。

此属性似乎 Tampermonkey 独有。

@inject-into

用来指定脚本的注入模式,有如下三种选项:

  • page:这是默认值,脚本会被直接注入到页面之中,各方面都和页面原生的脚本是一样的;
  • content:在一个与页面隔离的环境下运行,可以访问和修改页面的 DOM,但是无法访问到网页的 JavaScript 对象。 不过可以通过 unsafeWindow 对象来绕过限制进行访问,但这种做法(绕过)是非常不推荐的。
  • auto:先尝试使用 page 模式进行注入,如果出现问题,则改用 content 模式来进行注入。

这个字段似乎是 Violentmonkey 独有。Tampermonkey 默认用 content 模式注入,在必要的情况下用 unsafeWindow 来访问 window 对象。

应用市场提供的标记

这里仅参照 Greasy Fork 的说明,不同市场会有不同的规定。

@license

脚本的许可协议,如果没有填写,则表示仅允许个人使用,且不得二次分发。

@contributionURL

用来收取捐助的链接。

@contributionAmount

建议的捐助金额

@compatible

脚本兼容的浏览器,并可附加更多说明。如:

@compatible firefox 火狐上必须关闭广告过滤器

目前能被网站识别的浏览器名称有:firefox, chrome, opera, safari

@incompatible

不被兼容的浏览器,并可附加更多说明,格式参照上一条。


附录:上面提到的所有元数据的速读版本

  • @name 【通用】【多语言】脚本名称

  • @namespace 【通用】命名空间

  • @version 【通用】版本号

  • @author 【通用】作者

  • @description 【通用】【多语言】脚本描述

  • @match 【通用】【可多条】匹配规则

  • @homepage, @homepageURL, @website, @source 【可选】主页

  • @icon, @iconURL, @defaulticon 【可选】图标

  • @icon64, @icon64URL 【非通用】高清图标

  • @supportURL 【通用】【可选】支持页地址

  • @downloadURL 【通用】【不必填写】脚本下载地址,一般无需书写

  • @updateURL 【非通用】【不必填写】脚本的升级地址,无需书写

  • @exclude-match 【非通用】【可多条】排除规则

  • @include 【通用】【可选】【可多条】匹配规则

  • @exclude 【通用】【可选】【可多条】排除规则

  • @require 【通用】【可选】【可多条】引入的脚本

  • @resource 【通用】【可选】【可多条】预加载资源

  • @grant 【通用】【可选】【可多条】接口权限申请

  • @run-at 【通用】【可选】脚本注入时机

  • @noframes 【通用】【可选】不在框架内注入脚本

  • @connect 【非通用】【可选】【可多行】白名单外部域

  • @nocompat 【非通用】【可选】不兼容的浏览器

  • @inject-into 【非通用】【可选】脚本的注入模式

  • @license 【市场】脚本的许可协议

  • @contributionURL 【市场】脚本的捐助链接

  • @contributionAmount 【市场】建议的捐助金额

  • @compatible 【市场】兼容的浏览器

  • @incompatible 【市场】不兼容的浏览器


以上内容,小老鼠花费了一天多的时间辛苦整理。肯定还有很多疏漏,也有很多不准确的地方。但我也很难将它做的面面俱到,毕竟在不同的浏览器下,不同的脚本管理器下,面临的情况都不相同。而且还有油猴标准不断更新的因素在影响。所以这些内容只是作为给大家的参考,但实际书写的时候还是要以实际运行的效果为标准,根据自己面对的情况灵活的选择。
【返回目录】 | 【下一章 如何调试脚本】

【本文为付费内容,如您尚未付费请点此】

【返回目录】 | 【上一章 油猴子的基本格式】

如何调试脚本

校对完成,更新时间:2020-07-23 11:36:10

如果打算自己去书写脚本,就难免要对脚本进行调试。代码写出来,试一试它能不能按照我的预期去运行,如果不符合预期,那么看一看究竟是哪里出了差错,然后修正这个问题,让脚本回归预期。

最基本,最重要

最简单直接的方法就是把写出来的脚本安装进去,让它实际工作一下,看看效果如何。前面我们也已经讲到过了,无非就是直接在脚本编辑器里进行编辑;或者在外面编辑好,然后复制粘贴进去;再或者将本地文件安装到脚本管理器里,让它可以更加便捷地进行更新(同步更改)。

开发者工具

然后要用到的是浏览器的开发者工具,可以从浏览器的菜单打开,或者在页面上右键检查元素,再或者按快捷键 F12。

其中会有一个控制台的标签(Console),这里主要用来输出页面的各种运行信息,如果我们的脚本出错,那么它的报错信息应该也会出现在这里。

其中有一个禁止符,可以清空控制台当前的所有输出。如果在清空之后再触发脚本,那么可以更加容易阅读,避免其他干扰信息。

如果使用的是 Violentmonkey,那么脚本默认的插入模式是 page,就是和网页自身的脚本无异。所以这时候在控制台的过滤器里选择 top,就是查看最顶层的信息,就对了。如果是 Tampermonkey,或者以 content 方式插入,则在过滤器里选择对应的脚本管理器。

脚本出现问题产生的信息,一般在右侧的文件名处会是 脚本名称.user.js,也可能是 VM.js 或者什么类似的东西。因为环境的不同产生的效果可能也不一样,这种事情就灵活变通就好。反正大部分情况这些信息还是很容易分辨的。

这里我就不特别进行展开了,因为后面连贯的是一整套的前端知识和开发相关的技巧,一旦展开,那这篇内容就直接从中篇飙升为长篇,老鼠就真的要累趴趴了。

如果对这方面有兴趣建议去网络上搜索相关内容,或者深入阅读 MDN 的前端开发文档,或者谷歌的开发者工具文档( https://developers.google.com/web/tools/chrome-devtools )。又或者来阅读小老鼠写的前端教程( https://2019.dmnydn.com/ )。

断点

如果我们在代码中插入这样一行:

debugger

就为代码加入了一个断点,代码运行到这里会暂停并打开开发者工具显示对应的位置,这时候前面已经运行的代码上会标示出已有的量,方便查看程序是如何运作的。

片段

如果只是想测试代码中某一个细节,比如这一句代码是否可以准确的获取页面中某个元素的值。那么直接将这段代码复制到控制台中,回车运行,查看结果也是一个很不错的办法。

这里需要提示的就是在控制台中输入时,如果点击回车,就会将已输入的代码运行。想要对代码进行换行,则需要使用 Shift+Enter。

Code Runner

在 VS Code 编辑器里有一款叫做 Code Runner 的拓展,它可以在编辑器中直接运行 JavaScript,并查看效果。甚至需要测试的代码都无需保存,便可以直接运行。

当然这是在你的代码与页面内容无关的情况下,比如就做一个计算器,那么就和页面中的元素等无关,所以在编辑器中直接进行测试会方便一点。

或者只是用它来测试一些与页面无关的代码片段。

耐心

虽然这算不上工具,但真的是写脚本时非常重要的东西。

如果是书写一个页面,那么我们从零开始搭建,干扰的因素是很少的。但脚本却是在别人页面的基础上去进行修改,这就仿佛在尝试解开一个残局。其中会有很多制约,很多掣肘的地方,还有很多你可能没有想到的影响因素,就需要凭着耐心一点一点进行排查。


到这里我们算是对油猴脚本有了一定的了解。如果你本身有前端的基础,其实现在已经完全可以进行书写了。就算没有基础,凭着已经了解到的这些内容,再去看别人的代码,也能从中了解到很多信息。后面我们要进入到它的入门部分,了解它能够带来的便利,并尝试书写一些简单的小脚本。

【返回目录】 | 【下一章 油猴子的接口】

就是加不加 www 都可以。但这就很难使用通泰服进行表达。

感谢指正,已经修改。

因为是使用语音输入法完成的,难免有识别错误的地方。回头我会再做一遍校对。

【本文为付费内容,如您尚未付费请点此】

【返回目录】 | 【上一章 如何调试脚本】

油猴子的接口

校对完成,更新时间:2020-07-23 13:15:11

这是脚本管理器提供给作者们的便捷功能,所以是否有这些接口以及接口的具体使用方法,还有接口会产生怎样的效果,都取决于脚本管理器,在书写前先去查阅脚本管理器所提供的文档是很有必要的。

其中一部分接口,我们也可以通过自己书写代码来实现,但有现成的方法可以少写几行代码,而且兼容性可能更好一些,这还是很让人乐于接受的。

然后脚本管理器作为浏览器的扩展,它是有比较高的权限的,通过这些接口我们的脚本也可以调用这些权限,来实现更高级的功能。

再强调一次,这里所列出的接口仅仅作为参考,在使用时因为具体环境的差异而有所不同。

接口详解

unsafeWindow

这大概不能算是一个接口,但是 Tampermonkey 的文档中也把它列出来了。当脚本以 content 模式注入时,我们可以用这个对象来访问一些原本受限的内容。就如它的名字一样,使用这个方法意味着绕过了一些安全限制,也就有可能带来一些安全上的问题。

资源验证

这大概不能算是一个接口,但是 Tampermonkey 的文档中也把它列出来了,Violentmonkey 似乎并不支持此方法。

当我们引入一个资源,如何确定这个资源就是我们所预期的那个,而没有出差错,在请求的过程中没有被人替换掉。那么我们可以对引入的资源加入一个校验。

具体使用是在我们引入资源时,网址后面以井号分割的形式加入对应的校验特征码:

// @resource logo https://img3.appinn.net/static/wp-content/uploads/appinn190.png#md5=ad34bb...

Tampermonkey 本身支持 md5 方法,其它(SHA-1,SHA-256,SHA-384和SHA-512)资源特征方法,都取决于 window.crypto 的支持。

如果资源校验不成功,则不会传递给用户脚本。文档中说所有校验用的特征码都需要以十六进制或 Base64 格式编码,具体我未做测试。

一般的讲,这并不是很有必要,虽然确实在安全方面做得很到位。但是一般网站中的资源也都没有经过校验,目前对于资源的网址启用 https 已经可以杜绝大部分问题。

这个方法的主要问题是有的脚本管理器并不支持。不过因为附加的内容以锚点形式出现,所以并不会带来什么副作用,而且附带上文件的校验特征,也可以确保当此文件发生更新之后使用的是更新后的文件,而不是缓存。

以下以 GM_ 开头的这些方法才是相对正式的接口,如果需要使用,它们均需要在元数据用以 @grant 字段进行声明。

GM_info

这个方法可以用来获取一些有用的信息,但根据脚本管理器的不同,返回的内容也不太一样。不过一般都会包含脚本的元数据,脚本管理器的相关信息,系统环境的相关信息。

const info = GM_info()
console.log(info)

如果需要使用,可以像上面那样先输出出来,看一看它能够获取到的信息。下面列出一些其中比较通用的属性(Tampermonkey 和 Violentmonkey 中都有的):

  • scriptMetaStr: 脚本的元数据,整个元数据部分,以字符串形式返回。
  • scriptWillUpdate: 脚本是否会自动更新
  • scriptHandler: 脚本管理器的名称
  • version: 脚本管理器的版本
  • script: 脚本的元数据,以对象形式返回。其中包含的元数据可能因脚本管理器的不同而不同。@match 变成了 matchs,以数组形式包含全部的规则,其它可以出现多次的条目类似。@run-at 变成了 runAt,即避免产生歧义的驼峰写法。

GM_addStyle

向页面中加入样式,这个方法非常简单并且好用,只需要给它一串 CSS 的文本,它就会在文档的 <head> 标签中加入一个 <style> 标签,并把这些 CSS 的内容放进去。然后会返回这个 <style> 元素的对象。

一般来说, <style> 标签会被添加到 <head> 标签的最后面,以确保覆盖页面中原有的样式。但这个事情并没有绝对的保证,只是它在努力去做而已,所以想要确保自己的样式覆盖页面中原有的样式,还是要在 CSS 中添加 !important

听不太懂也没有关系,下面看示例代码:

GM_addStyle(`
	// 这里写 CSS 代码,可以多行
`)

复制上面代码,然后在中间添入你的 CSS 就行了。注意这里使用的是反引号,这被称为模板字符串,在其中可以进行换行,这样在里面书写 CSS 就可以带上清晰的格式,就像平时书写 CSS 文件一样。如果把其它地方复制过来的 CSS 粘贴进去,也无需因为是在 JS 中而调整 CSS 字符串的格式。

如果你有对这个 <style> 元素的对象进行进一步操作的需求,可以获取它的返回值。

GM_log

这个方法是 Tampermonkey 提供的,用来在控制台输出日志,就和 console.log 一样,但是我更愿意使用 console 下属的方法,因为足够强大,如果觉得名字有点长,完全可以自己命名一个变量进行指代。就没必要由脚本管理器再提供一个简写方式。不过也可能是这个方法有什么我没有发现的特别之处。

GM_log('要输出的内容')

GM_openInTab

很容易理解,就是在新标签页中打开一个网址:

GM_openInTab('https://www.appinn.com/')

可以加入第 2 个参数,表示是否在后台打开这个标签页,所以第 2 个参数是一个布尔值。

GM_openInTab('https://www.appinn.com/', 'true')

像上面这样书写这个标签,则会在后台打开.

第 2 个参数也可以是一个对象,用来传入一些配置信息,此选项对象在不同脚本管理器下的支持是不相同的,只说两个相对通用的属性:

  • active: 这个标签打开后是否处于激活状态,默认为 true ,这个设置和是否在后台打开的选项是正好相反的。如果在前台打开那么自然打开之后就是激活状态,如果在后台打开则是没有激活的状态。
  • insert: 新标签页是否紧挨着当前标签,默认为 true,如果设置为 false 一般浏览器会将这个标签放在最后一个。

然后选项中有可能还可以设置新打开的标签是否被固定,或者是否在隐私模式下打开。在使用这些选项之前,请先确定脚本管理器对此支持。不支持的结果就是达不到你预期的效果,但是链接还是可以打开的。

GM_download

这个方法用来生成一个下载,如果你对前端有所了解,就会知道这个其实和打开链接是类似的操作,同时也和获取网络资源的方法相类似。(这些类似是说的在原理层面)

最简单的使用方法就是直接给它要下载文件的网址:

GM_download('https://img3.appinn.net/static/wp-content/uploads/appinn190.png')

也可以再给它第 2 个参数来说明这个下载的文件默认保存为什么名字,推荐使用这个方法,因为有些脚本管理器可能不支持省略文件名称:

GM_download('https://img3.appinn.net/static/wp-content/uploads/appinn190.png', 'logo.png')

对于新人了解上面的书写方法已经足够了。如果需要进行更细致的控制,可以只给一个对象参数,对象中的属性如下:

  • url: 要下载的文件地址,这个是必须的。
  • name: 文件的默认保存名称,这个也是必须的。(不是所有脚本管理器都支持省略名称
  • onload: 当下载完成后要执行的函数
  • headers: 下载请求的头信息
  • save As:一个布尔型,是否显示另存为的对话框(Tampermonkey 支持)
  • onerror: 当下载出错时要执行的函数
  • onprogress: 在下载进度发生变化时执行的函数
  • ontimeout: 由于超时导致下载失败时需要执行的函数

GM_notification

让浏览器发出一个通知来提醒用户,现在浏览器基本都支持直接发出一个系统级的通知,就是在右下角弹出那种。

最基本的书写方式:

GM_notification('一些要显示在通知中的文本')

但是这样太简陋了,所以我们还可以给它第 2 个参数作为通知的标题:

GM_notification('一些要显示在通知中的文本', '还有标题就显得很专业')

如果有需要,还可以加入第 3 个参数,放入一个图片地址,它会显示在通知之中。第 4 个参数可以是一个函数,当这个通知被用户点击会执行此函数。这些都是可供选择的,我就不一一演示了。

如果需要更复杂的控制,同样可以传入一个对象作为参数,对象的通用属性如下:

  • text: 要在通知中显示的文字内容(必须)
  • title: 通知的标题
  • image: 要显示在通知中的图片
  • ondone:通知被关闭时要执行的函数(无论是何种原因使通知被关闭,都会触发此函数
  • onclick: 通知被点击时要执行的函数

还有一些其他属性可供选择,但是不同的脚本管理器有不同的设置,就不在这里详细举例了。

Tampermonk 中提供了通知超时的设置,即超过一定时间通知自动关闭。Violentmonkey 则是提供了 remove() 方法来移除通知(但我并未尝试出具体的使用方法)。所以让脚本发出一个通知是容易的,但是想让通知自动关闭却没有一个通行的并且可靠的方法。

GM_setClipboard

将内容写入系统剪切板,就是通过脚本复制。

GM_setClipboard('这些文本会被放入到剪切板之中')

它还可以有第 2 个参数来说明内容的类型,默认值是:text/plain ,就是纯文本。

如果需要设置为其它类型,可以自行查阅 MIME types 的相关信息。但除文本以外的内容复制效果不一定能够得到确切的保证。

GM_setValue

保存一个数据,这会保存在这个脚本自有的储存空间,以供此脚本随时访问,它不受标签页关闭、刷新以及浏览器关闭等操作的影响。借助这样的全局数据,我们可以实现页面间的通讯。此数据可在脚本设置中进行观察。不同脚本间的数据并不互通。我们可以将比如用户对脚本的设置等需要延续性的数据存储到这里。

GM_setValue(‘myAge’, 16)

第 1 个参数是这个数据的名字,第 2 个参数是它的值,这两个参数都是必须的。

GM_getValue

凭数据的名称,获取已保存的数据值。

let data = GM_getValue('myAge')

但有一种很容易出现的尴尬情况,就是当我们试图获取这个数据的时候,其实它还没有被储存过,也就是还不存在。因为这种现象太普遍了,所以准备了一个快捷的解决方法,就是给它第 2 个参数作为默认值,像下面这样:

let data = GM_getValue('myAge', 18)

如果我们获取到已经储存的数据,那就使用获取到的值作为结果,如果发现还没有储存这个数据,就使用后面的默认值,这里是 18。

GM_deleteValue

删除一个已储存的数据,只要有这个数据的名字就行了。

GM_deleteValue('myAge')

GM_listValues

当我们存储的数据比较多的时候,就可能记不起来都存储了哪些数据。又或者我只是想判断一下现在有哪些数据已经被存储。就可以使用这个方法,它会返回一个数组,数组中是所有已存储数据的名字。

const dataNameArray = GM_listValues()

GM_addValueChangeListener

添加一个监听器,某个储存的数据发生变化时,就执行对应的操作。第 1 个参数是这个数据的名字,第 2 个参数是要执行的操作(一般是一个函数)。

调用对应的操作时,会向它传入 4 个参数:

  • 这个数据的名称
  • 这个数据以前的值
  • 这个数据改变以后的值
  • 是否是由其他标签页造成的修改
const listenerId = GM_addValueChangeListener('myAge', (name, oldAge, newAge)=>{
    console.log('我以前的年纪是 ', oldAge, ' ,现在的年纪是 ', newAge)
})

这个操作会返回一个编号,用来标识此监听器。

GM_removeValueChangeListener

凭借监听器的编号移除对数据变化的监听。

GM_removeValueChangeListener(listenerId)

GM_getResourceText

获取资源的文本内容,它只有一个参数,就是这个资源的名称。这个资源应该在元数据中以如下形式进行声明:

// @resource logo https://img3.appinn.net/static/wp-content/uploads/appinn190.png

然后我们就可以像下面这样获取:

const text = GM_getResourceText('logo')
console.log(text)

但需要注意,此方法会将资源当作文本内容进行读取,所以适合用来获取一些文本类的信息,比如 Json 格式的文件。上面举例中因为资源是一个图片,所以用此方法,输出的内容会是一片乱码。

GM_getResourceURL

这个方法用来获取资源的 Blob 格式数据地址。可以用来读取各种二进制文件,就比如图片。

const blobUrl = GM_getResourceURL('logo')
const img = new Image()
img.src = blobUrl

GM_registerMenuCommand

这个方法可以注册一个脚本菜单,它只是出现在脚本管理器的弹出列表中,方便用户操作。

GM_registerMenuCommand('这里是显示的名称', ()=>{
	console.log('啊,你点击了菜单~')
})

第 1 个参数是菜单显示的名称,第 2 个参数是当菜单被点击时要执行的动作。

GM_unregisterMenuCommand

用来移除一个脚本菜单,这个操作在不同的脚本管理器下有所不同。

Violentmonkey 下:凭借菜单显示的文字即可删除对应菜单。

GM_unregisterMenuCommand('菜单显示的文字')

Tampermonkey 下: 注册菜单时会返回一个菜单 ID,然后凭借这个 ID 去删除对应的菜单。

const menuID = GM_registerMenuCommand('这里是显示的名称', ()=>{
	console.log('啊,你点击了菜单~')
})

GM_unregisterMenuCommand(menuID)

GM_xmlhttpRequest

创建一个 xmlHttpRequest 请求。如果你不懂,那基本现在也用不上。

它的参数是一个对象,其中包含有大量可选属性。

  • method: 发起请求的方法,可选: GET, HEAD, POST,默认: GET
  • url:请求的目标网址
  • headers:请求的头信息
  • dataPOST 方法下要发送的数据
  • cookie: 需要额外附加的 cookie 信息,这是在页面原有 cookie 信息之外增加的补丁。Tampermonkey 支持此属性
  • binary: 用二进制模式发送数据
  • nocache:不对资源进行缓存,Tampermonkey 支持此属性
  • revalidate:重新验证缓存的资源,如果通过验证则使用缓存,Tampermonkey 支持此属性
  • timeout: 请求的超时时限,单位是毫秒(ms)
  • context: 请求的上下文
  • responseType:请求期望的资源类型,默认为:text,可选值有: textjsonblobarraybufferdocument(此值 Violentmonkey 支持)
  • overrideMimeType: 请求的 MIME type 类型
  • anonymous:匿名模式,设为 true 则不发送任何 cookie 信息
  • fetch:使用 fetch 而不是 xhr 请求,Tampermonkey 支持此属性,且为测试功能
  • username: 用于验证的用户名
  • password:用于验证的密码

下面内容同样放在这个参数对象之中,它们是一些事件的处理方法。

  • onabort: 如果请求被终止,则执行此操作
  • onerror: 如果请求出现错误,则执行此操作
  • onloadstart: 当请求开始加载,则执行此操作
  • onprogress: 当请求的进度发生变化,则执行此操作
  • onreadystatechange: 如果请求的状态发生改变,则执行此操作
  • ontimeout: 当请求超时,则执行此操作
  • onload: 当请求完成,则执行此操作

执行上述事件所对应的处理方法时,会传入如下参数:

  • finalUrl:从请求中返回的重定向地址
  • readyState:请求完成的状态,这是一个数字
  • status:请求的状态,这是一个数字
  • statusText:请求状态的文本
  • responseHeaders: 响应的头部信息
  • response:响应的数据对象
  • responseXML:将响应数据当作 XML 文档读取,Tampermonkey 支持此属性
  • responseText:将响应数据当作纯文本文档读取,Tampermonkey 支持此属性

发出请求会返回一个对象,这个对象有如下属性:

  • abort:这是一个用来终止该请求的函数

以上便是油猴子中比较通用的接口。

你可以尝试将上面举例的代码放入自己的脚本之中,运行一下,看看能不能得到预期的效果。
【返回目录】 | 【下一章 需要的基础知识】

【本文为付费内容,如您尚未付费请点此】

【返回目录】 | 【上一章 油猴子的接口】

需要的基础知识

校对完成,更新时间:2020-07-23 13:40:55

如果你已经具备前端基础,那就别听老鼠在这里磨叽了,动手写吧,你可以的.

如果你对基础有所了解,但要想知道的更多,就去认真的研读文档吧。在技术精进的道路上,除了学和做真的没有任何捷径。如果谁说有,那么把他打死,因为他和小老鼠讲的不一样,哼唧~(霸道!

如果你毫无基础,那么往下读,我会告诉你一些最基本的东西,让你可以做一些非常简单的操作,从中获取成就感,也更有利于你对这些技术有一个比较直观的印象。

但是就不要期望我完全展开,因为那样真的就成了大长篇的教程了。如果对前端有兴趣,推荐阅读 MDN 的开发文档;或者阅读我书写的《代码能有多难?》( https://2019.dmnydn.com/ ),相对通俗浅显的将基础知识串讲了一遍,可能更有利于入门吧~如果你想学,又觉得难以坚持,那就来找小老鼠,我们一起探讨,或者让老鼠拿着小皮鞭每天督促你。

是的,以上是广告。这是多么良心的广告啊,难道你不想多读几遍么?!

以下所讲的内容,是写油猴脚本所必须的最基础的东西。当然别抬杠,就算是最基础的东西,也有可能一直都用不上它。一日三餐还是很基础的事情呢,但中午才起床的同学可能几年都没吃过早餐了。

HTML

认识标签,了解标签的结构,还有网页的基本结构。

下面是标签的最基本结构:

<这是开始标签>这是标签里面的文字或者代码</这是结束标签>

像这种东西,只要你不一看代码就吱哇的喊着:难呀难呀~应该有两分钟时间,自己就参悟透了。

<开始标签 属性名=“属性值”>内容</结束标签>

上面这样就给这个标签增加了一个属性。然后你看着代码对号入座就行了。

不知道有哪些标签,不知道有哪些属性,那你就先以为你不知道的就用不上,掩耳盗铃就很快活。因为油猴子很多时候都是去修改别人的网页,所以并不一定要求你对这些东西有特别深的了解,我也不知道你这是啥,我也不知道有什么用,但我就是想把这里的 1 改成 2,这没问题。

CSS

如果你想改变网页的样式,那需要学习 CSS;如果你想要一些看起来酷炫的动画,那最好也学习一下 CSS。

如果你只是想修改网页中的某个基本样式,那也许不需要深入的学习,只要能够通过开发者工具去查看这个元素所具有的样式,然后尝试修改就可以了,反正这些样式的属性名写的还算是比较直白,只不过是用英文单词去表述它的功能而已。大部分英文单词也都是非常基础的说。

但是,CSS 的选择器是必须学会的,想操作一个元素,你总要告诉脚本我要操作的是哪一个元素。而描述这件事情,就需要选择器的帮助。

这里简单的讲一下,记住,我只是简单的讲一下(强调!),所以还有更多更复杂的选择器,如果展开讲,你可能会看睡着的,而且有一些真的非常不常用。(但是学会了冷门的技能,遇到特殊情况的时候,就可以显示出自己比别人牛逼的地方来了,诶嘿嘿,我看你骨骼清奇,要不要跟我学习啊~~

选择器

  • 用标签选择: 就直接用标签名进行选择, a 指的就是链接,p 就是段落
  • 用 id 选择: 如果这个元素具有 id 属性,我们可以用井号加它的 id 去选择这个元素,比如:#meow
  • 用 class 选择: 如果这个元素具有 class 属性,我们可以用英文的句号加上它的 class 去选择这个元素,比如:.meow
  • 用通配符选择: 星号代表一切元素
  • 后代选择:把上面这些组合使用,中间用空格分隔,就表示什么里面的什么,比如:#cat .meow 就是说我们要找的是 id 为 cat 这个元素中所有具有值为 meow 的 class 属性的元素。
  • 子元素选择:如果把上面的空格换成大于号(大于号左右可以加上空格,只是为了好看),就表示什么里面的直接子元素中的什么。直接子元素就是,被套在里面又紧挨着的。反正就是儿子辈儿,不是孙子辈儿,也不是更下面的后代

JavaScript

这里就很重点了,因为这是我们主要写的东西,哪怕只是写一句代码,我们要写的也是它,这是真正绕不过去的。

有人跟我说:你就直接告诉我怎么去做这件事情,我不想听你从头讲起,深入展开。

如果可以,我也真的想这样做,但实际上那样你并没有学会,什么都没学会。都不如不学,然后请我喝杯咖啡,帮你写这句简单的代码。我这样说肯定有人不太相信,毕竟也有很多教程,告诉你一句代码解决什么问题,又好学又厉害,隔壁的小孩都馋哭了。

那我就按照这个思路给你们掰扯一下:我现在要把网页中所有链接都变成链接到我的网站:

document.querySelectorAll('a').forEach(e=>{ e.href = 'https://dmnydn.com/' })

这代码不算长吧,如果我说这是一句代码,虽然不是很严谨,但也绝对不算过分。现在你可以把这句代码拿走了,而且不需要我讲解,你就知道修改后面的网址可以改变最终的结果。

好了,一句话,学会一个功能,多厉害!但如果真的是零基础,除了最后那个网址,你还能修改什么?而且在特定的情况下只是替换网址都可以导致出错,出错了也不知道发生什么,那就是小老鼠教给你的技巧有问题,嗯,都是小老鼠的错。还有,如果页面中的链接是用 JS 控制跳转的,那这个方法可能修改不了,于是,这个方法没起作用,全是小老鼠不好!!

那么上面一行代码中包含着多少相关的知识呢,我列个表看看吧:

代码 对应知识 讲解难度(十分粗略)
document DOM
对象
系统对象
属性、方法、事件
……
仅基本理解这些概念
大概需要一万字
querySelectorAll 方法
元素对象
表面看几百字能说明白
但要了解相关方法也需要几千字
这个方法是选择所有符合元素的
还有只选择一个元素的方法
和一些其他选择方法
‘a’ 字符串
选择器
HTML 标签
HTML 基础
选择器本身就是一个比较复杂的事情
如果你想要准确的选择到自己想操作的元素
就必然需要对这方面有足够的了解
再加上 HTML 基础,得一万字以上才能讲清楚
forEach 数组
遍历
循环
都是重要概念,都是基础
只是弄明白这是在干啥
也需要三千字以上的讲解
e => {} 函数
箭头函数
函数的参数
形参和实参的分辨
元素对象
函数可是个大重点
用一万字讲解不算过分
e.href 元素对象
对象
这些前面已经提到过了
= 赋值
运算符
赋值操作对于没有基础的人
是第一个需要重点理解的概念
只要想再稍微多了解一点
就必然涉及到运算符的问题
聊个五千字完全挡不住啊
https://dmnydn.com/ 字符串 这个前面已经提到过了

所谓的讲解字数,你就当我是开玩笑就好,我只是为了让完全对此没有概念的朋友能拥有一个相对直观的感受。这个形容方式十分的不精确,因为讲解的字数并不能真正的表达这个问题的复杂程度,有些概念的一句话都需要我们认真的琢磨半天。

那就有人说了,我不想搞得这么深入,我就想对页面做一点非常小的修改,所以像上面那样,给我一句代码,告诉我这句代码是做什么的,就好啦。这样毫无痛苦的学习,难道它不香吗?!

你开心就好。那如果需要修改链接的其他属性呢?如果是修改其他元素的其他属性?不同的属性还有不同的特点呢,它们的注意事项也不相同……然后我们就为每个属性去记录一条代码,那笔记可就真成代码大全了。不过这也挺好,就这样穷举每一句可能的写法,这活儿能干到孙子辈儿。(如果技术停滞不前不再进行发展的话

如果一个人会煮凉面或者能用电饭锅做米饭,他说自己会做饭,也算能够接受的;如果一个人会炒鸡蛋或者哪怕是凉拌个黄瓜,他说自己会做菜,也算能够接受的。水平高低放在一边,但起码饿不着自己。然而有的人就希望别人把菜做好了,然后他端到餐桌上去跟大家说:别客气,尝尝我的手艺……这种我就不是很能理解。

下面我列举的这些知识,仅仅是让大家可以动手开始书写的基础,就是勉强可以让你下凉面、蒸米饭、炒鸡蛋、拌黄瓜的意思。

基础知识

常量、变量

我们在描述的时候不可能总是去说这个数据的值,为了方便就会使用变量或者常量去指代它们。连带出来的问题就是变量和常量的声明、赋值和作用域。

const a = 1
let b = 2

const 声明的是一个常量,let 声明的是一个变量。它们的作用域可以粗略的认为是它们所在的大括号范围。= 是赋值符,表示把右侧的值赋给左侧的量。

再次强调,这里只是非常粗略的告诉大家需要这些知识,并没有进行展开。如果需要了解,可以自己去查阅文档。查询所需要用到的关键词,我基本都已经讲出来了。

运算符

上面的赋值符也是一种运算。我们最熟悉的就是加减乘除。但除此之外还有非常多的运算符,了解它们会带来许多便利,让代码更加简洁。

数据类型

基本的是数字,然后我们会经常用到字符串,就是一串字符,它们会用引号进行包裹。

然后是布尔型,布尔型很简单,只有两个值:truefalse ,分别表示真和假,或者说成立和不成立。当我们判断两个值,它们是否相等,或者大于、小于、大于等于、小于等于这类关系时,就可以获得一个布尔型的数据,就是讲这个关系是成立还是不成立。

数组,当你有一组同类的数据时,可能就需要用到数组。当数据的结构变得更加复杂,就需要用到对象。这两个概念对于新人理解起来有一点困难,初期可以考虑先把它们绕过去,熟悉了简单操作,再去慢慢理解。

判断

如果小括号里的条件成立就执行大括号里的内容,如果不成立就执行 else 后面的大括号。

if(这里是条件){
	// 这里是条件成立时要执行的代码
}else{
	// 这里是条件不成立时要执行的代码
}

还有一些其他的变种格式,上面这个仅仅是最基础的,如果是非常简单的判断操作,可能我们会使用三元运算符解决。

循环

随便换一种就可以,如果仅仅说循环,那么肯定推荐使用 while 循环,因为它书写起来和判断差不多:

while(条件){
	// 如果条件达成就循环执行此大括号的内容,直到条件无法达成
}

但是,for 循环是无法躲避的,因为在很多情况下 for 循环都更加好用,但是它的格式挺多的,这里我只举例一个最基础的:

for(let i=0; i<10; i++){
	console.log(i)
}

以上是起码要会的,已经算是极限压缩了。下面列举一些要用到的对象:

有用的对象

数学

Math 对象包含很多数学方法,稍微复杂一点的运算都可能用到它。

字符串、数组、对象

这三个数据类型本身会带有一定的对它们的操作方法,了解这些方法会带来很多便利。

window

网页中的一切都在这个对象之上,怎么也得认识一下它吧。

document

网页中的元素都在这个对象之上。

网页元素对象

网页元素都具有哪些属性,如何进行获取和设置,这应该算是修改网页的基础操作了。

时间日期对象

Date 对象,涉及时间就会用到它,弄个时钟日历啥的,都和它有关。


这些就根据你希望侧重的方向和需求去了解就好。如果对某方面感兴趣,一定记得自己去阅读文档哦。遇到问题的话,可以来找小老鼠。

下一章我会给大家演示一些基础的操作和套路,算是给大家几块基础形状的积木,供大家尝试和理解。

【返回目录】 | 【下一章 基本操作演示】

【本文为付费内容,如您尚未付费请点此】

【返回目录】 | 【上一章 需要的基础知识】

基本操作演示

校对完成,更新时间:2020-07-23 14:31:52

这些操作并不会很全面,主要作为演示,供大家了解。尽量去顾及趣味性,并有一定的实用性。

想到什么就写什么,并没有特定的条理,后面有时间我可能会再完善一下。

基本

最简单的输出方式

console.log('这里是要输出的内容')
const text = '也可以把这些内容放在一个变量或者常量里'
console.log(text)

然后我们就可以在浏览器的开发者工具中看到这些输出了,它们不会对程序的运行造成太大的影响(如果有大量输出,会让程序明显减慢),这在我们调试代码希望弄清楚究竟发生了什么的时候非常有用。

alert('这也是要输出的内容')

这行代码会弹出一个提示,所以这个输出很明确也很直接,不需要打开开发者工具就可以看到,所以可以用作和用户的交互。但它会将程序暂停,等到关闭这个提示框之后,程序才会继续。

代数运算

let a = 1
let b = 2
let c = a+b*5
console.log(c)

除了需要对变量进行声明,其他的用法和我们学习的代数几乎一致(初级用法),就可以用来很方便的进行数学运算。

连接字符串

const text = '小老鼠'+'他好帅呀~'

我们对两个字符串做加法,就可以将两个字符串连接在一起。

模板字符串

const css = `
	body {
		background: yellow;
	}
`

这个我们在前面接口中,讲向页面里添加 CSS 的时候提到过。简单的讲就是用反引号(主键盘数字 1 左侧的按键)来替代引号,这样在字符串之中可以放引号和换行,就更方便在其中写入,比如 CSS 或者 html 的代码。

函数

()=>{}

对于初学者,又希望快速使用,那么可以先用箭头函数,因为写起来方便一点,不过在很老旧的浏览器里是不被支持的,如果你真遇到这种情况,请把浏览器换掉。

前面小括号里用来写参与这个函数执行的一些数据,后面大括号里是对这些数据进行的一些操作。函数其实将一些固定操作打包成一个整体,方便使用,

如果没有数据需要传入函数之中,那么就写一个空的小括号;如果只有一个数据需要传入函数之中,可以只写这个数据的名称而省略小括号;如果有多个数据要传入函数之中,那么小括号必须存在,并且中间用逗号分隔。

现在来定义一个做加法的函数:

const add = (a, b)=>{
    const result = a+b
	console.log(result)
}

我们定义了一个函数,然后用 add 来指代这个函数。现在仅仅是完成了定义,我们设计了这样一套操作,然后这套操作应该怎样去做。对这个事情都说清楚了,但并没有实际的进行操作。

下面来执行这个函数:

add(1, 2)

我要使用叫做 add 的这个函数,后面的小括号里放入这个函数需要的数据,然后程序就会完成我们刚才设定好的那一套操作,它会把两个数字相加,然后输出出来。

函数本身是一个比较复杂的概念,这里演示的十分简陋,只能是有一个大约的概念。有时候我们使用函数是为了将这些代码打包成一个整体,有时候是为了将这套操作方便的放入其他位置,而不至于因为代码太多,导致混乱。

网页

标题

document.title = '这个页面的标题已经被帅气的小老鼠占领了'

这是 document 对象下面的一个属性,表示的是我们页面的标题,就是显示在浏览器标签上的那行文字。我们可以像上面那样对它进行赋值,就修改了它的内容,也可以像下面这样将它的内容赋值给其他的变量,来获取它当前的值。

const title = document.title
console.log(title)

身体

document.body

这个一般不会单独使用,但是要注意页面中的所有元素基本都在这个对象之下,对应的是网页 <body> 这个元素对象。

同理可得,用 document.head 可以获取网页的头部信息。

网址

window.location

这个对象中包含当前网页网址的相关信息。大部分时间我们会这样用来获取完整的网址:

window.location.href

有了完整网址以后,只要稍加分析就可以获得网址相关的各种信息。不过其实上面的网址对象已经帮我们完成了初步的分析操作,大家可以将它输出出来,然后查看一下。比较常用的属性如下:

  • href: 完整的网址
  • hostname: 网站的域名
  • pathname: 域名后面的路径部分
  • search: 网址中问号及其后面的部分
  • hash: 网址中井号及其后面的部分

选择一个元素

document.body.querySelector('a')

这样我们就获取了页面中第 1 个链接元素,小括号中的这个字符串用来书写对应元素的选择器。这个方法只获得第 1 个符合这个选择器的元素,所以它获得的就是一个元素对象。

选择多个元素

document.body.querySelectorAll('a')

这样获取的就是页面中所有的链接元素,和上面代码对比多了一个 All,这就很合理。因为是多个元素,所以这个方法获得的是所有符合这个选择器元素所组成的数组。一般情况下这个方法后面都会连接 forEach(后面会讲)。

其实对于元素的选择还有很多方法,但对于新手快速掌握上面这两种是最方便的,因为直接使用选择器学习成本相对要低一点,通用性又很高。

元素遍历

document.body.querySelectorAll('a').forEach(e=>{  })

选择多个元素之后在后面直接接 forEach 对这些元素进行遍历,就是把这些元素都过一遍,对每一个元素执行相同的操作。后面的小括号里就是要进行的操作。

小括号里的内容是一个箭头函数,这里的 e 是我假定的一个值,用它来代表当前正在被操作的那个元素,就是现在轮到哪个元素它就代表哪个元素。就好像小学生排队打预防针,当然要一个一个进行,不过对每一个同学做的操作都是一样的。而医生和学生说话的时候,都是叫同学,但我们明白,他嘴里的同学是用来代表当前正在打针的那个同学。这样类比着去理解就容易一些。

然后大括号里再书写对元素的具体操作。很容易看出,这里是一个函数的定义。也就是程序会对每一个单独的元素去执行一下这里的函数。

元素操作

对于元素的大部分属性都可以用 元素.属性名 的形式调用,用起来和上面设置网页标题的操作一样。

document.body.querySelectorAll('a').href = 'https://dmnydn.com/'

我选择了一个链接元素,然后它的 href 属性被我重新赋值。这样就修改了这个链接的链接地址。其他属性也都类似,当然你只能给属性去赋予它能够接受的值,否则一般情况是不起作用。至于都有哪些属性,大家可以通过开发者工具去观察网页中的元素,不同元素可以拥有的属性也不太一样。

注意有一些特殊属性是没有办法按照上面方法进行操作的。

元素的内容

document.body.querySelectorAll('a').innerText = '小老鼠他超帅的'

这样我们就修改了这个链接显示的文字。如果一个元素中可以包含文字内容,那么就可以用这个方法进行设置。或者用它来获取这个元素中的文字内容。

元素的代码

但是上面的方法有一些问题,获取的时候,我们彻底忽略了元素中的其他代码。设置的时候这个元素里面就只能拥有文字了。那如果想让在这个元素里放入其他代码,可以用下面的方法:

document.body.querySelectorAll('a').innerHTML = '小老鼠他超帅的(<strong>确信</strong>'

追加元素

有时候我们需要向页面中插入某个元素,当然可以像上面那样去修改元素的代码,把我们的元素直接书写进去。但这样导致整个元素的代码都被重写了,原有的内容会被覆盖。

我们先获取原有的代码,然后通过修改来加入我们自己的元素。这看起来是可行的,但实际会有副作用。留下一句解释,新人可以忽略掉,因为很可能看不懂。这样的操作会导致这些代码中的元素全被更新,而绑定在它们上面的事件可能就丢失了。

正确的方法是像下面这样操作,比如我要在 <body> 元素中追加一个链接:

const newLink = document.createElement('a')
newLink.href = 'https://dmnydn.com/'
newLink.innerText = '可可爱爱'
document.body.appendChild(newLink)

第 1 行先创建了一个元素,它的标签是 a,下面两行用来设定这个元素的一些属性,最后将这个元素追加到 body 对象之中。

点击元素

document.body.querySelectorAll('a').click()

这样就对页面中的第 1 个链接元素进行了一个点击操作,这个方法可以用来自动化的完成一些事情。

数据

日期

时间日期是我最喜欢鼓捣的数据之一,因为用法简单,又十分实用,特别适合给新手用作练习。

那么先来一个时间:

const now = new Date()

我们说需要一个全新的时间对象,但是没有给出更多的要求(小括号里面是空的),于是它就返回了当前时间。所以这里我把这个时间对象放在了 now 之中。

它是一个对象,但是你可以把它直接输出出来,会显示成为一段文字,类似这样:Wed Jul 22 2020 16:14:59 GMT+0800 (中国标准时间)

我们也可以把它转换为数字,这时候代表的是时间戳,就表示这个时间相对于某个特定时间相差的毫秒数。也可以从中获取这个时间对象中的一些具体数据,比如下面这样:

const year = now.getFullYear()

这样就可以获取其中的年份信息,其他方法和详细解释请查阅文档。

类型转换

有时候我们需要对数据类型进行转换,比较常见的就是数字和字符串之间的转换。以下讲解不考虑无法转换而出错的情况。

const a = '123'
const b = +a
const c = Number(a)

这里的 a 是一个字符串,它的内容是一串数字。如果在它前面加一个正号,相当于进行了一次数学运算,所以程序会自动将它转换为数字之后再进行运算,这样得出的结果就是一个数字。而因为前面加正号,这个运算并不会对数字本身产生修改,所以就起到了类型转换的作用。

第二种写法(Number(a)),则是语义上十分明确,虽然长了一点,但还是很推荐使用。

const a = 666
const b = ''+a
const c = String(a)

两种方法的思路和解释都和上面类似.

操作

延时

window.setTimeout(()=>{
    // 一些操作
}, 2000)

小括号里是两个参数,第 1 个参数是一个函数,用来放我们希望进行的操作;第 2 个参数是一个时间,单位是毫秒。这样就会在经过这个时间之后再执行前面的函数。

window.setInterval(()=>{
    // 一些操作
}, 2000)

这段代码和上边非常相似,但它表示的是每经过这个固定的时间间隔就执行一次这个函数。

【返回目录】 | 【下一章 精通?!】

【本文为付费内容,如您尚未付费请点此】

【返回目录】 | 【上一章 基本操作演示】

精通?!

精通什么的,大概只会存在于毕业生的简历之中。

随着年龄的增长,越来越不敢去讲精通,甚至连自己是否会这项技能都有所怀疑。

而且用户脚本是会遇到许多限制的,这些限制是日常书写网页不太容易遇到的,所以仅仅是擅长写网页,并不一定擅长解决这些问题。

JavaScript 的整个知识体系也是巨大的,一般开发者很难做到全都了解。所以难免遇到许多知识上的盲区。而在知识的基础之上,还有解决问题的灵活性。如果是与人下棋,我们可以设法让局面走向自己熟悉的状况;但如果是解开残局,就是把自己扔到各种可能之中了。

如何进一步进行学习,我已经在前面讲了好多次。反正这种东西就是一个工具,学着玩儿呗。

如果你对前端有兴趣可以找小老鼠学习交流

那么都已经读到这里了,想必你已经付费了吧?!如果没有,记得补个票哦~

在后面我会不定期的放上来一些简短的小脚本,供大家研究,也向大家证明,几行代码也能够去完成一些有用的事情。

发布脚本

https://greasyfork.org/zh-CN 注册个账号,然后填表发布就好,限制不高。

或者简单的把自己的脚本放在网上,供人下载,就更没限制了。(但需要有自己的空间。

或者,脚本存在 Github 里面,然后让 greasyfork 自动同步,我喜欢这种方法,它可以绕过某些限制。而且很方便啊~

原来喵喵喵喵是这个意思啊.

其实比较希望能看到一个完整的可以使用的脚本的编写流程,函数、语句、关键字很容易就能找到相关的介绍,迷糊的是到底该怎么开始.

原本打算先做完文字校对再放示例的。

但是几万字嗷,自己懒得逐字检查,却发现中文的拼写检查工具没几个好用的,正在乱翻……

你等一下,我这就先上一个例子。

期待中

示例一:获取 B 站视频封面

需求分析:首先在视频列表页复制封面的图片地址,然后进入这个视频的页面,在源码中去搜索这个地址的关键词(就是文件名)。这个操作本身是碰运气,然后发现他把封面图片的地址清晰的写在了网页的头信息中。

<meta data-vue-meta="true" itemprop="image" content="http://i1.hdslb.com/bfs/archive/8ec1b95254e0e67cacad1191e918e66e06fabcbf.jpg">

这个问题就变得非常简单了,只需要获取网页中特定元素的特定属性即可。

那么下一个问题就是获取到的这个地址,如何输出出来。我选择的方法是直接将当前页面的网址修改为这个图片地址,因为这样最简单,而且图片直接显示出来了,如果是在手机上长按图片就可以进行分享。

接下来的问题是如何触发。如果这个功能在网页打开之后就立刻执行,得到的结果是打开一个视频页面,结果显示出来的是它的封面图。(那我以后还怎么摸鱼呀!!!

所以我选择给这个脚本添加了一个菜单,当点击这个菜单的时候才执行这个动作。


好了,问题分析完了,下面来看代码比解释少系列:

// ==UserScript==
// @name        获取 B 站视频封面
// @namespace   Get bilibili video cover
// @match       *://www.bilibili.com/video/*
// @match       *://m.bilibili.com/video/*
// @grant       GM_registerMenuCommand
// @version     1.0
// @author      -
// @description 2020/7/15 上午7:27:34 
// ==/UserScript==

GM_registerMenuCommand('获取此视频封面', ()=>{
  window.location.href = document.querySelector('meta[itemprop="image"]').getAttribute('content')
})

代码中相比我讲到的知识有两处超纲:

  • 这个选择器是稍微复杂一点的属性选择器
  • 这个元素的 content 属性似乎无法直接获得,于是用了 getAttribute 方法

但在操作思路上没有超纲的地方

示例二:论坛高亮楼主头像

有时候楼层比较高,看着看着就忘记了谁是楼主。本论坛看过的帖子再点进去或直接从你上次阅读的地方开始,就更容易遇到,看回复时不知道楼主是谁的尴尬。这会很影响我对回复内容的理解。所以就做一下高亮,让我能够识别出来。

本来也想像 V2EX 家插件那样直接对楼层进行高亮,但试了试,在本论坛的显示效果不好。那就高亮头像吧。然后我也记不清是从哪里复制了一下样式,反正挺简单的两行 CSS。

那怎么确定谁是楼主呢?这个问题我也挺犯愁的,因为如果直接打开是最先回复的位置,那么有可能顶楼并没有被加载出来,这就不太容易判断。但我觉得程序不会把这么重要的信息直接忽略掉,于是在代码中观察了一下,发现凡是楼主所在的楼层都会有这样的类 topic-owner。那问题就很简单了,就是给对应的元素添加上样式就好。

看下面代码,和我们讲添加样式的接口时所举的例子已经十分一致了。

// ==UserScript==
// @name        论坛细节优化
// @namespace   Appinn Forum Details Optimization
// @match       *://meta.appinn.net/*
// @grant       GM_addStyle
// @version     1.0
// @author      -
// @description 2020/7/13 下午2:05:15
// ==/UserScript==

GM_addStyle(`
  .topic-owner .topic-avatar img {
    box-shadow: 0 0 3px 1px #81c3e4;
    border: 2px solid #85c2e0;
  }
`)

示例三:链接地址洗白白(简化)

地址简化,主要就是这样几种情况:

  • 地址中的 get 参数可以清理,比如淘宝的搜索结果,只保留查询的关键词即可
  • 地址中的路径可以被出新组合,比如本论坛的帖子地址

对于第 2 种,可以利用正则替换来修改网址,简单理解就是查找替换。对于第 1 种,因为查询的参数前后顺序是不固定的,用正则难以处理这种顺序不固定的情况,所以先提取出查询参数,然后重组成自己需要的格式。

针对已知的网站设定规则,然后根据当前网站使用对应的规则进行处理。为了适配更多的网站,所以设置了一些常用的查询参数,对于为准确是配的网站,尝试只保留这些查询参数。

// ==UserScript==
// @name 链接地址洗白白(精简示例版)
// @namespace Daomouse Link Cleaner
// @version 0.0.1
// @author 稻米鼠
// @description 把链接地址缩减至最短可用状态
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @noframes
// ==/UserScript==
const cleanIt = ()=>{
  const url=window.location.href
  const hash = url.replace(/^[^#]*(#.*)?$/, '$1')
  const base = url.replace(/(\?|#).*$/, '')
  let pureUrl = url
  const getQueryString = function(key) {
    let ret = url.match(new RegExp('(?:\\?|&)(' + key + '=[^?#&]*)', 'i'))
    return ret === null ? '' : ret[1]
  }
  const pureUrl = base + '?q=' + getQueryString('q')
  GM_setClipboard(pureUrl)
}
GM_registerMenuCommand('链接净化', cleanIt)

大概就是这个样子,上面代码实现了一下对淘宝搜索结果链接的净化。其中正则表达式应用的很多,但是前面我们没有讲。

总之就是注册一个菜单,当我们点击这个菜单的时候,运行上面已经写好的函数。函数里面对当前网址一通操作,然后把修改完的地址放入剪切板。

只要你会使用正则,并且对网址的结构有基础的了解,做这样一个脚本并不困难。真正麻烦的是对规则的收集和维护。

不过这种方法并不能够解决所有网址净化的问题,不过那不在我们这次的讨论范围之内了。

示例四:自动展开全文(简化)

这个更简单,就做两件事情,让那个按钮不显示,然后去掉对内容部分的限制。

这两个问题都可以通过 CSS 解决,所以事实上就是对页面添加几句 CSS。麻烦的就是对每一个网站的适配确定这个网站中哪个元素是按钮,哪个元素是文章内容。

这个我就不放代码了,因为精简到只针对一个网站的话,和前面我们写的为页面加入 CSS 是一样的。

针对多个网站,就是遍历已有的规则,验证网址是否符合,如果符合就按照这个规则中记录的元素去设定相应的 CSS。

(代码不复杂,维护规则就……

示例五:网易云音乐歌词放大

没啥解释的,就是把当前(那一句)歌词的字号设置大一点,方便看歌词。

// ==UserScript==
// @name        网易云音乐歌词放大
// @namespace   Violentmonkey Scripts
// @match       https://music.163.com/*
// @grant       GM_addStyle
// @version     1.0
// @author      -
// @description 2020/5/24 下午6:18:58
// ==/UserScript==

GM_addStyle(`
  .m-playbar .listlyric p.z-sel {
    font-size: 22px;
  }
`)

这个例子就是告诉大家用已有的知识去解决小问题是可行的。我也确实在用这个脚本。

如果愿意折腾,把歌词弄到网页标题里也可以,这样切到别的页面也能看歌词。不知道如果动用浏览器的 Picture in picture 功能的话,能否实现桌面浮动歌词。我自己需求不大,就懒得折腾。

示例六:假装水墨屏

代码看这里吧: https://greasyfork.org/zh-CN/scripts/407641/code

原理很简单,为页面插入一段 CSS,其中有四段内容:

  • 页面滤镜,让网页变成黑白色调,在各种纪念日,网站变黑白用的就是这个方法。我这里增加了一层亮度调节,这样可以降低深色元素的浓度,毕竟水墨屏的纯黑也不是特别黑,而手机屏现在可以显示纯黑的,这差别很大。
  • 定义一个类,具有浅灰色背景,来模拟水墨屏本身的灰色(随着发展,现在的水墨屏越来越白了,但依旧不算纯白,加上手机电脑的屏幕本身比较亮,要灰一点才像
  • 定义一个类,具有深灰色文字,页面中深灰色的文字,全都换成这个值,让显示效果更好控制。
  • 让所有元素的文字具有阴影,借此模拟水墨屏文字发虚(现在高 PPI 设备基本不虚了),和屏幕残影造成的效果。

然后做了两个判断:

  • 每一个背景色为浅色的元素,设置为上面的浅灰色背景
  • 每一个文字颜色为深色的元素,调整我设定的那个颜色

其实直接把前面样式全局应用就可以兼容大多数小说站了,加入判断是为了更加严谨。


高级技法:

瀑布流和延迟加载的页面效果会很差。因为我们上面讲的这些,只是在页面载入后执行一次,后面新增加的元素并不会受到影响,背景和文字的颜色就不会被优化到。

所以加入和页面变化监控,当有元素变化时,对元素进行上述两个判断,按需要修改。这就解决问题了。

但是下一个问题又来了:当新元素需要修改时,我们修改了。这个元素不也发生变化了么,那监控元素变化的部分就会再通知我们这个元素变化了……然后陷入死循环(可能)。所以,当我们对元素进行修改时,要先停止对元素变化的监控,修改后再重新开启监控。

这部分比较比较复杂,不推荐新手现在啃。就是先了解些思路。这里算是展示一个看起来很简单,但其实挺复杂的脚本。

为了尽可能提高自己脚本的兼容性,所以十分诚心诚意的去尝试手机上那些号称支持脚本的浏览器。

以前我对他们的认知只限于他们介绍中的说明,并未实际使用过。现在体验下来大跌眼镜,我是真的用来。

如果支持从市场中直接安装脚本的体验,还算好一丢丢,但是可能浏览器自建的市场中收入的脚本非常少,并且收入之后不再及时更新。如果自己新建复制粘贴进去,因为好多脚本的代码比较长,在手机上的操作体验是非常差的,甚至多次操作才能成功。

然后自己新建的话一般匹配网址部分需要自己单独额外输入,如果匹配多个网址,在手机上的输入体验,依然让人抓狂。

既然在输入格式上都不能脚好的去适配油猴规范,就很难指望他们能够提供对应的接口。前面我们说过这些接口是给开发者带来便利的,所以在有需要的情况下,开发者肯定会优先选择使用这些接口。这就导致这些浏览器对脚本的兼容性必然很差。

感觉他们对脚本的支持,都处于小书签的水平。都怀疑是用 window.location.href = 脚本代码 来实现的。这种情况下,想对它们进行兼容十分困难。

可以的,谢谢

原来可以用 window.location.href = 脚本代码 这种方式自动调用小书签啊

学到了