JavaScript吸管(告诉鼠标光标下像素的颜色)

85

我在寻找一个"眼滴"工具,可以在JavaScript中为CMS提供鼠标光标所在像素的十六进制值。

对于Firefox浏览器,有一个名为ColorZilla的扩展能够完美实现这一功能。但它只适用于FF浏览器,我希望能够将其与CMS一同使用。

一位荷兰开发者想到了非常巧妙的方法,使用Ajax和PHP的imagecolorat()来查找图像上的像素颜色。但是,这种方法的局限在于只能针对服务器端能够访问到的图片进行操作,而我真正想要的是一个通用的工具。

我可以使用其中的任一方法,但更希望能够使用跨浏览器、基于JavaScript或Flash的方式,无需进行服务器端调整,也无需安装扩展程序。

如果有任何可在IE中实现类似于ColorZilla的功能的解决方案,我也会感兴趣 - 我可以支持IE和FF浏览器,虽然跨浏览器解决方案当然更理想。


这在Javascript中不可能实现;我不确定Flash是否可以。 - SLaks
理论上,您可能能够处理鼠标光标下面的最顶层元素关联的样式信息,并确定该元素的颜色应该是什么,如果它不是图像或画布的话。但在实践中,这可能会让您发疯,需要处理边框、边距和浏览器怪癖等问题。我认为Elijah的回答是唯一可行的方案。 - Aaronaught
12个回答

90

由于涉及到跨域安全性,使用 JavaScript 是不可能的。如果你知道哪些像素组成了 http://some-other-host/yourPassword.png 这个图片,那么这将非常糟糕。只有当鼠标悬停在同一域名下的 canvas 或 image 元素上(或者另一个允许跨域访问的 image 元素,其服务器设置了Access-Control-Allow-Origin: * 头信息)时,才能获取鼠标所在像素点的颜色值。对于 canvas,可以使用 canvasElement.getContext('2d').getImageData(x, y, 1, 1).data 方法获取像素颜色值。对于 image 元素,则需要将它们绘制到 canvas 中,具体操作如下:

var canvas = document.createElement("canvas");
canvas.width = yourImageElement.width;
canvas.height = yourImageElement.height;
canvas.getContext('2d').drawImage(yourImageElement, 0, 0);

然后只需按照前面介绍的canvas方法操作即可。如果你需要能够转换为不同表示形式的颜色值,请尝试使用我的color.js库。

另外,您永远无法支持IE<9(假设IE9支持canvas),使用Flash也无济于事,因为它也不能读取文档的像素数据。


6
安全方面有趣的一面,也感谢Canvas的角度。然而,我可以接受只能访问我所在域内的像素。 - Pekka
1
如果我知道URL,那么有什么能阻止我将“yourPassword.png”嵌入画布中并在那里对其进行采样呢? - hughes
1
@zzzzBov,@hughes,你们没有理解重点:如果你可以抓取屏幕上的任何像素,那么你就可以查看不同浏览器窗口或甚至在PDF银行对账单中的值,在不同的窗口中。任何处理敏感信息的页面都不应该运行不受信任的JS,并且提供敏感文件应涉及适当的身份验证。您需要劫持用户的会话才能访问他们的“password.png”。 - Dan Ross
2
@danross,JS没有跨沙盒访问执行脚本的权限(除非明确授权),因此限制API仅限于来自相同安全沙盒内的帧中的颜色值是完全可行的。 - zzzzBov
@danross,我认为这是一个更好的使用情况,可以返回null值,但这是最好由whatwg邮件列表之一解决的问题。 - zzzzBov
显示剩余4条评论

22
使用一种称为“浏览器定时攻击”的技术,可以(部分地)确定任何像素的颜色,甚至是在iframes中。基本上,这种技术测量了在元素上呈现SVG过滤器的时间,而不是颜色本身(requestAnimationFrame()能够以比setTimeout()更高的精度测量时间)。 根据当前的像素颜色,过滤器需要更多或更少的时间来应用。这使得可以确定像素是否与已知颜色相同-例如黑色或白色。有关更多详细信息,请参见此白皮书(pdf): https://www.contextis.com/media/downloads/Pixel_Perfect_Timing_Attacks_with_HTML5_Whitepaper.pdf。顺便说一句: 是的,这是一个浏览器安全漏洞,但我不知道浏览器供应商如何修补它。

有趣的解决方法,但是由于跨域策略,它不安全吗? - bren
这篇论文已经快7年了!有人知道这个安全漏洞今天是否仍然存在吗? - Rodrigo
我怀疑它已经被修复了,而且CORS不适用。 - xendi

12

在StackOverflow和其他网站上找到的各种参考资料,我使用JavaScript和JQuery进行了合并:

<html>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<script src="jquery.js"></script>
<script type="text/javascript">
    window.onload = function(){
        var canvas = document.getElementById('myCanvas');
        var context = canvas.getContext('2d');
        var img = new Image();
        img.src = 'photo_apple.jpg';
        context.drawImage(img, 0, 0);
    };

    function findPos(obj){
    var current_left = 0, current_top = 0;
    if (obj.offsetParent){
        do{
            current_left += obj.offsetLeft;
            current_top += obj.offsetTop;
        }while(obj = obj.offsetParent);
        return {x: current_left, y: current_top};
    }
    return undefined;
    }

    function rgbToHex(r, g, b){
    if (r > 255 || g > 255 || b > 255)
        throw "Invalid color component";
    return ((r << 16) | (g << 8) | b).toString(16);
    }

$('#myCanvas').click(function(e){
    var position = findPos(this);
    var x = e.pageX - position.x;
    var y = e.pageY - position.y;
    var coordinate = "x=" + x + ", y=" + y;
    var canvas = this.getContext('2d');
    var p = canvas.getImageData(x, y, 1, 1).data;
    var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
    alert("HEX: " + hex);
});
</script>
<img src="photo_apple.jpg"/>
</body>
</html>

这是我的完整解决方案.. 这里我只使用了画布(canvas)和一张图片,但如果你需要在图片上使用<map>也是可以的。希望我能够帮到你。


3
使用取色器从任何地方获取颜色怎么样? - SearchForKnowledge

7
我非常赞同Elijah提供的详细答案。此外,我认为在涉及图像时,您不需要使用canvas。正如您自己所述,您可以从php中获取这些图像并在服务器上进行颜色查询。
我建议您使用外部工具来处理此问题-这使得它甚至可以独立于浏览器(但依赖于操作系统):编写一个小工具(例如c#),它可以为您执行颜色查询,通过快捷方式调用并将颜色提交给您的服务器。将该工具作为下载文件提供给您的CMS用户。
我曾经为一个CMS使用过另一种方法来“窃取”颜色:用例是将已经存在的网站颜色作为颜色调色板提供给我的应用程序:
1. 我要求用户提供目标系统的URL-通常是公司的主页 2. 我解析页面以查找所有内联样式和链接样式中的所有颜色定义 3. (您还可以轻松扩展到所有引用的图像) 4. 结果是一个漂亮的颜色调色板,其中包含所有企业颜色可供选择
也许这也是您的CMS的解决方案?

谢谢,Ralf。构建自定义调色板是一个非常好的想法,但不适用于我手头的任务,因为将有大量没有定义颜色范围的新内容。客户端应用程序肯定是最干净的方法,但我需要一个可以在Windows和Mac OS上运行的工具。这将是重新进入客户端编程(以及首次在Mac上)的好理由,但我现在有太多事情要做了。我想我会采用仅图像的方法。 - Pekka

7

不错。在Firefox中也可以工作,但如果您不必弹出浏览器对话框就能完成这项任务,那将是非常棒的。 - NoBugs
3
很不幸,Chrome 最近更新了表单控件的设计,他们新的颜色选择器没有取色器。 - mattsven

7

Chrome Canary现在支持EyeDropper API!

更新:Chrome 95+版本支持该功能。

https://www.chromestatus.com/feature/6304275594477568

我相信我们很快会在所有流行的浏览器中看到它。

基本上,它将把用户的光标变成放大镜,并让用户选择页面上的任何像素(适用于图像、视频和iframe)。

const eyeDropper = new EyeDropper()

async function useEyeDropper() {
  try {
    const selectedColor = await eyeDropper.open()
    console.log(selectedColor) // { sRGBHex: '#008080' }
  } catch (err) {
    console.log('eye dropper cancelled')
  }
}

someTriggerEl.addEventListener('click', () => {
  useEyeDropper();
})

如果您不理解async/await:

const eyeDropper = new EyeDropper()

someTriggerEl.addEventListener('click', () => {
  eyeDropper.open().then(selectedColor => {
    console.log(selectedColor) // { sRGBHex: '#008080' }
  }).catch(() => {
    console.log('eye dropper cancelled')
  })
})

我对这个功能非常兴奋。


1
从Chrome 95开始,这已经生效了。 很棒的代码片段@BorisTB - kevin walker
1
今天,在Linux上最新的Chrome生成了"rgba(172,150,118,0)",其中alpha通道始终为零,而不是十六进制结果。因此,您需要对结果进行一些手动处理。 - sarkiroka

5
我不知道这是否可行,但如果您的页面是静态的,您可以保存每个页面的图像截图(或者也许为每个浏览器/屏幕分辨率保存一个?),然后使用AJAX将光标坐标发送到服务器,并使用PHP的imagecolorat()返回像素颜色。
要进行截屏,您可以使用Selenium IDE,如此处所述。
希望对您有帮助。

3

除了之前给出的答案之外 --

解决这个问题的一种思路是,您想要能够截取1像素乘1像素的区域屏幕截图。捕获屏幕区域的相当普遍的技术(例如从 Web 基础的错误报告系统中)是使用有签名的 Java applet 和 java.awt.Robot 来捕获图片。如果您签署了 applet,则用户将收到“是否信任此应用”对话框(带有“始终信任该发布商的应用程序”复选框),然后将能够使用该工具。

然后,您可以使用 LiveConnect 将结果传递给 JavaScript(文档已经过时,但 Java applets 仍支持此操作),或者您可以将其发布到服务器。同样,您可以从 JavaScript 调用 Java applet。


Java Applet 的角度很有趣。然而,我不精通编写 Applet,所以我可能无法为手头的任务实现它,会采用上述的 Canvas 方法。但是,感谢您让我注意到它,也许在以后的某个时候我能够做些什么。 - Pekka

3

我知道这是一篇非常旧的文章,但谷歌浏览器最近推出了一个名为“眼滴”的新API调用特性

这是从那篇博客中摘录的信息

const eyeDropper = new EyeDropper();

try {
  const selectedColor = await eyeDropper.open();
  console.log(selectedColor);
  // logs: { sRGBHex: '#ff0000' }
} catch (err) {
  console.log("color selection cancelled");
}
//You can test for support of EyeDropper using the following snippet, if you’re browsing with a browser that supports the EyeDropper API a button will be rendered below the code snippet.

if ("EyeDropper" in window) {
  // supported
}

https://pqina.nl/blog/use-the-javascript-eye-dropper-api-to-select-colors-on-the-web/


3
作为安全预防措施,您无法使用Javascript捕获屏幕像素(因此开发人员无法拍摄您的个人数据快照),但是您可以在Flash中执行此操作--您可以使用flash.display.BitmapData类在Flash容器内获取像素数据。
请查看http://www.sephiroth.it/tutorials/flashPHP/print_screen/--我已将其用于基于Flash的所见即所得项目,以便将图像保存到LAMP(PHP)服务器上。
使用Flash的问题在于它不受iOS设备的本地支持,而这些设备现在非常流行并且值得开发。 Flash正在走下坡路。
如果所有访问者都具有支持画布标记和JavaScript的最新Web浏览器,则基于画布的方法肯定是一个好方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接