面试官:给我实现一个图片标注工具,截图标注,讲一下思路

这个工具是干啥的?

就像用画图软件在图片上画框框

  • 打开一张图片
  • 选择红色/黄色/蓝色框框
  • 在图片上拖拽画框
  • 画错了可以撤销(后退)
  • 撤销了又想恢复(前进)

核心代码(去掉复杂术语)

1. 先定义"框框"长什么样

typescript 复制代码
// 一个框框需要哪些信息?
interface MarkerType {
  x: number,        // 框框左上角的横坐标
  y: number,        // 框框左上角的纵坐标  
  width: number,    // 框框有多宽
  height: number,   // 框框有多高
  type: "1" | "2" | "3"  // 框框类型:1=红,2=黄,3=蓝
}

// 整张图片的信息
interface ImgMarkerType {
  img: string,      // 图片网址
  markList: MarkerType[]  // 所有的框框
}

2. 准备工具和材料

typescript 复制代码
// 主要的数据
const imgMarkerData = reactive<ImgMarkerType>({
  img: "https://图片网址.com",  // 要标注的图片
  markList: []  // 开始时空的,画了框框就往里加
});

// 当前选中的颜色
const nowType = ref<"1" | "2" | "3">("1");  // 默认红色

// 记录当前显示到第几个框框(用于前进后退)
const nowIndex = ref(0);

// 是否正在画框(鼠标按下才开始画)
const canDraw = ref(false);

3. 最重要的功能:在画布上画画

typescript 复制代码
function renderCanvas() {
  if (!ctx.value) return;
  
  // 1. 先擦干净画布
  ctx.value.clearRect(0, 0, 800, 800);
  
  // 2. 画上背景图片
  if (img.value) {
    ctx.value.drawImage(img.value, 0, 0, 800, 800);
  }
  
  // 3. 画所有的框框
  imgMarkerData.markList.forEach((marker, index) => {
    // 前进后退功能:只画到当前步骤的框框
    if (index > nowIndex.value) return;
    
    // 根据类型设置颜色
    let color = "red";
    if (marker.type === "2") color = "yellow";
    if (marker.type === "3") color = "blue";
    
    // 画框框
    ctx.value!.strokeStyle = color;
    ctx.value!.strokeRect(marker.x, marker.y, marker.width, marker.height);
  });
}

4. 鼠标操作:怎么画框框?

typescript 复制代码
// 鼠标按下:开始画框
function mousedown(e: MouseEvent) {
  canDraw.value = true;  // 告诉程序:开始画了!
  
  // 计算鼠标点击的位置
  const rect = (e.target as HTMLCanvasElement).getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  
  // 创建一个新框框(开始很小,宽高都是0)
  const newMarker: MarkerType = {
    x: x,
    y: y,
    width: 0,
    height: 0,
    type: nowType.value  // 用当前选中的颜色
  };
  
  // 把新框框加到列表里
  imgMarkerData.markList.push(newMarker);
  nowIndex.value = imgMarkerData.markList.length - 1;
}

// 鼠标移动:拖拽调整框框大小
function mousemove(e: MouseEvent) {
  if (!canDraw.value) return;  // 如果没按下鼠标,就不画
  
  // 计算当前鼠标位置
  const rect = (e.target as HTMLCanvasElement).getBoundingClientRect();
  const currentX = e.clientX - rect.left;
  const currentY = e.clientY - rect.top;
  
  // 找到当前正在画的框框
  const currentMarker = imgMarkerData.markList[nowIndex.value];
  
  // 更新框框的大小(当前位置 - 起点位置)
  currentMarker.width = currentX - currentMarker.x;
  currentMarker.height = currentY - currentMarker.y;
  
  // 重新画一遍(这样就能看到框框在变大)
  renderCanvas();
}

// 鼠标松开:结束画框
function mouseup() {
  canDraw.value = false;  // 告诉程序:画完了!
  renderCanvas();  // 最后画一次
}

5. 前进后退功能

typescript 复制代码
// 后退:撤销上一步
function goBack() {
  if (nowIndex.value > 0) {
    nowIndex.value--;  // 少显示一个框框
    renderCanvas();
  }
}

// 前进:恢复撤销的操作
function goForward() {
  if (nowIndex.value < imgMarkerData.markList.length - 1) {
    nowIndex.value++;  // 多显示一个框框
    renderCanvas();
  }
}

6. 切换框框颜色

typescript 复制代码
// 很简单,就是改一下当前选中的类型
function changeType(type: "1" | "2" | "3") {
  nowType.value = type;
}

下面👇🏻说一下主要思想吧:跟着来是实现,实战一下前端的一些功能,以不变应万变。


  1. 页面加载时:显示图片,准备画布
  2. 选择颜色:点击"标注1"(红)、"标注2"(黄)、"标注3"(蓝)
  3. 开始画框
    • 在图片上按下鼠标(记录起点)
    • 拖拽鼠标(框框跟着变大)
    • 松开鼠标(框框完成)
  4. 修改错误
    • 点"后退":撤销最后一个框框
    • 点"前进":恢复刚才撤销的框框

为什么这样设计?

核心思想:数据驱动

  • 不直接操作画面,而是操作数据
  • 数据变了,画面自动更新
  • 就像玩橡皮泥:你只管捏形状,眼睛会自动看到变化

好处

  • 代码清晰好维护
  • 功能容易扩展(比如加个绿色框框很简单)
  • 前进后退实现简单(只是改个数字)

这样设计后,无论功能多复杂,只要想清楚"数据怎么变",代码就很好写了!

相关推荐
Mintopia3 小时前
🧩 隐私计算技术在 Web AIGC 数据处理中的应用实践
前端·javascript·aigc
尘世中一位迷途小书童3 小时前
代码质量保障:ESLint + Prettier + Stylelint 三剑客完美配置
前端·架构
Mintopia3 小时前
🧭 Next.js 架构与运维:当现代前端拥有了“分布式的灵魂”
前端·javascript·全栈
尘世中一位迷途小书童3 小时前
从零搭建:pnpm + Turborepo 项目架构实战(含完整代码)
前端·架构
JarvanMo3 小时前
Flutter 中的 ClipRRect | 每日 Flutter 组件
前端
某柚啊3 小时前
iOS移动端H5键盘弹出时页面布局异常和滚动解决方案
前端·javascript·css·ios·html5
心.c3 小时前
如何学习Lodash源码?
前端·javascript·学习
JamSlade3 小时前
react 无限画布难点和实现
前端·react.js
im_AMBER3 小时前
React 02
前端·笔记·学习·react.js·前端框架
浩男孩3 小时前
🍀我实现了个摸鱼聊天室🚀
前端