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

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

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

如何调试脚本

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

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

最基本,最重要

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

开发者工具

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

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

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

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

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

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

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

断点

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

debugger

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

片段

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

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

Code Runner

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

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

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

耐心

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

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


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

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

1 个赞

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

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

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

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

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

发布脚本

Greasy Fork - 安全、实用的用户脚本大全 注册个账号,然后填表发布就好,限制不高。

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

或者,脚本存在 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 = 脚本代码 这种方式自动调用小书签啊

学到了

好多知识冷的叫人发抖。真的可能掌握以后直到这技术淘汰都用不上。

但是玩油猴子这种,就免不了时常需要一些冷知识,也挺有趣的。