1. 背景
之前写过两篇类似的文章:
在这两篇文章中都是使用同一个例子来演示:让网页链接默认在新标签页中打开。
在写这两篇文章的时候,我也在思考还有没有别的使用场景,然后,突然想要有的时候我们可能会有编辑网页的需求,比如:我要用 SingleFile 这个浏览器插件来保存网页,或者就是直接使用浏览器的 Ctrl + P 来打印网页,或者是需要截图网页的时候,结果发现网页中竟然有错别字。
如果想要修改这个错别字,或者想要增加、删除一些内容,那么就需要到开发者工具中去执行修改,因为网页默认是不能被直接编辑。那如果不想打开开发者工具呢?能做到吗?
能!用我接下来说的方法,可以切换网页能否编辑的状态,也就是:点一下书签,网页就可以编辑了,再点一次书签,网页就无法编辑了。
2. 原理
就是利用元素的 contentEditable
这个属性来实现的。由于这个属性是可被继承的,所以,我们只需要给 body
标签设置这个属性的值为 true
即可让整个网页的内容都可以被编辑。
如何设置?如果是在控制台执行的话,那么就是使用如下的代码:
document.body.contentEditable = true;
但是很明显,这个是一次性的,如果要让网页恢复为不可编辑状态,那么就是需要将上面的 true 改为 false,并重新在控制台中运行,如下:
document.body.contentEditable = false;
这样很麻烦,有没有可以实现 toggle 效果的写法?也就是执行的是同一行代码,但效果却是不同的,也就是第一次执行这个代码是让网页可以编辑,然后再执行一遍同一个代码,让网页变成不可编辑。
有!通过 JavaScript 的三元表达式(或者叫:三元运算符),具体代码如下:
document.body.contentEditable = document.body.contentEditable != 'true' ? true : false;
这行代码的详细解释:
- 默认情况下,
document.body.contentEditable
的值是‘inherit’
即 继承。所以,上述代码中的三元表达式部分的判断结果为true
,因为'inherit' != 'ture'
为真,所以,三元表达式的运算后的最终结果就是 true ,然后 true 会被赋值给前面的document.body.contentEditable
。所以,网页可以被编辑了。 - 第二次执行这同一段代码的时候的情况是:此时的
document.body.contentEditable
的值是 true,而三元表达式的判断是 ‘true’ 不等于 ‘true’ ,很显然这是不对的,所以,这次三元运算的结果是 false ,而 false 的值就会被赋值给document.body.contentEditable
。所以,执行后网页又不可以被编辑了。 - 第三次运行这段代码的时候:此时的
document.body.contentEditable
的值是上一次赋值的 false,所以判断条件是:‘false’ 不等于 ‘true’ ,很显然,这是正确的,所以会将后面的 true 又赋值给document.body.contentEditable
。此时网页又可以编辑了。
同一行代码,反复执行,就可以让网页在 “可以编辑” 和 “不可以编辑” 的状态之间来回切换。
3. 具体的步骤
关于用书签来运行 JavaScript 的代码的相关问题我这里不再解释,你可以看之前的提到的两篇文章。这篇文章我将重心放在具体的功能实现上。
我们接下来要做的就是:将上面那行可以反复运行的代码放在下面这个结构的第一个小括号中:
javascript:()();
完整的代码如下:
javascript: (document.body.contentEditable = document.body.contentEditable != 'true' ? true : false;)();
所以,我们现在只需要复制上面这个完整的代码到一个书签的 URL 的位置,保存即可。现在只要点击书签,上面的代码就会被执行,网页就可以被编辑了,再次点击,网页就恢复到不可以编辑的状态,当然,已修改的网页内容不会被恢复,只有刷新了网页还可以恢复成原本的样子。
如下图:
4. 添加提示信息
这个时候的书签,虽然从功能上满足了,但是还是不够 “人性化”,因为状态切换了,却没有相关的提示信息弹出。虽然可以通过查看 “点击网页文字” 时,是否有编辑的光标来判断,但总归是不方便,所以,现在就稍微完善下提示功能。
我就直接将前面提到的那两篇文章中用到的代码复制过来稍微改了下逻辑,就得到了下面的完整代码,你要是不喜欢样式,修改成你自己喜欢的即可:
javascript: (function() {
document.body.contentEditable = document.body.contentEditable != 'true' ? true : false;
var toast = document.createElement("div");
toast.style.color = "white";
toast.style.fontSize = "36px";
toast.style.fontWeight = "bold";
toast.style.lineHeight = "50px";
toast.style.padding = "5px 10px";
toast.style.backgroundColor = "darkslategrey";
toast.style.border = "none";
toast.style.borderRadius = "10px";
toast.style.position = "fixed";
toast.style.top = "80px";
toast.style.right = "80px";
toast.style.zIndex = "9999";
toast.style.transition = "1s";
if (document.body.contentEditable == 'true') {
toast.textContent = "可编辑";
} else {
toast.textContent = "不可编辑";
}
document.body.appendChild(toast);
setTimeout( () => {
toast.style.opacity = "0";
setTimeout( () => {
document.body.removeChild(toast);
}
, 500);
}
, 1500);
}
)();
将上述完整代码复制并替换我们第一次写在书签的 URL 中的内容,然后保存,现在点击这个书签就有提示信息了。
5. 能用 Vimium C 插件来实现吗?
不能。
比如,最开始我做了这样的按键映射:
map e openUrl url="javascript:(document.body.contentEditable\u0020=\u0020document.body.contentEditable\u0020!=\u0020'true'\u0020?\u0020true\u0020:\u0020false)();"
虽然,按下字母 e 可以让网页变成可以编辑的状态,但就无法变回去了,因为再次按字母 e 就成了输入字母 e 到网页中,而不是执行 e 对应的代码了。
6. 能用油猴来实现吗?
能。
比如下面的油猴脚本代码【就是复制上面的代码,然后稍微修改下】,我将 Ctrl + E 这个快捷键绑定为来回切换网页是否可以编辑的快捷键。但要注意,Ctrl + E 这个快捷键在浏览器中原本就有它的功能,它的功能就是聚焦到地址栏,并用你设置的搜索引擎搜索你输入的内容。如果你本来就在用这个快捷键,那么你将下面代码中的 if(event.key === "e" && event.ctrlKey)
中的字母 e 改为你要用的,我使用字母 e 是因为好记,e 就对应到 edit 。如果要使用原本的 Ctrl + E 的功能,你可以修改为 字母 i 也可以,i 可以表示 insert ,即向网页中插入内容,也行。
完整代码如下:
// ==UserScript==
// @name Toggle 网页编辑
// @namespace http://tampermonkey.net/
// @version 2024-12-05
// @description 来回切换网页内容能否编辑的状态。
// @author 王某人
// @run-at document-end
// @match *://*/*
// @icon https://www.google.com/s2/favicons?sz=64
// @grant none
// ==/UserScript==
(function() {
'use strict';
document.addEventListener("keydown", (event) => {
if(event.key === "e" && event.ctrlKey) {
event.preventDefault();
document.body.contentEditable = document.body.contentEditable != 'true' ? true : false;
var toast = document.createElement("div");
toast.style.color = "white";
toast.style.fontSize = "36px";
toast.style.fontWeight = "bold";
toast.style.lineHeight = "50px";
toast.style.padding = "5px 10px";
toast.style.backgroundColor = "darkslategrey";
toast.style.border = "none";
toast.style.borderRadius = "10px";
toast.style.position = "fixed";
toast.style.top = "80px";
toast.style.right = "80px";
toast.style.zIndex = "9999";
toast.style.transition = "1s";
if (document.body.contentEditable == 'true') {
toast.textContent = "可编辑";
} else {
toast.textContent = "不可编辑";
}
document.body.appendChild(toast);
setTimeout( () => {
toast.style.opacity = "0";
setTimeout( () => {
document.body.removeChild(toast);
} , 500);
} , 1500);
}
}, false);
})();
7. 2024-12-9更新【简化写法】
说来真是羞愧,以前在使用这个 contentEidtable
属性的时候,从来就没有把注意力放在这个补全列表的第二项上,也就是下图中的 isContentEditable
,这个属性,其实就是用来获取该元素是否可以被编辑的状态的。
在控制台中执行它的话,会得到一个布尔值,所以,我们完全可以对这个属性执行:取反运算【就是在前面加个英文状态下的感叹号即可】,然后将值再赋值给 document.body.contentEditable
即可可以实现来回切换状态的效果了,具体的代码如下:
document.body.contentEditable = !document.body.isContentEditable;
这样的写法,比之前的 “三元运算符” 的写法简洁很多。
这两个属性都是被定义在混入接口 ElementContentEditable 中的,这个接口的 IDL【Interface Definition Language,接口定义语言】 定义如下:
interface mixin ElementContentEditable {
[CEReactions] attribute DOMString contentEditable; // 修改状态用这个,并非只读属性
[CEReactions] attribute DOMString enterKeyHint;
readonly attribute boolean isContentEditable; // 只读属性,布尔类型
[CEReactions] attribute DOMString inputMode;
};
它的 IDL 的官网地址我也贴出来吧,感兴趣可以去看看:https://html.spec.whatwg.org/multipage/interaction.html#attr-contenteditable
官网中的解释如下图:
另外,我之前提到过,这个 contentEditable 是可以被继承的,你除了在上面的官网中可以看到详细的解释外,还可以用下图中的操作,来自己验证:
如有问题,欢迎下方留言讨论!