Skip to content

hetailang/GridFlow

Repository files navigation

GridFlow - 动态拼图生成器

基于网格布局的交互式图片拼接工具,支持实时调整布局和导出高清拼图。

使用示例

主界面演示 主界面演示

导出 alt text

✨ 核心功能

1. 多种上传方式

  • 📷 拖放上传 - 直接拖放图片文件到网格区域
  • 🖱️ 点击选择 - 点击区域打开文件浏览器
  • ⌨️ 剪贴板粘贴 - 使用 Ctrl+V 直接粘贴截图

2. 灵活的布局系统

  • 支持 1-9 张图片的多种布局模式
  • 可拖动边框 - 拖动分隔线实时调整每个区域的大小
  • 智能初始布局(左一右二、2x2网格、2x3网格等)

3. 图片管理

  • 删除功能 - 鼠标悬停时在图片右上角显示删除按钮
  • 🔀 拖拽换位 - 在布局调整阶段,直接将已有图片拖拽到另一个格子即可互换位置
  • 🎯 智能裁剪 - 自动 Center Crop 填充容器

4. 样式自定义

  • 调整图片间距(0-50px)
  • 设置圆角大小(0-50px)
  • 自定义背景颜色
  • 多种画布比例(16:9、4:3、1:1、9:16、3:4)

5. 精细调整(Phase 2)

  • 🎛️ 点击"进入精细调整"将网格布局转化为自由画布
  • 移动 - 拖动任意元素自由定位,可相互重叠
  • 缩放 - 拖动 8 个边角/边缘 handle 等比或拉伸缩放,旋转后仍保持正确的数学关系
  • 旋转 - 拖动元素正上方的旋转手柄顺时针/逆时针旋转
  • 图层管理 - 上移/下移/置顶/置底调整元素叠放顺序
  • 属性面板 - 精确输入 X/Y/宽/高/旋转角度/圆角数值
  • 图片裁剪 - 调整图片在元素框内的内容缩放(0.1x–2.0x)及 X/Y 偏移,独立于元素大小
  • 自适应图片大小 - 一键将容器宽高比调整为与图片原始比例一致,使图片完整显示、无裁剪

6. 高清导出

  • 💾 导出为 2400px 宽度的高清 PNG 图片
  • 保持所有样式设置和布局比例
  • Phase 2 精细调整后同样支持导出和复制到剪贴板

🚀 快速开始

安装依赖

npm install

启动开发服务器

npm run dev

访问 http://localhost:3000/

构建生产版本

npm run build

🎯 使用方法

Phase 1 - 布局调整

  1. 选择图片数量 - 在右侧控制面板选择要拼接的图片数量
  2. 添加图片 - 通过拖放、点击或粘贴方式添加图片
  3. 调整布局 - 拖动图片之间的分隔线调整区域大小;拖动已有图片到另一格可互换位置
  4. 自定义样式 - 调整间距、圆角、背景色等参数
  5. 删除图片 - 鼠标悬停在图片上,点击右上角的删除按钮
  6. 导出拼图 - 点击"导出图片"按钮下载最终作品

Phase 2 - 精细调整

  1. 进入精细调整 - 点击控制面板的"进入精细调整"按钮,网格布局将转化为自由画布
  2. 移动元素 - 拖动图片元素到任意位置(支持超出画布边界)
  3. 缩放元素 - 拖动 8 个 handle 调整大小,旋转后仍保持正确方向
  4. 旋转元素 - 拖动元素上方的蓝色圆形旋转手柄
  5. 调整图层 - 在属性面板点击图层按钮调整元素的叠放顺序
  6. 精确调整 - 在属性面板直接输入数值精确控制位置、尺寸、旋转角度、圆角
  7. 图片裁剪 - 在属性面板"图片裁剪"区域调整内容缩放(<1 显示更多内容,>1 放大聚焦)及偏移
  8. 自适应图片大小 - 点击"图片裁剪"区域的"自适应图片大小"按钮,容器高度自动调整为与图片原始比例一致,同时重置缩放和偏移
  9. 导出结果 - 在精细调整面板点击导出或复制,输出 2400px 宽高清 PNG

🛠 技术栈

  • React 18 - UI 框架
  • Vite 5 - 构建工具
  • Canvas API - 图片渲染和导出
  • 原生 CSS - 样式设计

📝 项目结构

GridFlow/
├── src/
│   ├── components/
│   │   ├── ImageCell.jsx           # 图片单元格组件(拖放/点击/粘贴)
│   │   ├── GridLayout.jsx          # Phase 1 网格布局组件
│   │   ├── Divider.jsx             # 可拖动分隔线组件
│   │   ├── ControlPanel.jsx        # 控制面板(Phase 1 & 2 双模式)
│   │   ├── FinetuneCanvas.jsx      # Phase 2 自由画布容器
│   │   └── FinetuneElementItem.jsx # Phase 2 可移动/缩放/旋转元素
│   ├── utils/
│   │   └── exportCanvas.js         # 导出功能(Phase 1 & 2)
│   ├── App.jsx                     # 主应用组件(含阶段切换逻辑)
│   └── main.jsx                    # 应用入口
├── index.html
├── vite.config.js
└── package.json

📒 Changelog

2026-03-14 — Phase 2 新增自适应图片大小

新增:Phase 2 一键自适应图片大小

在精细调整阶段,选中含有图片的元素后,控制面板"图片裁剪"区域顶部新增"自适应图片大小"按钮:

  • 保持容器宽度不变,自动将高度调整为 width × (naturalHeight / naturalWidth),使容器宽高比与图片原始比例完全一致
  • 同步将 cropZoom 重置为 1、cropOffsetX/Y 归零,因容器已能完整显示图片,裁剪参数无需保留
  • 容器宽高比与图片一致后,cover 填充与 contain 等效,图片完整呈现、无裁剪、无黑边
实现细节
  • ControlPanel.jsx:在 selectedElement.src 区块的"图片裁剪"标题下方新增 .fit-image-button,点击时通过 onElementChange 更新 height / cropZoom / cropOffsetX / cropOffsetY,逻辑完全内联,无需改动 App.jsx
  • ControlPanel.css:新增 .fit-image-button 样式(蓝紫色描边,hover 填充为实色)
2026-02-28 — 布局阶段图片拖拽换位 / 修复精细调整导出与预览不一致

新增:Phase 1 图片拖拽换位

在布局调整阶段,已上传图片的格子现在可直接拖拽到其他格子进行换位:

  • 鼠标悬停在有图片的格子上显示 grab 光标,提示可拖拽
  • 拖拽中源格子半透明淡出,目标格子显示蓝色描边高亮
  • 松手后两格图片完整互换(含宽高元数据);拖到同一格无操作
  • 外部文件拖入行为不受影响(通过 dataTransfer 类型区分内部/外部拖拽)

修复:Phase 2 导出与预览缩放比例不一致(三层根因)

进入精细调整阶段后,导出图片与预览显示的图片缩放比例不一致。根因分三层:

  1. cropOffset 系数错误:canvas 导出中偏移量基于缩放后尺寸 (baseW × cropZoom) 计算,而 CSS translate(%) 始终以元素原始尺寸为基准,修正为以 baseW(cover 原始尺寸)为基准
  2. calculateLayout vs CSS Grid 精度差:Phase 2 元素原先由 calculateLayout 手工计算初始化,结果与浏览器 CSS Grid 实际渲染尺寸存在 padding/2 的偏差,修正为在 handleEnterFinetune 中通过 querySelectorAll('.image-cell') + getBoundingClientRect() 直接读取 DOM 真实位置和尺寸
  3. img 渲染与 background-size: cover 行为不等价:Phase 1 使用 CSS 背景 background-size: cover 会将图片缩放至容器大小;Phase 2 使用 <img minWidth/minHeight: 100%; width: auto> 时,对自然尺寸已超过容器的图片(如照片)不会触发缩放,导致视觉上"放大",修正为在 FinetuneElementItem 中用 JS 显式计算 cover 尺寸,与 canvas 导出公式完全一致
实现细节
  • exportCanvas.js renderFinetunedToCanvas:cropOffset 改为相对于 baseW/baseH(cover 原始尺寸),而非 dw/dh(已乘 cropZoom 的绘制尺寸)
  • App.jsx handleEnterFinetune:用 querySelectorAll('.image-cell') + getBoundingClientRect() 读取真实格子位置,替代 calculateLayout 计算;同时传递 naturalWidth/naturalHeight(来自 images[cellId].width/height
  • FinetuneElementItem.jsx:移除 minWidth/minHeight/objectFit CSS 方案,改为根据 naturalWidth/naturalHeight 和当前 width/height 显式计算 baseW/baseH/drawW/drawH/drawLeft/drawTop,直接设为 img 的绝对像素样式
  • ImageCell.jsx:新增 draggable={!!image}onDragStart(写入 application/gridflow-cell 类型标识)、onDragEndisDragSource/isDragTarget 状态;handleDrop 优先检测内部拖拽并调用 onImageSwap
  • App.jsx 新增 handleImageSwap,原子性交换 images map 中两个 key 的值
2026-02-27 — 精细调整阶段新增图片裁剪控制

新增:Phase 2 图片裁剪(内容缩放 + 偏移)

选中精细调整阶段中的图片元素后,控制面板新增"图片裁剪"区域,支持独立于元素尺寸的图片内容调整:

  • 内容缩放(0.10x–2.00x,步进 0.05):小于 1 时图片缩小、显示更多内容(可留白);大于 1 时图片放大、聚焦更少区域
  • 裁剪偏移 X / Y(-100%–+100%):在缩放基础上左右/上下平移图片显示位置
  • 导出与复制完全同步裁剪参数,确保 2400px 高清输出与预览一致
实现细节
  • App.jsx 初始化 elements 时新增 cropOffsetX: 0, cropOffsetY: 0, cropZoom: 1 默认值
  • FinetuneElementItem.jsx 改用 <div overflow:hidden> + <img> 嵌套结构;图片通过 transform: translate(-50%,-50%) translate(cropOffsetX%, cropOffsetY%) scale(cropZoom) 实现缩放与偏移,minWidth/minHeight: 100% 保证基础 cover 填充
  • exportCanvas.js 先按 cover 规则计算 baseW/baseH,再乘以 cropZoom 得到绘制尺寸,最后加上 cropOffsetX/Y 相对于缩放后图片尺寸的偏移量
2026-02-23 — 新增精细调整阶段(Phase 2)

新增:精细调整阶段(Phase 2)

在 Phase 1 布局调整完成后,可点击"进入精细调整"进入 Phase 2。Phase 1 的网格布局将转化为自由画布,每张图片成为可独立操作的元素:

  • 拖动移动、8 个 handle 缩放、旋转手柄旋转
  • 属性面板精确输入坐标、尺寸、角度、圆角
  • 图层顺序管理(上移/下移/置顶/置底)
  • 导出和复制功能延续到 Phase 2,输出 2400px 高清 PNG
实现细节
  • App.jsx 新增 phase / elements / canvasDisplaySize / selectedId state,handleEnterFinetune 通过 getBoundingClientRect 获取显示尺寸后调用 calculateLayout 初始化 elements 数组
  • FinetuneElementItem.jsx 核心交互:旋转角度 = atan2(dy, dx) + 90°;resize 时通过局部坐标系变换矩阵(cosθ/sinθ)计算新 center 和尺寸,边缘 handle 投影到对应轴保证单轴约束
  • exportCanvas.js 新增 exportFinetuned / copyFinetuned,将 display 坐标缩放到 2400px,用 ctx.translate + rotate + clip + drawImage 渲染每个元素
2026-02-17 — 复制到剪贴板 / 修复粘贴目标

新增:复制图片到剪贴板

在控制面板导出区域新增「📋 复制图片」按钮,可将当前拼图直接复制到系统剪贴板,方便粘贴到微信、飞书、Figma 等应用中,无需先下载文件。

  • 按钮带有状态反馈:复制中 → 已复制 / 复制失败,2 秒后自动重置
  • 使用绿色渐变样式,与蓝紫色的导出按钮区分
实现细节
  • exportToImage 中提取公共的 renderToCanvas() 函数,复用 Canvas 渲染逻辑
  • 新增 copyToClipboard() 导出函数,通过 navigator.clipboard.write + ClipboardItem 将 PNG 写入剪贴板
  • 需要浏览器支持 Clipboard API(主流现代浏览器均已支持)

修复:Ctrl+V 粘贴图片会填充所有网格

之前每个 ImageCell 都在 document 上注册了 paste 监听器,导致粘贴时所有网格同时接收图片。现已修复为仅将图片粘贴到鼠标悬停的网格中。

实现细节
  • ImageCell 中新增 isHovered 状态,通过 onMouseEnter / onMouseLeave 追踪鼠标位置
  • paste 事件处理函数中增加 if (!isHovered) return 守卫,仅悬停的 cell 响应粘贴

📋 TODO

  • Phase 2 图片内部 pan/zoom - 精细调整阶段支持调整图片在元素框内的显示位置(类似 object-position)
  • Phase 2 键盘快捷键 - Delete 删除选中元素,方向键微移,Esc 取消选中

📄 许可证

ISC

About

Combine multiple images into one picture

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors