diff --git a/docs/web/api/color-picker.en-US.md b/docs/web/api/color-picker.en-US.md
index 1423c7ba06..27ca0a89d2 100644
--- a/docs/web/api/color-picker.en-US.md
+++ b/docs/web/api/color-picker.en-US.md
@@ -49,3 +49,9 @@ The most recently used color can be configured through `recentColors`. A value o
### Read-only Color Selector
{{ status-readonly }}
+
+### Color Picker with Screen Color Sampling
+
+With `eyeDropper` enabled, an eyedropper button in the panel header invokes the browser's native EyeDropper API to pick a color from anywhere on the screen. This relies on browser support (Chrome / Edge 95+); the button is disabled in unsupported environments.
+
+{{ eye-dropper }}
diff --git a/docs/web/api/color-picker.md b/docs/web/api/color-picker.md
index d78785a147..57a5dd8552 100644
--- a/docs/web/api/color-picker.md
+++ b/docs/web/api/color-picker.md
@@ -49,3 +49,9 @@ spline: form
### 只读状态的颜色选择器
{{ status-readonly }}
+
+### 吸管取色的颜色选择器
+
+开启 `eyeDropper` 后,可通过面板顶部的吸管按钮调起浏览器原生 EyeDropper API,直接拾取屏幕上任意位置的颜色。该能力依赖浏览器支持(Chrome / Edge 95+),不支持的环境中按钮呈禁用态。
+
+{{ eye-dropper }}
diff --git a/js/color-picker/eyedropper.ts b/js/color-picker/eyedropper.ts
new file mode 100644
index 0000000000..4a161e87ef
--- /dev/null
+++ b/js/color-picker/eyedropper.ts
@@ -0,0 +1,41 @@
+/**
+ * 浏览器原生 EyeDropper API 封装
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper
+ */
+
+export interface OpenEyeDropperOptions {
+ /** 用于中断取色过程(如组件卸载时) */
+ signal?: AbortSignal;
+}
+
+interface NativeEyeDropper {
+ open(options?: { signal?: AbortSignal }): Promise<{ sRGBHex: string }>;
+}
+
+type EyeDropperHost = Window & { EyeDropper?: new () => NativeEyeDropper };
+
+/**
+ * 检测当前环境是否支持 EyeDropper API(SSR 环境返回 false)。
+ */
+export function isEyeDropperSupported(): boolean {
+ if (typeof window === 'undefined') return false;
+ return typeof (window as EyeDropperHost).EyeDropper === 'function';
+}
+
+/**
+ * 调起系统吸色器。
+ * @returns 用户选中的颜色(小写 `#rrggbb`,与 `` 的 value 语义一致);
+ * 环境不支持、用户取消(Esc / AbortSignal)或取色失败时返回 `null`,调用方无需 try/catch。
+ */
+export async function openEyeDropper(options?: OpenEyeDropperOptions): Promise {
+ if (!isEyeDropperSupported()) return null;
+ const EyeDropperCtor = (window as EyeDropperHost).EyeDropper;
+ try {
+ const { sRGBHex } = await new EyeDropperCtor().open(options?.signal ? { signal: options.signal } : undefined);
+ // 规范未强制 sRGBHex 的大小写,统一转小写便于比较与存储
+ return sRGBHex.toLowerCase();
+ } catch (e) {
+ // AbortError(用户取消/中断)与并发取色冲突等场景统一按“未取到色”处理
+ return null;
+ }
+}
diff --git a/js/color-picker/index.ts b/js/color-picker/index.ts
index 7df636765b..121acb28e5 100644
--- a/js/color-picker/index.ts
+++ b/js/color-picker/index.ts
@@ -2,6 +2,7 @@ export * from './cmyk';
export * from './color';
export * from './constants';
export * from './draggable';
+export * from './eyedropper';
export * from './format';
export * from './gradient';
export * from './types';
diff --git a/style/web/components/color-picker/_index.less b/style/web/components/color-picker/_index.less
index 2d04f49448..4bed428b1b 100644
--- a/style/web/components/color-picker/_index.less
+++ b/style/web/components/color-picker/_index.less
@@ -51,6 +51,17 @@
pointer-events: none;
}
}
+
+ // 吸色按钮:视觉规则复用 &__icon(组件侧同时挂载两个类),此处仅补充原生 button 元素的复位与固定尺寸
+ &__eyedropper {
+ width: @color-picker-swatch-icon-size;
+ height: @color-picker-swatch-icon-size;
+ padding: 0;
+ border: none;
+ outline: none;
+ background: none;
+ flex: none;
+ }
}
.@{prefix}-color-picker__head {
diff --git a/test/unit/color-picker/eyedropper.test.ts b/test/unit/color-picker/eyedropper.test.ts
new file mode 100644
index 0000000000..c55068fbd5
--- /dev/null
+++ b/test/unit/color-picker/eyedropper.test.ts
@@ -0,0 +1,74 @@
+// @vitest-environment jsdom
+
+import { afterEach, describe, expect, it, vi } from 'vitest';
+
+import { isEyeDropperSupported, openEyeDropper } from '../../../js/color-picker/eyedropper';
+
+/**
+ * 以指定的 open 实现 stub 全局 EyeDropper 构造器,返回其 mock 以便断言调用参数
+ */
+const stubEyeDropper = (impl: (options?: { signal?: AbortSignal }) => Promise<{ sRGBHex: string }>) => {
+ const openMock = vi.fn(impl);
+ vi.stubGlobal(
+ 'EyeDropper',
+ class {
+ open = openMock;
+ }
+ );
+ return openMock;
+};
+
+afterEach(() => {
+ vi.unstubAllGlobals();
+});
+
+describe('isEyeDropperSupported', () => {
+ it('returns false in environments without EyeDropper (SSR / old browsers)', () => {
+ expect(isEyeDropperSupported()).toBe(false);
+ });
+
+ it('returns false when the EyeDropper global is not a constructor', () => {
+ vi.stubGlobal('EyeDropper', {});
+ expect(isEyeDropperSupported()).toBe(false);
+ });
+
+ it('returns true when EyeDropper is available', () => {
+ stubEyeDropper(() => Promise.resolve({ sRGBHex: '#000000' }));
+ expect(isEyeDropperSupported()).toBe(true);
+ });
+});
+
+describe('openEyeDropper', () => {
+ it('returns null when the API is unavailable', async () => {
+ expect(await openEyeDropper()).toBeNull();
+ });
+
+ it('returns the picked color normalized to lowercase hex', async () => {
+ // 规范未强制 sRGBHex 的大小写,这里用大写验证归一化
+ stubEyeDropper(() => Promise.resolve({ sRGBHex: '#FF6600' }));
+ expect(await openEyeDropper()).toBe('#ff6600');
+ });
+
+ it('passes the AbortSignal through to the native open()', async () => {
+ const openMock = stubEyeDropper(() => Promise.resolve({ sRGBHex: '#123456' }));
+ const controller = new AbortController();
+ await openEyeDropper({ signal: controller.signal });
+ expect(openMock).toHaveBeenCalledWith({ signal: controller.signal });
+ });
+
+ it('calls open() without options when no signal is provided', async () => {
+ const openMock = stubEyeDropper(() => Promise.resolve({ sRGBHex: '#123456' }));
+ await openEyeDropper();
+ expect(openMock).toHaveBeenCalledWith(undefined);
+ });
+
+ it('returns null when the user cancels picking (AbortError)', async () => {
+ stubEyeDropper(() => Promise.reject(new DOMException('The user canceled the selection.', 'AbortError')));
+ expect(await openEyeDropper()).toBeNull();
+ });
+
+ it('returns null on unexpected native errors', async () => {
+ stubEyeDropper(() => Promise.reject(new Error('unexpected')));
+ expect(await openEyeDropper()).toBeNull();
+ });
+});