使用 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 文件或沉重的依赖。

功能

  • HEXRGBA 输出格式
  • Shadow DOM 封装,实现样式隔离
  • Canvas fallback 眼滴工具(eye‑dropper)用于 Firefox
  • 多种布局以提升可访问性
  • 原生支持 jQueryReactJSNuxtJSNextJS

要求与守则

通用兼容性

  • 该库必须在没有外部依赖的情况下工作,并且编译器无关(不使用 Vite、Babel、Turbopack 等)。
  • 构建产出:通过 Rollup 生成ESM 模块UMD 包

API 与事件

  • 暴露一个名为 color-change 的标准 CustomEvent,该事件冒泡composed,能够跨越 Shadow DOM 边界。
  • 对于 React,提供一个小包装器或使用 ref,以确保在 React 18/19 中无缝绑定事件。

服务器端渲染(SSR)安全性

  • 对所有浏览器特定的 API(windowdocumentcustomElements 等)进行检查或延迟执行,直至组件挂载后再使用。
  • 该库必须能够在 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,确保渐变生成符合质量预期。
0 浏览
Back to Blog

相关文章

阅读更多 »