从【一键复制网页标题和地址(油猴脚本)】所收获的知识点
文章更新:2021年7月19日15:26:07
前言
这应该是可以看做是我自己从产生想法到最后完成的我的第一个作品,虽然是个很简单的小功能,但是还是特地做一下记录吧。我给自己的蓝色小按钮取名为“福妞”,取“用来复(福)制的按钮(妞)”之意。这个脚本的组织结构还很拉胯,但是功能方面倒是已经可以满足我了,所以重构的工作等以后再说吧,这一应该不会是最后版本。
为什么要搞一个看起来好像没啥大用的脚本呢?究其根本,还是一个字:懒!因为我之前每次用Markdown做一些记录要用到参考链接的时候,都是要先复制标题、地址之类的再写上[]()
这样的模板,我感觉切来切去很麻烦,大大降低了我写日志的热情。于是我便对这个工具密谋已久,但是还是因为懒,迟迟没有动手。最后还是因为那段时间需要大量地用到参考链接,实在是受不了了,太不方便了!所以就下定决心要做这么个小工具。懒是耽误大事的重要因素,也是推动生产的要素之一……
我是在2021年5月27日左右初步完成功能的,之后几天好像还有个阿里本地生活化的面试,当时我啥作品也没有,本来还打算要是面试官问我做过啥东西,那我就把硬着头皮把这个给拿出来说说吧,结果面试时出现了意外:面试官先是打电话给我说先让我做大概四十分钟笔试,然后在打电话给我,笔试过程表现非常差,因为那个时候我的JavaScript都还没怎么学全,笔试里有个Promise
的,当时就懵了,当时还没学过啊……后来我还在答题区留了句:I’m definitely a vegetable dog…大概四十分钟后,我盯着手机屏幕,面试官确实给我打电话了,但是没想到的是,我的手机居然把电话拦截了,之后面试官也没打电话给我了,我自然也没法儿回拨了……我就眼睁睁看着一个谈话机会被我的智障手机给自作聪明地拦截了,淦!关键是,我还为了这次面试,人生中头一回去一个小宾馆开了三小时的房,结果……虽说就算接到了电话也大概率过不了,但是还是很难受……
不知道怎么回事,我的greasyfork账号一开始没法儿发布脚本,索性一番周折终于算是放上了greasyfork。
更新:2021年7月19日21:26:54
0、效果
1、如何编写油猴脚本
更新:2021年7月19日10:59:43
更新:2021年7月19日11:15:33
更新:2021年7月27日17:11:58
参考:如何开发一个油猴(TamperMonkey)脚本 - 简书
参考:OpenUserJS
更新:2021年7月19日11:13:13
更新:2021年8月30日24:05:00
2、JavaScript读取剪切板
更新:2021年7月19日11:34:13
参考:剪贴板操作 Clipboard API 教程 - 阮一峰的网络日志
参考:浏览器原生剪贴板 navigator.clipboard与document.execCommand 及 vue-clipboards使用_Umbrella_Um的博客-CSDN博客
// ==UserScript==
// 脚本名称
// @name 复制标题和地址(myFirstScript)
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 复制标题和地址(myFirstScript)
// @author LiarCoder
// 在哪些页面生效, 支持通配符
// @match *://*/*
// @grant none
// ==/UserScript==
我以为把上面的@match参数改成 *://*/*/
之后就可以解决在阮一峰博客无法使用我的脚本的bug,结果还是没有效果,后来通过控制台观察,看到了【无法从 undefined 中找到writeText属性】这个错误,那就说明是 navigator.clipboard.writeText(result);
这句出了问题,通过不同网页的控制台测试,发现其他网页中是有navigator.clipboard
这一属性的,而且也可以使用writeText
方法,于是我又瞄了一眼阮一峰那篇讲剪切板的博客:http://www.ruanyifeng.com/blog/2021/01/clipboard-api.html,发现了错误所在:Chrome 浏览器规定,只有 HTTPS 协议的页面才能使用这个 API。我尝试在http
后加了个s
然后回车,发现可以跳转至相同内容的页面,而协议也变成了https。于是我就开始找把http
转换成https
的方法,没有找到合适的答案,后来就记起来好像location
这一对象中有许多有关页面地址的属性,于是就开始试验,发现了location.protocol
就是我想找的,一开始
我想通过正则表达式把http
换成https
,然后再让浏览器重新跳转至https
协议下的网页,后来我发现只要直接对location.protocol
重新赋值就行了,所以在使用navigator.clipboard
接口前做一下判断,如果当前页面不是https
协议,那就把它换成https
再说!
if (location.protocol !== 'https:') {
location.protocol = 'https:';
}
navigator.clipboard.writeText(result);
试验过后完美解决我的问题。只不过遇到http
协议的网页要重新跳转一下了,多花了点时间。
更新:2021年7月19日16:22:30
突然发现问题并没有完美解决,有的网页不支持https
,要是强行将http
改为了https
,那么可能就会报错。比如以下网址:
参考:中国软件杯2015-01-23-第四届“中国软件杯”大学生软件设计大赛赛题(B):基于-HTML5的动态数据3D展示软件
但是碰上这样的问题也目前也没啥办法解决了,因为只有在https
协议下的链接或是localhost
才能往剪切板写入数据。
更新:2021年10月5日16:00:08
这个问题已经用某种替代的方法解决了,就要把当前的标题和地址信息包装成Markdown格式后放入一个自定义的弹框组件,由用户手动复制。
3、通用的createEle函数
// 该函数用于创建一个<eleName k="attrs[k]">text</eleName>样式的页面元素
function createEle(eleName, text, attrs){
let ele = document.createElement(eleName);
// innerText 也就是 <p>text会被添加到这里</p>
ele.innerText = text;
// attrs 的类型是一个 map
for (let k in attrs) {
// 遍历 attrs, 给节点 ele 添加我们想要的属性
ele.setAttribute(k, attrs[k]);
}
// 返回节点
return ele;
}
4、iframe标签相关问题(iframe标签;self和this;同源限制)
我最开始发现iframe
引起的问题是在QQ邮箱记事本中的文本输入框旁边出现福妞(这个是预期之外的),同时网页的左上角也出现了一个福妞(这个是预期之内的),当时我没有在意,只是在油猴的排除网址(油猴允许开发者设置类似黑名单网址列表的东西,在这个网址中该油猴脚本不起作用,且支持正则表达式)加入了QQ邮箱的域名,问题暂时解决。后来我又在阿里巴巴的校园招聘的官网登录界面发现除了页面左上角那个应该出现的福妞之外,在登录框组件也惊现福妞的身影。于是我就决定认真把这个问题给解决。
我通过开发者工具定位相关元素,发现这个登录框是被嵌入在一个<iframe>
标签中,而在这个标签中也有一个完整的HTML网页结构。
<iframe id="alibaba-login-box" src="……">
#document
<!DOCTYPE html>
<html lang="en">
<head>
<title>登录</title>
……
<head>
<body>
……
</body>
</html>
</iframe>
于是乎,我们可以发现,一个网页里出现了两个body
元素,因为我的福妞是通过document.body.appendChild();
这种方式来追加到页面的DOM结构中的,所以就导致了页面中出现了两个福妞(要是一个页面中有多个iframe
标签,那按理来说自然也就会出现多个福妞,比如在 - HTML(超文本标记语言) | MDN 这个页面中,就可能出现多个福妞的身影)。于是我便开始寻找判断当前页面是不是在<iframe>
标签内的方法。最终经过测试,采用了下面的写法。
// document.body.appendChild(style); // 这种写法会导致脚本在<iframe>标签的html文档的body标签也被选中
// self === top 是用来判断当前页面是否是在<iframe>标签内,如果为true则表示不<iframe>标签内
if(window.self === window.top) {
// 下面这个判断是我看【网页限制解除(改)】这个油猴脚本里的写法,感觉更全面一点。
// 但是其实我一开始改的目的是想解决我的脚本在阮一峰的网络日志里不起作用的事,
// 但是后来发现不是这里的问题,而是上面写@match参数的时候没有考虑到网络协议是http的情况,
// 所以一开始写成了@match https://*/*,而恰好阮一峰的博客所用的协议是http,
// 所以怎么都显示不出来我的福妞,原来是压根就没匹配到,所以改成了@match *://*/*/,
// 这样一来所有网页理论上都会有我的福妞了
if (document.querySelector('body')){
document.body.appendChild(style);
} else {
document.documentElement.appendChild(style);
}
// document.querySelector('body').appendChild(style);
// document.body.appendChild(style);
}
更新:2021年5月31日23:46:49
参考:(方式三经测试有效)js判断一个元素是否在iframe里面 - 星空飘渺 - 博客园
参考:(没试过)html判断当前页面是否在iframe中_小单的博客专栏-CSDN博客_判断页面是否在iframe中
参考:(经测试,好像不大好用)如何检查元素是否在iframe中 - IT屋-程序员软件开发技术分享社区
参考:Node.ownerDocument - Web API 接口参考 | MDN
// js判断一个元素是否在iframe里面
//1.方式一 (在我的脚本里好像不起作用,而且下面的"IFRAME"似乎必须得是大写)
if (self.frameElement && self.frameElement.tagName == "IFRAME") {
alert('在iframe中');
}
//2.方式二 (没试过)
if (window.frames.length != parent.frames.length) {
alert('在iframe中');
}
//3.方式三 (亲测有效)
if (self != top) {
alert('在iframe中');
}
5、福妞在某些网页中的显示样式与预期不一样
主要是调教下面这个initialBtnStyle
。注意下面的\
是因为一行太长了,用它来分割为多行,JavaScript知道这是啥意思。
let initialBtnStyle = '#copy-title-and-location {position: fixed; top: 100px; left: -100px; z-index: 2147483647; opacity: 0.5; background-image: none;\
background-color: #0084ff !important; margin: 5px 0px; cursor:pointer; font-size: 12px; line-height: 17px; width: auto; font-family: Arial, sans-serif;\
color: #fff; border-radius: 3px; border: 1px solid; padding: 3px 6px ; transition: left, 0.5s; height: 26px;}';
-
一开始发现在知乎和百度的页面中,我的福妞显示样式有点不一样,在知乎页面中明显更大一些。于是经过开发者工具调试发现是不同的网页对于body、html等较高层的元素有相关的样式设置,其子元素就继承了部分设置。我在上面的
CSS
样式设置中特意加了font-size
和line-height
两个属性,并写死了相关的数值,我发现我的脚本小按钮在不同的网页中有时会有不同的显示效果,所以想要一致的显示效果,把这两个属性写死就可以了。 -
在这个博客园的网页上(https://www.cnblogs.com/llhthinker/p/6719779.html)我的按钮显示效果与预期不同,怎么调都没反应。具体就是在网页中我的福妞没有显示出来。但是我通过开发者工具看到网页中是有我的福妞这个网页元素的,但是却被隐藏在了网页左侧(就有点像是
left
值的负值设置得太大了的那种感觉),于是我开发者工具中调整相关的属性数值。最终发现将福妞的width
值给设置为一个固定值:124px
,这样就解决我的福妞在这个网页不显示的问题了。这中间还有个小插曲,那就是我把width: 124px
里面的英文冒号:
写成了中文冒号:
,结果发现我明明改了相应值,却死活没有反应。后来看到谷歌开发者工具中看到width
属性之前有个警告的符号,将鼠标移上去一看,发现它提示:Unknown property name
,我这才意识到自己把冒号给打错了……(淦,小丑竟是我自己) -
【更新:2021年6月20日23:20:01】在苹果官网发现了按钮样式有点奇怪,所以加上了
font-family
属性,还顺便把width
由固定的124px
改成了auto
。 -
【更新:2021年7月13日00:47:55】在https://www.linuxidc.com/Linux/2016-11/137495.htm发现按钮的高度不对劲,通过开发者工具发现了该网页把button元素的高度设置为固定的
22px
,于是我在上面的样式中加上了height: 26px
将高度固定为合适值。 -
【更新:2021年6月22日00:33:02】在outlook官网发现我的按钮的那个复制图标异常得大,样式完全不符合预期,通过控制台发现是
img
的width
属性被默认设置为了100%
,尝试设置为auto
之后就好了。let imgTag = createEle('img', '', {src:icon, style: "width: auto; vertical-align: middle; margin-left: 10px; border-style: none;text-align: center;display: inline-block;margin-bottom: 2px"});
-
【更新:2021年7月20日11:00:16】在 MDN文档页面,我发现我的福妞的复制图标无法显示出来,而是表现为图片加载失败的样子,按理来说不对啊,因为我的图片的格式为
base64
,于是我在开发者工具里将<img>
标签的src
替换为其他的图片链接也是一样,而在其他页面却可以正常显示。不知道是啥原因……【更新:2021年10月5日23:38:08】上面的MDN文档页面图标显示异常的问题已经解决。
![]() |
---|
![]() |
-
【更新:2021年7月27日17:10:16】在 Tampermonkey • 文档 页面,我发现我的福妞又不见了,在开发者工具中倒是找到了相关元素,老规矩,调整
left
值后,福妞现身了,同时也发现了福妞不见的原因:<img>
标签里的图片没有显示。我将鼠标移动到<img>
的src
属性上(是base64
格式的),发现有期待中的图标出现。难道是这个页面不支持解析base64
,于是我将src
属性换为另一个图片的在线链接地址,发现正常显示,于是我就将图标上传到图床,用在线图片链接代替原来的base64
编码。问题算是解决了。不过可惜的是,上面说的那个MDN文档页面的问题没有一并解决。【更新:2021年10月5日23:38:08】已经将
<img>
换成了svg
。 -
【更新:2021年10月4日12:48:25】在 browserslist详解 - 简书 里的这个链接 Help Browserslist 所指向的页面,我的福妞又一次出现了样式问题,就是
<img>
标签的宽高有点问题,图标显得特别大,但是又找不出啥原因……【更新:2021年10月5日23:38:08】将
<img>
换成svg
后已解决。 -
【更新:2021年10月6日15:37:30】在 首页 | 技术胖-胜洪宇关注web前端技术-前端免费视频第一博客 这个网页中,我发现弹框的样式有点奇怪:
通过开发者工具仔细观察之后发现是由于这个网页里的盒子模型和其他的网页有点不一样:
于是我就改变了一下弹框的宽高设置,宽度还是设置为固定的 450px
,而高度不设置,而是让内容自动撑开。同时将弹框内部的 textarea
的宽度由原来固定的 445px
改为 100%
。同时由于又发现上面那个网页中对于 padding
的解析比较让人迷惑,于是我就改变了原来那个 关闭
按钮的定位方式。最后终于算是解决了问题。
- 【更新:2021年10月15日20:51:51】在阮一峰的个人博客里,我想用我的按钮,但是发现怎么点击都没有用,按理来说应该会有弹框出来的,因为当前页面是http协议。后来经过排查,发现是ADGuard这个广告拦截扩展里有这么一条拦截规则:
意思就是如果 div
标签里有 style
内联样式,且该样式中有某个属性设置了 !important
的话就会被广告拦截扩展给拦截,具体的表现就是将该 div
的 display
属性强制设置为 none
。而我为了让弹框的背景遮罩在黑暗模式和白昼模式下都显示为透明的白色,所以给背景遮罩设置了 background: rgba(243,242,238,0.8) !important;
,这样一来,就会匹配到上面所说的拦截规则,于是乎,弹框就不会出现,尽管在Element面板可以看到该元素。但是在其他网页却不会出现这个问题。没办法,我就将一张透明度为80%的白色图片作为该背景遮罩的背景图,这样的话,就能暂时适应大部分情况了。
6、获取本地时间并改造成自己想要的格式
一开始我的脚本是没有:【更新:xxxx年x月xx日xx:xx:xx】这部分的,后来我觉得应该把写日志的时间也给记下,就加上了这个功能。但是JavaScript提供的有关获取时间的方法中没有哪个可以直接返回我上面想要的那种格式,于是我就在JavaScript提供的原生方法之上弄了个自己想要的时间格式。
let date = new Date();
// 原生方法
date.toLocaleTimeString();
"下午2:38:52"
// 改造方法(这里有点小问题,那就是晚上12点的话,以下方法会返回24:xx:xx而不是我想要的00:xx:xx,但无伤大雅
date.toLocaleTimeString('chinese', {hour12: false});
"14:38:38"
// 原生方法
date.toLocaleDateString();
"2021/7/19"
// 改造方法
date.toLocaleDateString().replace('\/', '年').replace('\/', '月') + '日'
"2021年7月19日"
// 最终格式
let timeStamp = date.toLocaleDateString().replace('\/', '年').replace('\/', '月') + '日' + date.toLocaleTimeString('chinese', {hour12: false});
"2021年7月19日14:38:38"
更新:2021年7月19日14:50:02
7、base64与图片互转
下面的在线转换工具是我试过了几个之后觉得最好用的。
更新:2021年7月19日14:55:14
更新:2021年7月27日20:37:40
8、油猴脚本的name和description不能一样
否则在greasyfork.org上提交时会出现错误而不允许你提交。
// ==UserScript==
// 脚本名称
// @name 复制标题和地址(myFirstScript)
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 一键复制标题和地址(myFirstScript)
// @author LiarCoder
// 在哪些页面生效, 支持通配符
// @match *://*/*
// @grant none
// ==/UserScript==
9、添加try…catch
更新:2021年9月13日24:06:16
测试网页:Word里面如何删除空白页?删除Word空白页的六种方法_OF软件站
测试网页:ASCII码字符对照表
// 【更新:2021年9月12日22:48:36】添加了一个try catch来应对当前页面不能访问 navigator.clipboard 对象的问题
try {
navigator.clipboard.writeText(result);
} catch (err) {
console.log('当前页面不支持访问 navigator.clipboard 对象:' + err);
// myAlert 是我自己实现的一个简易的弹框组件
myAlert(result);
}
// 之前是直接往剪切板写东西
// navigator.clipboard.writeText(result);
更新:2021年9月13日24:00:01
10、通过AJAX在进行页面跳转前先判断目标页面是否支持https
有些页面是不支持通过https协议访问的,所以在替换protocol前,先试着用https协议访问目标页面。根据访问结果来判断是否替换protocol为https。
if (location.protocol !== 'https:') {
// location.protocol = 'https:';
// 【更新:2021年9月12日23:25:40】把简单粗暴的替换protocol改为了先用AJAX访问,根据访问结果来进行后续操作
let newLocation = 'https:' + location.toString().slice(5);
//1. 创建对象
const xhr = new XMLHttpRequest();
//2. 初始化 设置请求方法和 url
xhr.open('GET', newLocation);
//3. 发送
xhr.send();
xhr.onreadystatechange = function() {
//判断 (服务端返回了所有的结果)
if (xhr.readyState === 4) {
//判断响应状态码 200 404 403 401 500
// 2xx 成功
if (xhr.status >= 200 && xhr.status < 300) {
console.log('可以跳转');
location.protocol = 'https:';
} else {
// alert(result); // 原生alert弹框
// 注意下面的那个换行,这里有个问题,就是当复制弹窗中的文本到typora中时那个 > 符号前会有一个转移符号,目前还不知道如何处理
result = '更新:' + timeStamp + '<br> > 参考:[' + titleTag.innerText + ']' + '(' + location + ')';
myalert(result);
}
}
}
}
更新:2021年9月15日13:10:26
突然发现这种在替换protocol之前发AJAX的方法也不是那么“智能”。因为在下面这个测试网页中:
如果把原来的http强制改成https的话是可以成功跳转的,但是用AJAX发请求的话,就会出现一个跨域问题:
也就是说,所有的从http强制改为https的行为都会走 myalert(result);
所在的那个 else
分支,这就很头疼了,这不是我想要的效果啊……所以说,现在就是有些网页强制改protocol是可以跳转的,这种情况我们就让它跳;但是有些网站是无法用https访问的,那这种情况就需要我的自定义弹框了……头疼……
更新:2021年9月15日13:39:30
11、实现一个自己的弹框组件
// 【更新:2021年9月13日00:04:08】因为原生的alert弹框里的文本不能复制,所以我决定自己实现一个弹框以便我们手动复制结果
function myAlert(msg) {
if (document.getElementById('alert-box-iVBORw0KGg')) {
return;
}
let alertBox = createEle('div', '本网页不支持操作剪切板,请手动复制下方内容:', {
id: 'alert-box-iVBORw0KGg',
style: `position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
border-radius: 4px; padding: 20px 20px; width: 450px; height: 160px;
background: #292A2D; color: #ffffff; line-height: 20px; z-index: 300;
font-size: 12px; font-family: Microsoft YaHei;
`});
let msgBox = createEle('textarea', '', {
style: `width: 445px; height: 80px; font-size: 12px !important; margin: 15px 0; resize: none; background: #292A2D; color: #ffffff; border: #292A2D; outline: none;
`});
msgBox.innerHTML = msg;
let closeBtn = createEle('button', '关闭', {
style: `width: 64px; height: 32px; float: right; border: #799dd7; border-radius: 4px; background: #799dd7; outline: none;
`});
closeBtn.onclick = function () {
alertBox.parentNode.removeChild(alertBox);
}
alertBox.appendChild(msgBox);
alertBox.appendChild(closeBtn);
document.body.appendChild(alertBox);
}
更新:2021年9月13日24:05:56
更新:2021年10月5日12:58:00
参考:textarea 限制宽高 固定宽高 拖拽 - 酸柠檬 - 博客园
12、如何操作浏览器右键菜单
更新:2021年9月20日21:29:37
参考:javascript自定义浏览器右键菜单 - 爱你爱自己 - 博客园
参考:js实现浏览器右键菜单,屏蔽默认菜单_http://kingim.cn/-CSDN博客_js屏蔽右键菜单
PS:上面这个参考链接的形式还挺有意思的~
13、设置按钮被点击时的动效
更新:2021年10月5日23:40:15
14、设置网页遮罩
更新:2021年10月10日18:12:37