使用 Google Antigravity 的 Vibe coding React Color Picker
发布: (2026年2月24日 GMT+8 16:27)
5 分钟阅读
原文: Dev.to
Source: Dev.to
请提供您希望翻译的完整文本内容(除代码块和 URL 之外),我将把它翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。谢谢!
概览
我在这个周末使用 Google Antigravity 为我的 ReactJS/NextJS 项目即兴编写了一个 Color Picker 的 MVP 版本。其目标并非单纯的 JavaScript 自测,而是想挑战一下 Prompt Engineering 能否生成我在项目中经常使用的组件。
技术栈
- Lit – 用于构建 Web 组件
- Open Web Components – 作为起点的生成器
- Rollup – 模块打包工具(配置为同时输出 ESM 和 UMD)
- Chrome EyeDropper API – 原生取色器支持
- ElementInternals – 表单兼容性
- Canvas‑based fallback – 针对缺少 EyeDropper API 的浏览器(如 Firefox)的回退方案
所有 CSS 均内置并封装在组件的 Shadow DOM 中;没有外部 .css 文件或沉重的依赖。
功能
- HEX 和 RGBA 输出格式
- Shadow DOM 封装,实现样式隔离
- Canvas fallback 眼滴工具(eye‑dropper)用于 Firefox
- 多种布局以提升可访问性
- 原生支持 jQuery、ReactJS、NuxtJS 和 NextJS
要求与守则
通用兼容性
- 该库必须在没有外部依赖的情况下工作,并且编译器无关(不使用 Vite、Babel、Turbopack 等)。
- 构建产出:通过 Rollup 生成ESM 模块和UMD 包。
API 与事件
- 暴露一个名为
color-change的标准CustomEvent,该事件冒泡且composed,能够跨越 Shadow DOM 边界。 - 对于 React,提供一个小包装器或使用
ref,以确保在 React 18/19 中无缝绑定事件。
服务器端渲染(SSR)安全性
- 对所有浏览器特定的 API(
window、document、customElements等)进行检查或延迟执行,直至组件挂载后再使用。 - 该库必须能够在 Node.js 环境中导入,而不会抛出
ReferenceError: window is not defined。
表单集成
- 当组件放置在 “ 中时,应使用 ElementInternals API,表现得像原生表单控件。
性能
- 避免使用笨重的外部颜色数学库;实现轻量级的内部工具函数。
- 严格使用 Shadow DOM 进行样式封装。
实现细节
Rollup 配置 (rollup.config.js)
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';
import copy from 'rollup-plugin-copy';
export default {
input: 'src/color-picker.js',
output: [
{
file: 'dist/color-picker.esm.js',
format: 'esm',
sourcemap: true,
},
{
file: 'dist/color-picker.umd.js',
format: 'umd',
name: 'ColorPicker',
sourcemap: true,
},
],
plugins: [
nodeResolve(),
terser(),
copy({
targets: [{ src: 'src/*.css', dest: 'dist' }],
}),
],
};
核心组件 (color-picker.js)
import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
@customElement('color-picker')
class ColorPicker extends LitElement {
static styles = css`
:host { display: block; }
/* component‑specific styles go here */
`;
@property({ type: String }) value = '#000000';
@state() _eyeDropperSupported = false;
constructor() {
super();
if (typeof window !== 'undefined' && 'EyeDropper' in window) {
this._eyeDropperSupported = true;
}
}
connectedCallback() {
super.connectedCallback();
// Ensure any browser‑only APIs run after mount
}
_dispatchColorChange(color) {
this.dispatchEvent(new CustomEvent('color-change', {
detail: { color },
bubbles: true,
composed: true,
}));
}
async _activateEyeDropper() {
if (!this._eyeDropperSupported) return;
try {
const eyeDropper = new EyeDropper();
const { sRGBHex } = await eyeDropper.open();
this.value = `#${sRGBHex}`;
this._dispatchColorChange(this.value);
} catch (e) {
console.error('EyeDropper cancelled or failed', e);
}
}
render() {
return html`
{
this.value = e.target.value;
this._dispatchColorChange(this.value);
}}>
${this._eyeDropperSupported
? html\`Eye Dropper\`
: html\`\`}
`;
}
}
React 包装组件 (ColorPickerReact.jsx)
import React, { useRef, useEffect } from 'react';
import '@my-org/color-picker'; // registers
export default function ColorPickerReact({ onChange, ...props }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
const handler = e => onChange?.(e.detail.color);
el?.addEventListener('color-change', handler);
return () => el?.removeEventListener('color-change', handler);
}, [onChange]);
return ;
}
Web Test Runner 示例
import { fixture, expect, a11yCheck } from '@open-wc/testing-helpers';
import './color-picker.js';
describe('color-picker', () => {
it('passes accessibility audit', async () => {
const el = await fixture('');
await expect(el).to.be.accessible();
});
it('dispatches color-change on input', async () => {
const el = await fixture('');
const input = el.shadowRoot.querySelector('input');
const spy = sinon.spy();
el.addEventListener('color-change', spy);
input.value = '#ff0000';
input.dispatchEvent(new Event('input'));
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][0].detail.color).to.equal('#ff0000');
});
});
未来步骤
- 重构代码,使其更 人性化 并提升可读性。
- 更深入地研究 颜色转换背后的数学原理,以便让库具备更好的可扩展性和可维护性。
- 根据反馈完善 UI/UX,确保渐变生成符合质量预期。