最近在研究爬虫中验证码及 Canvas 相关的东西,简单记录一下 code snippets
{
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
//
const noisify = function (canvas, context) {
if (context) {
const shift = {
'r': Math.floor(Math.random() * 10) - 5,
'g': Math.floor(Math.random() * 10) - 5,
'b': Math.floor(Math.random() * 10) - 5,
'a': Math.floor(Math.random() * 10) - 5
};
//
const width = canvas.width;
const height = canvas.height;
//
if (width && height) {
const imageData = getImageData.apply(context, [0, 0, width, height]);
//
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const n = ((i * (width * 4)) + (j * 4));
imageData.data[n + 0] = imageData.data[n + 0] + shift.r;
imageData.data[n + 1] = imageData.data[n + 1] + shift.g;
imageData.data[n + 2] = imageData.data[n + 2] + shift.b;
imageData.data[n + 3] = imageData.data[n + 3] + shift.a;
}
}
//
window.top.postMessage("canvas-defender-alert", '*');
context.putImageData(imageData, 0, 0);
}
}
};
//
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
//
return Reflect.apply(target, self, args);
}
});
//
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
//
return Reflect.apply(target, self, args);
}
});
//
CanvasRenderingContext2D.prototype.getImageData = new Proxy(CanvasRenderingContext2D.prototype.getImageData, {
apply(target, self, args) {
noisify(self.canvas, self);
//
return Reflect.apply(target, self, args);
}
});
}
{
const mkey = "canvas-defender-sandboxed-frame";
document.documentElement.setAttribute(mkey, '');
//
window.addEventListener("message", function (e) {
if (e.data && e.data === mkey) {
e.preventDefault();
e.stopPropagation();
//
if (e.source) {
if (e.source.CanvasRenderingContext2D) {
e.source.CanvasRenderingContext2D.prototype.getImageData = CanvasRenderingContext2D.prototype.getImageData;
}
//
if (e.source.HTMLCanvasElement) {
e.source.HTMLCanvasElement.prototype.toBlob = HTMLCanvasElement.prototype.toBlob;
e.source.HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL;
}
}
}
}, false);
}
这段 JavaScript 代码是用于对 Canvas 元素进行处理的。下面是对代码的解释:
第一部分:
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
这行代码创建了一个名为 getImageData
的变量,并将 CanvasRenderingContext2D.prototype.getImageData
的引用赋值给它。这意味着在后续的代码中,可以通过 getImageData
变量来调用 CanvasRenderingContext2D
原型上的 getImageData
方法。
然后是一个名为 noisify
的函数,该函数用于给 Canvas 添加噪点效果。接下来的代码是对 noisify
函数的定义和实现。
在 noisify
函数内部,存在以下逻辑:
- 首先,通过传入的参数
canvas
和context
进行判断。 - 然后,生成一个包含随机数的
shift
对象,用于控制噪点的颜色偏移量。 - 接着,获取传入的 Canvas 的宽度和高度。
- 如果宽度和高度都存在,则使用
getImageData
方法获取 Canvas 上的像素数据,并存储在imageData
变量中。 - 然后,使用嵌套的循环遍历每个像素,并根据
shift
对象中的随机数对像素的颜色值进行偏移。 - 在处理完所有像素后,发送一条消息给顶层窗口以触发 "canvas-defender-alert" 事件。
- 最后,使用
context.putImageData(imageData, 0, 0)
将经过处理的像素数据重新绘制到 Canvas 上。
第二部分:
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
return Reflect.apply(target, self, args);
}
});
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
return Reflect.apply(target, self, args);
}
});
CanvasRenderingContext2D.prototype.getImageData = new Proxy(CanvasRenderingContext2D.prototype.getImageData, {
apply(target, self, args) {
noisify(self.canvas, self);
return Reflect.apply(target, self, args);
}
});
这部分代码重新定义了三个方法:toBlob
、toDataURL
和 getImageData
,并使用 Proxy
进行了代理处理。通过对这些方法进行代理,可以在调用它们时自动调用 noisify
函数给 Canvas 添加噪点效果。
第三部分:
const mkey = "canvas-defender-sandboxed-frame";
document.documentElement.setAttribute(mkey, '');
window.addEventListener("message", function (e) {
if (e.data && e.data === mkey) {
e.preventDefault();
e.stopPropagation();
if (e.source) {
if (e.source.CanvasRenderingContext2D) {
e.source.CanvasRenderingContext2D.prototype.getImageData = CanvasRenderingContext2D.prototype.getImageData;
}
if (e.source.HTMLCanvasElement) {
e.source.HTMLCanvasElement.prototype.toBlob = HTMLCanvasElement.prototype.toBlob;
e.source.HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL;
}
}
}
}, false);
这部分代码定义了一个用于监听消息的事件监听器。当收到特定消息 mkey
时,会执行一系列操作。具体而言:
- 首先,将自定义属性
mkey
添加给页面的根元素(document.documentElement
)。 - 然后,注册一个
"message"
事件的监听器,并通过条件判断来处理接收到的消息。 - 如果消息是等于
mkey
的,则阻止默认行为和事件传播。 - 接着,通过
e.source
来访问消息来源的窗口对象,并检查其是否具有CanvasRenderingContext2D
和HTMLCanvasElement
这两个原型上的方法。 - 如果是,将相应的方法重新定义为之前代理过的方法,从而保证对这些方法的调用会经过
noisify
处理。
总而言之,该 JavaScript 代码实现了给 Canvas 元素添加噪点效果的功能,并在特定条件下对 Canvas 相关的方法进行重定义和代理处理。