一、什么是 EyeDropper API?
EyeDropper API 是浏览器提供的一个系统级取色器接口,允许用户从屏幕上的任意位置(包括浏览器外部)拾取颜色。它不需要任何第三方依赖库,是 Web 平台原生的取色解决方案。
1.1 传统取色方案的痛点
在 EyeDropper API 出现之前,Web 前端要实现取色功能通常有以下几种方式:
- 第三方库:如 Pickr、iro.js 等,体积较大且功能冗余
- HTML5 Canvas 方案:截取页面截图后用 Canvas 分析像素颜色,实现复杂且只能取页面内的颜色
- input[type=color]:只能取色板中的颜色,无法从屏幕任意位置取色
这些方案要么依赖外部库,要么功能受限,都无法做到真正的"系统级取色"。
1.2 EyeDropper 的优势
| 特性 | EyeDropper | 第三方库 | Canvas 方案 |
|---|---|---|---|
| 系统级取色(屏幕任意位置) | ✅ | ❌ | ❌ |
| 原生支持,零依赖 | ✅ | ❌ | ✅ |
| 跨浏览器 | 有限(Chrome 为主) | ✅ | ✅ |
| 使用复杂度 | ⭐ 简单 | ⭐⭐ 中等 | ⭐⭐⭐ 复杂 |
| 性能开销 | 极低 | 中等 | 较高 |
二、基础用法
2.1 浏览器兼容性检查
在使用 EyeDropper API 之前,首先需要检测浏览器是否支持:
const isSupported = 'EyeDropper' in window;
if (!isSupported) {
console.warn('当前浏览器不支持 EyeDropper API');
// 可以降级使用 input[type=color] 或第三方库
}2.2 基本使用流程
EyeDropper API 的使用非常简洁,只需三步:
// 1. 创建 EyeDropper 实例
const eyeDropper = new EyeDropper();
// 2. 打开取色器(用户选择颜色)
eyeDropper.open()
.then(result => {
// 3. 获取拾取的颜色(sRGB 十六进制格式)
console.log(result.sRGBHex); // 例如: '#3366ff'
})
.catch(error => {
// 用户取消或取色失败
console.error('取色取消:', error);
});2.3 完整示例:点击按钮取色
<!DOCTYPE html>
<html>
<head>
<style>
.picker-wrapper {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
background: #f5f5f5;
border-radius: 12px;
}
.color-preview {
width: 60px;
height: 60px;
border-radius: 8px;
border: 2px solid #ddd;
}
.color-value {
font-family: monospace;
font-size: 18px;
}
.pick-btn {
padding: 10px 24px;
background: #4a90d9;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
}
.pick-btn:hover { background: #357abd; }
.pick-btn:disabled { opacity: 0.5; cursor: not-allowed; }
</style>
</head>
<body>
<div class="picker-wrapper">
<div class="color-preview" id="preview" style="background:#cccccc"></div>
<span class="color-value" id="value">#cccccc</span>
<button class="pick-btn" id="pickBtn">🎨 取色</button>
</div>
<script>
document.getElementById('pickBtn').addEventListener('click', async () => {
if (!('EyeDropper' in window)) {
alert('当前浏览器不支持 EyeDropper API');
return;
}
const eyeDropper = new EyeDropper();
try {
const result = await eyeDropper.open();
const color = result.sRGBHex;
document.getElementById('preview').style.background = color;
document.getElementById('value').textContent = color;
} catch (err) {
console.log('取色已取消');
}
});
</script>
</body>
</html>三、进阶用法
3.1 颜色格式转换
EyeDropper 返回的颜色格式为 sRGB 十六进制(如 #3366ff),但实际开发中可能需要其他格式:
function hexToRgb(hex) {
const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function hexToHsl(hex) {
const rgb = hexToRgb(hex);
if (!rgb) return null;
let r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) * 60; break;
case g: h = ((b - r) / d + 2) * 60; break;
case b: h = ((r - g) / d + 4) * 60; break;
}
}
return 'hsl(' + Math.round(h) + ', ' + Math.round(s * 100) + '%, ' + Math.round(l * 100) + '%)';
}
// 使用示例
const hex = '#3366ff';
console.log(hexToRgb(hex)); // {r: 51, g: 102, b: 255}
console.log(hexToHsl(hex)); // hsl(225, 100%, 60%)3.2 配合 AbortController 实现超时控制
某些情况下,你可能希望在用户长时间不取色时自动取消:
function pickColorWithTimeout(timeout = 30000) {
if (!('EyeDropper' in window)) {
return Promise.reject(new Error('不支持 EyeDropper'));
}
const controller = new AbortController();
const eyeDropper = new EyeDropper();
const timer = setTimeout(() => controller.abort(), timeout);
return eyeDropper.open({ signal: controller.signal })
.then(result => {
clearTimeout(timer);
return result.sRGBHex;
})
.catch(err => {
clearTimeout(timer);
throw err;
});
}
// 使用
pickColorWithTimeout(10000)
.then(color => console.log('取色结果:', color))
.catch(err => console.log('取色取消或超时'));3.3 取色历史记录
实现取色历史记录功能,方便用户重复使用最近拾取的颜色:
class ColorPicker {
constructor(maxHistory = 10) {
this.history = [];
this.maxHistory = maxHistory;
this.container = document.getElementById('colorHistory');
}
async pick() {
if (!('EyeDropper' in window)) return;
const eyeDropper = new EyeDropper();
try {
const result = await eyeDropper.open();
this.addToHistory(result.sRGBHex);
return result.sRGBHex;
} catch {
return null;
}
}
addToHistory(color) {
const idx = this.history.indexOf(color);
if (idx > -1) this.history.splice(idx, 1);
this.history.unshift(color);
if (this.history.length > this.maxHistory) this.history.pop();
this.render();
}
render() {
this.container.innerHTML = this.history.map(color =>
'<div class="history-item" style="background:' + color +
'" title="' + color + '" data-color="' + color + '"></div>'
).join('');
}
}四、实际应用场景
4.1 设计工具中的取色器
class DesignTool {
constructor() {
this.selectedColor = '#000000';
}
async pickColor() {
if (!('EyeDropper' in window)) return null;
const eyeDropper = new EyeDropper();
try {
const result = await eyeDropper.open();
this.selectedColor = result.sRGBHex;
this.updateUI();
return result.sRGBHex;
} catch {
return null;
}
}
updateUI() {
document.querySelectorAll('.color-display').forEach(el => {
el.style.background = this.selectedColor;
el.querySelector('.color-label').textContent = this.selectedColor;
});
}
}4.2 主题定制器
class ThemeCustomizer {
constructor() {
this.colors = {
primary: '#4a90d9',
background: '#ffffff',
text: '#333333',
accent: '#e74c3c'
};
}
async pickColor(colorKey) {
if (!('EyeDropper' in window)) return;
const eyeDropper = new EyeDropper();
try {
const result = await eyeDropper.open();
this.colors[colorKey] = result.sRGBHex;
this.applyTheme();
this.saveToStorage();
} catch {}
}
applyTheme() {
const root = document.documentElement;
Object.entries(this.colors).forEach(([key, value]) => {
root.style.setProperty('--' + key, value);
});
}
saveToStorage() {
localStorage.setItem('theme-colors', JSON.stringify(this.colors));
}
}4.3 无障碍颜色对比度检测
结合取色器和对比度检测,帮助用户选择无障碍友好的颜色组合:
function getLuminance(hex) {
const rgb = hexToRgb(hex);
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function getContrastRatio(hex1, hex2) {
const l1 = getLuminance(hex1);
const l2 = getLuminance(hex2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// WCAG 标准检测
function checkWCAG(hex1, hex2) {
const ratio = getContrastRatio(hex1, hex2);
return {
ratio: ratio.toFixed(2) + ':1',
passesAA: ratio >= 4.5,
passesAAA: ratio >= 7
};
}五、浏览器兼容性与降级方案
5.1 当前浏览器支持情况
| 浏览器 | 支持版本 | 状态 |
|---|---|---|
| Chrome | 95+ | ✅ 完整支持 |
| Edge | 95+ | ✅ 完整支持 |
| Opera | 81+ | ✅ 完整支持 |
| Firefox | ❌ | 未实现 |
| Safari | ❌ | 未实现 |
| IE | ❌ | 不支持 |
5.2 优雅降级
function createColorPicker(options) {
options = options || {};
var onPick = options.onPick;
// 优先使用 EyeDropper
if ('EyeDropper' in window) {
return {
pick: function() {
var dropper = new EyeDropper();
return dropper.open()
.then(function(result) {
if (onPick) onPick(result.sRGBHex);
return result.sRGBHex;
})
.catch(function() {
return null;
});
}
};
}
// 降级到 input[type=color]
var input = document.createElement('input');
input.type = 'color';
input.style.position = 'fixed';
input.style.opacity = '0';
document.body.appendChild(input);
return {
pick: function() {
return new Promise(function(resolve) {
input.addEventListener('input', function() {
var color = input.value;
if (onPick) onPick(color);
resolve(color);
}, { once: true });
input.addEventListener('blur', function() {
resolve(null);
}, { once: true });
input.click();
});
}
};
}六、安全与隐私
6.1 安全限制
- 必须由用户手势触发:
open()必须在点击、触摸等用户事件中调用 - 需要 HTTPS:API 仅在安全上下文(HTTPS)中可用
- 取色范围无限制:可以取屏幕任意位置的颜色,包括其他应用窗口
6.2 隐私保护
- EyeDropper 不会自动捕获屏幕内容
- 取色完成后仅返回颜色值,不返回任何屏幕截图或像素数据
- 应用无法在后台悄悄取色
- 取色器打开时,鼠标指针变为放大镜样式,用户可见取色操作
七、总结
EyeDropper API 为 Web 前端提供了一个原生、高效、零依赖的系统级取色方案。主要优势:
- 系统级取色:从屏幕任意位置(包括其他应用)拾取颜色
- 极简 API:
new EyeDropper().open()三步搞定 - 用户体验好:鼠标放大镜预览、实时颜色显示
- 安全可靠:必须用户手势触发,无法后台取色
目前虽仅 Chromium 内核支持,但结合降级方案已可在实际项目中使用。