# EyeDropper API：拾取屏幕上任意像素的颜色

> EyeDropper() 构造函数与 open() 方法如何让页面为自定义取色器拾取屏幕上任意像素的颜色、为什么 open() 必须在用户手势中调用，以及目前仅限桌面端、仅 Chromium 内核、从 Chrome 与 Edge 95 开始的支持情况。

**一句话：** 根据 Chrome for Developers 的说明，"EyeDropper API 让开发者可以在自定义取色器中
使用浏览器自带的取色工具"，从而在不自行实现取色逻辑的情况下，拾取屏幕上任意像素（包括浏览器
窗口以外的像素）的颜色。

## 创建并打开取色器

根据 Chrome for Developers 的说明，`open()` "只能在响应用户操作（例如按钮点击）时调用"，
因此这次调用应放在点击处理函数内部——把它粘贴到脚本顶层并不会启动取色器：

```js
const pickButton = document.createElement("button");
pickButton.textContent = "Pick color";
const colorInput = document.createElement("input");
colorInput.type = "color";
document.body.append(pickButton, colorInput);

pickButton.addEventListener("click", async () => {
  const eyeDropper = new EyeDropper();
  try {
    const result = await eyeDropper.open();
    colorInput.value = result.sRGBHex; // 例如 "#3366ff"
  } catch (err) {
    // 如果用户取消取色（例如按下 Escape），Promise 会 reject。
    console.log("取色已取消：", err);
  }
});
```

根据 Chrome for Developers，`open()` 方法"返回一个 Promise，在用户选取屏幕上的某个像素后
resolve"，其 resolve 的值提供"以 sRGBHex 格式（`#RRGGBB`）访问该像素颜色"的能力。当用户
取消而不是选取像素时，这个 Promise 会 reject；示例使用 `try`/`catch` 处理该 rejection。

## 用户手势要求

Chrome for Developers 明确指出了激活要求："该 API 不会在没有用户意图的情况下启动取色模式。
`open()` 方法只能在响应用户操作（例如按钮点击）时调用。" 在点击等用户手势处理函数之外调用
`open()` 不会启动取色器。

## 取消正在进行的取色

除了用户按下 Escape，`open()` 还接受一个 `AbortSignal`，可用于以编程方式取消正在进行的
取色：

```js
const controller = new AbortController();
eyeDropper.open({ signal: controller.signal }).catch(() => {
  // 如果用户取消（例如按下 Escape）或信号被中止，Promise 会 reject。
});
// 在其他地方，例如某个"取消"按钮上：
controller.abort();
```

## 运行时检测与回退

在未实现该 API 的每个浏览器（Firefox、Safari）中，`"EyeDropper" in window` 都为
`false`。下面的回退分支会真正展示 `<input type="color">` 色块来代替取色按钮，而不只是
注释说明应该这样做：

```js
function initColorPicker(pickButton, colorInput) {
  if (!("EyeDropper" in window)) {
    // 此处不支持原生取色器——隐藏取色按钮，改用始终存在的
    // <input type="color"> 色块让用户选取颜色。
    pickButton.hidden = true;
    colorInput.hidden = false;
    return;
  }
  pickButton.hidden = false;
  pickButton.addEventListener("click", async () => {
    const eyeDropper = new EyeDropper();
    try {
      const { sRGBHex } = await eyeDropper.open();
      colorInput.value = sRGBHex;
    } catch (err) {
      // 如果用户取消取色（例如按下 Escape），Promise 会 reject。
      console.log("取色已取消：", err);
    }
  });
}

const pickButton = document.createElement("button");
pickButton.textContent = "Pick color";
const colorInput = document.createElement("input");
colorInput.type = "color";
colorInput.hidden = true;
document.body.append(pickButton, colorInput);
initColorPicker(pickButton, colorInput);
```

## 浏览器支持情况

根据 MDN，EyeDropper API "不属于 Baseline，因为它在一些广泛使用的浏览器中尚不可用"。根据
Chrome for Developers，该 API "自版本 95 起在 Chromium 内核浏览器（如 Edge 或 Chrome）中受支持"；
根据 caniuse 对该特性的跟踪记录，其支持范围还延伸到了 Opera。Chrome for Developers 自己的演示
教程在"Windows 或 Mac"上运行，这与 Chromium 实现中该 API 仅限桌面端的情况相符。Firefox 与
Safari 均未实现 EyeDropper API。

## 实用清单

- [ ] 在构造 `EyeDropper` 之前先做 `"EyeDropper" in window` 特性检测——根据 MDN 与 Chrome for
      Developers，支持范围仅限于 Chromium 桌面浏览器（Chrome/Edge 95+）。
- [ ] 只在用户手势处理函数内部（例如点击监听器）调用 `open()`——根据 Chrome for Developers，
      它"只能在响应用户操作时调用"。
- [ ] 处理 `open()` 被拒绝的 Promise——如果用户取消取色（例如按下 Escape）而不是选取像素，
      Promise 会 reject。
- [ ] 如果界面需要以编程方式取消正在进行的取色（例如取色器所在的对话框被关闭），请传入
      `AbortSignal`。
- [ ] 为不支持 `EyeDropper` 的浏览器提供回退颜色输入（例如 `<input type="color">`），因为
      Firefox 与 Safari 均未实现该 API。

## 相关参考

- [Async Clipboard API](/zh/reference/capabilities/clipboard/)
- [Screen Wake Lock API](/zh/reference/capabilities/wake-lock/)