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

这个工具是干啥的?

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

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

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

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

为什么这样设计?

核心思想:数据驱动

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

好处

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

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

相关推荐
喵桑丶2 小时前
无界(微前端框架)
前端·javascript
leeggco2 小时前
AI数字人可视化图表设计文档
前端
我是天龙_绍2 小时前
仿一下微信的截图标注功能
前端
_AaronWong2 小时前
前端工程化:基于Node.js的自动化版本管理与发布说明生成工具
前端·javascript·node.js
Healer9183 小时前
纯css实现高度0-auto动画过度interpolate-size 和 height: calc-size(auto,size)
前端
智慧源点3 小时前
解决 Vite + React 项目部署 GitHub Pages 的完整指南:从 404 到成功部署
前端·react.js·github
葡萄城技术团队3 小时前
浏览器端音视频处理新选择:Mediabunny 让 Web 媒体开发飞起来
前端·音视频·媒体
FogLetter3 小时前
深入浅出 JavaScript 闭包:从背包理论到实战应用
前端·javascript
前端大卫3 小时前
表单与上传组件校验
前端·javascript·vue.js