如何使用JavaScript在图片上绘制可写入内容的矩形区域
原生JS实现图片上可写入内容的交互矩形绘制
核心实现采用三层叠加结构,无需引入第三方库,兼容桌面端和移动端场景:
- 最底层:展示习题原图
- 中间层:透明Canvas层,捕获鼠标/触摸事件,实时绘制矩形选框边框
- 最上层:选框绘制完成后,在对应坐标挂载可编辑输入组件,支持内容写入、编辑、删除
具体实现步骤
1. 搭建基础HTML结构
外层容器设置相对定位,所有内部元素基于容器做位置校准,避免缩放、偏移导致的选框错位:
<div class="worksheet-wrap" style="position: relative; display: inline-block; line-height: 0;"> <img id="sheetImg" alt="习题图片" src="your-target-image.jpg"> <canvas id="drawLayer" style="position: absolute; top:0; left:0; cursor: crosshair;"></canvas> <div id="inputLayer" style="position: absolute; top:0; left:0; width: 100%; height: 100%;"></div> </div>
2. 初始化尺寸校准
等图片加载完成后,将Canvas层尺寸和图片实际渲染尺寸完全对齐,统一绘制样式:
const img = document.getElementById('sheetImg'); const canvas = document.getElementById('drawLayer'); const ctx = canvas.getContext('2d'); const inputLayer = document.getElementById('inputLayer'); let isDrawing = false; let drawStart = {x:0, y:0}; // 存储所有已创建的答题区域数据,可直接序列化存储 const answerAreaList = []; img.onload = () => { canvas.width = img.offsetWidth; canvas.height = img.offsetHeight; ctx.strokeStyle = '#165DFF'; ctx.lineWidth = 2; }
3. 实现拖拽绘制矩形逻辑
监听鼠标事件,拖拽过程中实时预览矩形效果,拖拽结束后校验选框有效性:
// 鼠标按下:记录绘制起点 canvas.addEventListener('mousedown', (e) => { isDrawing = true; const canvasRect = canvas.getBoundingClientRect(); drawStart.x = e.clientX - canvasRect.left; drawStart.y = e.clientY - canvasRect.top; }) // 鼠标移动:实时绘制临时矩形预览 canvas.addEventListener('mousemove', (e) => { if (!isDrawing) return; const canvasRect = canvas.getBoundingClientRect(); const currentX = e.clientX - canvasRect.left; const currentY = e.clientY - canvasRect.top; // 清空画布重绘,避免预览残影 ctx.clearRect(0, 0, canvas.width, canvas.height); redrawSavedAreas(); // 绘制当前拖拽的临时选框 ctx.strokeRect( drawStart.x, drawStart.y, currentX - drawStart.x, currentY - drawStart.y ); }) // 鼠标抬起:完成绘制,生成可编辑区域 canvas.addEventListener('mouseup', (e) => { if (!isDrawing) return; isDrawing = false; const canvasRect = canvas.getBoundingClientRect(); const endX = e.clientX - canvasRect.left; const endY = e.clientY - canvasRect.top; // 计算选框真实坐标和尺寸,兼容反向拖拽(从右下往左上选的场景) const area = { x: Math.min(drawStart.x, endX), y: Math.min(drawStart.y, endY), w: Math.abs(endX - drawStart.x), h: Math.abs(endY - drawStart.y), content: '' } // 过滤尺寸过小的误触选框 if (area.w < 20 || area.h < 20) { ctx.clearRect(0,0,canvas.width, canvas.height); redrawSavedAreas(); return; } answerAreaList.push(area); mountInputArea(area); }) // 重绘所有已保存的选框边框 function redrawSavedAreas() { answerAreaList.forEach(item => { ctx.strokeRect(item.x, item.y, item.w, item.h); }) }
4. 绑定选框内容写入能力
选框绘制完成后,在对应坐标挂载和选框尺寸完全匹配的多行输入框,支持内容编辑、删除:
function mountInputArea(area) { const textarea = document.createElement('textarea'); // 输入框样式和选框完全对齐 textarea.style.cssText = ` position: absolute; left: ${area.x}px; top: ${area.y}px; width: ${area.w}px; height: ${area.h}px; border: none; background: rgba(22, 93, 255, 0.08); padding: 4px 6px; font-size: 15px; resize: none; outline: none; line-height: 1.4; `; // 实时同步输入内容到存储数据 textarea.addEventListener('input', () => { area.content = textarea.value; }) // 右键点击选框可删除 textarea.addEventListener('contextmenu', (e) => { e.preventDefault(); textarea.remove(); const idx = answerAreaList.indexOf(area); if (idx > -1) answerAreaList.splice(idx, 1); ctx.clearRect(0,0,canvas.width, canvas.height); redrawSavedAreas(); }) inputLayer.appendChild(textarea); textarea.focus(); }
扩展适配说明
- 移动端适配:将上述鼠标事件替换为对应
touchstart/touchmove/touchend事件,坐标计算时取e.touches[0]的触点坐标即可 - 学生端下发:下发习题时可关闭Canvas绘制权限,清空所有输入框内容,只保留选框边框即可
- 选框调整能力:如果需要支持已绘制选框的拖拽移动、尺寸调整,可给每个选框增加8个控制点,监听拖拽事件更新坐标和输入框位置即可
- 数据存储:所有选框的坐标、尺寸、填写内容都存在
answerAreaList数组中,可直接序列化为JSON存入后端,下次加载时自动重绘选框、回填内容即可
内容的提问来源于stack exchange,提问作者Mahmoud Ramadan
相关产品推荐
相关产品推荐

