npm package: @hexagrams/model-preview-component
Description: A React-based 3D model preview Web Component built with Three.js and React Three Fiber
Author: hexagrams
Latest version: 2.0.2

# @hexagrams/model-preview-component

基于 React Three Fiber 和 Three.js 的 3D 模型预览 Web 组件，可直接在任意网页中使用，也可在 React 项目中作为组件使用。支持多格式模型加载、动态数据卡片、以及在 SVG/foreignObject 外部缩放场景下 Html 叠层的精准对齐。

## 特性

- Web Component 与 React 组件双形态使用
- 支持模型格式：`glb`、`gltf`、`fbx`、`obj`
- 动态卡片：通过 WebComponent `data-cards` 属性或 React `dataCards` props 实时更新
- Html 叠层抗缩放：在外部 `SVG/foreignObject` 缩放场景下保持与网格对齐（portal + 反缩放）
- 场景配置：背景、环境光、方向光、环境贴图、模型位姿、相机默认位置
- 事件通信：相机变化、模型点击、加载状态、组件就绪/错误

## 安装

```bash
npm install @hexagrams/model-preview-component
```

## 快速开始

### 作为 ES 模块引入

```html
<!DOCTYPE html>
<html>
  <body>
    <model-preview-component model-url="/model.glb"></model-preview-component>
    <script type="module">
      import '@hexagrams/model-preview-component';
    </script>
  </body>
</html>
```

### 直接引入脚本

```html
<model-preview-component model-url="/model.glb" data-cards='[{"position":[0.27,0.006,0.016],"data":{"title":"温度","value":"30℃"}}]'></model-preview-component>
<script type="module" src="https://unpkg.com/@hexagrams/model-preview-component/dist/model-preview-component.es.js"></script>
```

## Web Component 属性

| 属性         | 类型           | 默认值 | 描述                                                                 |
|--------------|----------------|--------|------------------------------------------------------------------------|
| `model-url`  | string         | -      | 模型文件 URL（支持 `glb/gltf/fbx/obj`）                                |
| `config`     | string (JSON)  | `{}`   | 场景/光照/相机/模型位姿等配置                                         |
| `data-cards` | string (JSON)  | `[]`   | 卡片数组：`{ position: [x,y,z], data: { title, value } }`              |

示例（配置与卡片）：

```html
<model-preview-component 
  model-url="/model.glb"
  config='{"background":"linear-gradient(to bottom,#1e3a8a,#3b82f6,#93c5fd)","ambientColor":"#ffffff","ambientIntensity":0.5,"directionalColor":"#ffffff","directionalIntensity":1,"environmentBlur":0.1,"modelPosition":[0,0,0],"modelScale":1,"defaultCameraPosition":[1.36,0.23,-1.36],"defaultCameraTarget":[-0.08,0.002,0.205] }'
  data-cards='[{"position":[0.27,0.006,0.016],"data":{"title":"温度","value":"30℃"}}]'
></model-preview-component>
```

动态更新卡片：

```js
const el = document.querySelector('model-preview-component');
const cards = [{ position: [0.27, 0.006, 0.016], data: { title: '温度', value: '30℃' } }];
el.setAttribute('data-cards', JSON.stringify(cards));
// 或
el.dataCards = cards;
```

## React 组件用法

```tsx
import ModelViewer from './components/ModelViewer';

const config = {
  background: 'linear-gradient(to bottom, #1e3a8a, #3b82f6, #93c5fd)',
  ambientColor: '#ffffff',
  ambientIntensity: 0.5,
  directionalColor: '#ffffff',
  directionalIntensity: 1,
  environmentFiles: '/hdri/potsdamer_platz_1k.hdr',
  environmentBlur: 0.1,
  modelPosition: [0, 0, 0],
  modelScale: 1,
  defaultCameraPosition: [1.36, 0.23, -1.36],
  defaultCameraTarget: [-0.08, 0.002, 0.205],
};

const dataCards = [
  { position: [0.27, 0.006, 0.016], data: { title: '温度', value: '30℃' } },
];

<ModelViewer modelUrl="/model.glb" config={config} dataCards={dataCards} />
```

### React Props（摘要）

- `modelUrl: string`
- `config: ViewerConfig`
- `dataCards?: { position: [number, number, number]; data: { title: string; value: any } }[]`
- 回调：`onCameraChange`、`onModelClick`、`onConfigChange`

### ViewerConfig 关键字段

- 背景：`background: string`
- 环境光：`ambientColor: string`、`ambientIntensity: number`
- 方向光：`directionalColor: string`、`directionalIntensity: number`
- 环境贴图：`environmentFiles: string | string[]`、`environmentBlur: number`
- 模型位姿：`modelPosition: [number, number, number]`、`modelScale: number`
- 相机：`defaultCameraPosition: [number, number, number]`、`defaultCameraTarget: [number, number, number]`

## 支持的模型格式

- `glb/gltf`：通过 `useGLTF`
- `fbx`：通过 `FBXLoader`
- `obj`：通过 `OBJLoader`

路径扩展名自动选择加载器；未知扩展名将回退到 GLTF 并输出警告。

## 事件

- `model-load-start` / `model-load-success` / `model-load-error`
- `model-click`：返回点击位置与交点
- `camera-change`：返回相机位置与目标
- `component-ready` / `component-error`

事件类型参考：`src/types/events.ts`。

## Html 叠层抗缩放（SVG/foreignObject）

组件在 Canvas 外部被 `SVG/foreignObject` 非一致缩放时，Html 叠层会通过自定义 portal 容器施加反向缩放，使其与 3D 网格保持像素级对齐。该容器的尺寸与 Canvas `clientWidth/Height` 同步，并基于 `getBoundingClientRect()` 计算缩放比，应用 `transform: scale(scaleX, scaleY)` 与 `transformOrigin: 0 0`。这避免了在多个 Html 节点上分别处理缩放的副作用。

## 构建与本地预览

```bash
npm install
npm run dev        # 开发预览（默认 5173/5174）
npm run build      # 打包（生成 dist/*.js）
npm preview        # 本地预览打包产物
```

## 发布到 npm

```bash
npm run build
npm pack                     # 可选：本地生成 tarball 验证
npm publish --access public  # 需已登录 npm
```

## 变更日志

- 2.0.1
  - 文档重写并对齐项目现状（WebComponent 与 React 用法、属性与事件、模型格式、Html 叠层抗缩放、发布流程）。
- 2.0.0
  - React 组件：将 `dataCards` 从 `config` 中拆分为独立 props。
  - WebComponent：新增并推荐使用 `data-cards` 属性动态更新卡片。
  - 修复在 `SVG/foreignObject` 外部缩放场景下 Html 叠层漂移（portal + 反缩放）。

## 许可证

MIT 许可证，详见 `LICENSE`。