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

这个工具是干啥的?

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

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

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

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. 修改错误
    • 点"后退":撤销最后一个框框
    • 点"前进":恢复刚才撤销的框框

为什么这样设计?

核心思想:数据驱动

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

好处

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

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

相关推荐
mldong6 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排6 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60617 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角8 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!8 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰8 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi10 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen11 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马11 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒12 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端