开篇
最近在做一个图片截图的功能。 因为工作时间很紧张, 当时是使用的是一个截图插件。 周末两天无所事事,来写一个简单版本的截图功能。 因为写的比较简单,如果写的不好,求大佬轻一点喷
读取图片并获取图片的宽度和高度思路
首先读取文件我们使用input中类型是file。 我们需要对读取的对象进行限制,必须是图片类型。 这个可以使用这个属性:accept="image/*" 来实现 但是这个属性不可靠,最好还是通过正则来判断。 我们要获取图片的宽和高,需要创建FileReader对象。 使用reader.readAsDataURL(file)异步读取文件内容, 并将其编码为一个Data URL(数据URL) 当文件读取完成之后,会触发reader.onload事件。 这个时候我们还要创建一个图片对象。 等待这个图片读取完成后,通过 img.width, img.height返回图片的宽和高。 下面我们就来简单实现一下
读取图片并获取图片的宽度
<div> <input type="file" id="file" accept="image/*" /> </div>
<script> // 获取文件节点 let fileNode = document.getElementById("file") // 给文件节点注册事件 fileNode.addEventListener("change", readFile) // 读取文件,然后返回宽度和高度 function readFile(e){ let file = e.target.files[0] getImageWH(file, function(width, height) { console.log('Width:', width, 'Height:', height); }); } // 返回文件(图片的宽和高) function getImageWH(file, callback) { // 创建一个FileReader实例 const reader = new FileReader(); // 当文件读取完成时触发 reader.onload = function(e) { // e 这个对象中包含这个图片相关的属性 console.log('e这个对象', e) // 创建一个新的Image对象 const img = new Image(); // 设置Image的src为读取到的文件内容 img.src = e.target.result; // 当图片加载时触发 img.onload = function() { // 调用回调函数,并传入图片的宽高 callback(img.width, img.height); }; }; // 开始读取文件内容,以DataURL的形式 // reader.onload 方法的执行需要调用下面这个 reader.readAsDataURL reader.readAsDataURL(file); } </script>

将图片的宽高赋值给canvas
我们在获取图片的宽和高之后然后赋值给canvas。 并且将canvas给显示出来就行。 这一步比较简单
<style> .canvas-box{ border: 1px solid red; display: none; } </style> <canvas id="canvas-node" class="canvas-box"></canvas>
// 获取canvas节点 let canvasNode = document.getElementById("canvas-node") // 读取文件 function readFile(e){ let file = e.target.files[0] getImageWH(file, function(width, height) { // 将宽度和高度传给canvasSetWH函数,显示在页面上 canvasSetWH(canvasNode,width, height) }); } function canvasSetWH(canvasNode,width, height){ canvasNode.width = width canvasNode.height = height canvasNode.style.display = "block" }

将图片内容在canvas中显示出来
想要将图片绘制出来,此时我们需要借助drawImage这个API。 这个API有三种形式的传参 第1种:drawImage(image, x, y) image: 绘制的图像源 x, y: 图像在画布上的起始坐标(x,y), 图像将以原始尺寸绘制 第2种:drawImage(image, x, y, width, height) image: 绘制的图像源 x, y: 图像在画布上的起始坐标(x,y) width, height(可选):绘制到画布上的图像的宽度和高度 第3种: drawImage(image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) image: 绘制的图像源 sx, sy: 图像源中矩形区域的起始坐标 swidth, sheight:图像源中矩形区域的宽度和高度,即要绘制的图像部分 dx, dy:绘制到画布上的起始坐标 dwidth, dheight:绘制到画布上的矩形区域的宽度和高度,允许对绘制的图像进行缩放 也就是说:我们这里绘制可以使用第1种方法和第2种方法。 图像源在getImageWH 这个方法中返回来。
// 返回文件(图片的宽和高和图像源) function getImageWH(file, callback) { // ....其他代码..... // 当文件读取完成时触发 reader.onload = function(e) { // ....其他代码..... // 当图片加载时触发 img.onload = function() { // 调用回调函数,返回图像源,图片的宽度,高度 callback(img,img.width, img.height); }; }; // 开始读取文件内容,以DataURL的形式 // reader.onload 方法的执行需要调用下面这个 reader.readAsDataURL reader.readAsDataURL(file); } // 获取canvas节点 let canvasNode = document.getElementById("canvas-node") // 创建上下文 let ctx = canvasNode.getContext("2d") function readFile(e){ let file = e.target.files[0] getImageWH(file, function(img, width, height) { // 将宽度和高度传给canvasSetWH函数,显示在页面上 canvasSetWH(canvasNode,width, height) // 将图片绘制出来 ctx.drawImage(img, 0, 0,width, height ); }); }

绘制蒙层
绘制蒙层这一步相对比较简单。 我们只需要在图片上绘制一个跟图片大小一样的蒙层即可。 可以借助fillStyle来填充颜色。fillRect绘制矩形。 下面我们简单实现一下
// 调用绘制蒙层的方法(在绘制图片完成后调用这个函数) drawMask(0,0,width, height); //绘制蒙层 function drawMask(x, y, width, height, opactity) { ctx.fillStyle = "rgba(0,0,0,0.5)"; ctx.fillRect(x, y, width, height); }

绘制截图区域
我们需要给canvas绑定鼠标按下事件。 在鼠标按下的时候记录上当前鼠标的坐标信息(x,y) 在鼠标按下的时候还要注册移动事件和抬起事件。 在鼠标移动的时候计算出蒙层的位置信息(rectEndX,rectEndY) 然后计算出截图区域的位置信息 最后还需要鼠标抬起的时候要移除移动事件和抬起事件 下面我们来简单实现一下
.... 其他代码..... // 图像源 let img = new Image(); // 注册事件用于得到鼠标按下时的偏移量 canvasNode.addEventListener("mousedown", mousedownInCanvasHandler) let currentPoint = {} // 鼠标按下 function mousedownInCanvasHandler(e){ currentPoint= { x: e.offsetX, y: e.offsetY } // 按下鼠标的时候我们需要注册移动事件和抬起事件 canvasNode.addEventListener('mousemove', mousemoveInCanvasHandler) canvasNode.addEventListener('mouseup', mouseupInCanvasHandler) } // 绘制矩形 function mousemoveInCanvasHandler(e){ let rectEndX = e.offsetX let rectEndY = e.offsetY // 得到矩形的宽度和高度 let rectWidth = rectEndX - currentPoint.x let rectHeight = rectEndY - currentPoint.y let {width, height} = canvasNode ctx.clearRect(0, 0, width, height) // 绘制蒙层 drawMask(0,0,width, height); drawScreenShot(width, height,rectWidth, rectHeight) } // 绘制截图 function drawScreenShot( canvasWidth, canvasHeight,rectWidth,rectHeight){ // 在原图形之外画出一个矩形 ctx.globalCompositeOperation = "destination-out"; ctx.fillStyle='#000' ctx.fillRect(currentPoint.x, currentPoint.y,rectWidth,rectHeight) ctx.globalCompositeOperation ='destination-over' // 绘制截图区域的矩形 ctx.drawImage(img, 0, 0,canvasWidth, canvasHeight,0,0,canvasWidth, canvasHeight ); } // 鼠标抬起的时候要移除移动事件和抬起事件 function mouseupInCanvasHandler(e){ canvasNode.removeEventListener('mousemove', mousemoveInCanvasHandler) canvasNode.removeEventListener('mouseup', mouseupInCanvasHandler) } .... 其他代码.....

把截图的区域显示出来
我们只需要在截图完成后(鼠标抬起时) 得到截图区域的信息ctx.getImageData() 然后把截图区域的信息写入一个新的画布即可。 在绘制前先清空画布
<style> .canvas-box,.canvas2-box{ display: none; } </style> <body> <!-- 文件读取 --> <div> <input type="file" id="file" accept="image/*" /> </div> <canvas id="canvas-node" class="canvas-box"></canvas> <!-- 截图区域的图像显示在下面这个新的画布上 --> <div class="canvas2-box"> <canvas id="canvas2"></canvas> </div> </body> // 将截图区域的数据保存下来 screenshotData= [currentPoint.x, currentPoint.y, rectWidth, rectHeight] // 鼠标抬起的时候要移除移动事件和抬起事件 function mouseupInCanvasHandler(e){ canvasNode.removeEventListener('mousemove', mousemoveInCanvasHandler) canvasNode.removeEventListener('mouseup', mouseupInCanvasHandler) drawScreenShotImg(screenshotData) } // 绘制一个截图区域的信息在另外一个画布上,并且将他显示出来 function drawScreenShotImg(screenshotData){ // screenshotData是截图的开始和结束坐标 let drawData = ctx.getImageData(...screenshotData) canvasSetWH(canvas2Box,screenshotData[2],screenshotData[3]) // 先清空画布,注意清空的大小,否者会造成叠加(清除不干净) ctx2.clearRect(0,0, currentPoint.x, currentPoint.y) // 将截图区域绘制到canvas2上 ctx2.putImageData(drawData,0,0) }

将截图区域的部分下载下来
将canvas下载下来时,需要借助 语法:canvas.toDataURL(picType, encoderOptions) 参数: picType:表示的是图片的格式。默认为 image/png。 encoderOptions:从 0 到 1 的区间内选择图片的质量。 如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。 获取图片的类型我们可以通过(file.type)来知道 let file = e.target.files[0] // 得到图片的类型,等会下载的时候需要 fileType = file.type
<button id="downBtn">down</button>
// 注册下载事件 downBtn.addEventListener('click',()=>{ let {width, height} = canvas2 // toDataURL的第一个参数:图片格式,默认为 image/png, // 第2个参数:可以从 0 到 1 的区间内选择图片的质量。 let imgURL = canvas2.toDataURL( fileType, 1); let link = document.createElement('a'); link.download = "截图图片"; link.href = imgURL; document.body.appendChild(link); link.click(); document.body.removeChild(link); })

截图功能全部代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>canvas实现截图功能</title> <style> .canvas-box,.canvas2-box{ display: none; } </style> </head> <body> <!-- 文件读取 --> <div> <input type="file" id="file" accept="image/*" /> </div> <canvas id="canvas-node" class="canvas-box"></canvas> <button id="downBtn">down</button> <div class="canvas2-box"> <canvas id="canvas2"></canvas> </div> </body> <script> // 获取canvas节点 let canvasNode = document.getElementById("canvas-node") // 创建上下文 let ctx = canvasNode.getContext("2d") let downBtn = document.getElementById("downBtn") let canvas2Box = document.querySelector(".canvas2-box") let canvas2 = document.getElementById("canvas2") let ctx2 = canvas2.getContext("2d") // 获取文件节点 let fileNode = document.getElementById("file") // 给文件节点注册事件 fileNode.addEventListener("change", readFile) // 图像源 let img = new Image(); // 截图区域的数据 let screenshotData = [] let fileType = "" // 文件的类型,下载的时候需要 // 注册事件用于得到鼠标按下时的偏移量 canvasNode.addEventListener("mousedown", mousedownInCanvasHandler) let currentPoint = {} // 注册下载事件 downBtn.addEventListener('click',()=>{ let {width, height} = canvas2 // format:表示的是图片的类型 "image/png" // toDataURL的第一个参数:图片格式,默认为 image/png, // 第2个参数:可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。 let imgURL = canvas2.toDataURL( fileType, 1); let link = document.createElement('a'); link.download = "截图图片"; link.href = imgURL; document.body.appendChild(link); link.click(); document.body.removeChild(link); }) // 鼠标按下 function mousedownInCanvasHandler(e){ currentPoint= { x: e.offsetX, y: e.offsetY } // 按下鼠标的时候我们需要注册移动事件和抬起事件 canvasNode.addEventListener('mousemove', mousemoveInCanvasHandler) canvasNode.addEventListener('mouseup', mouseupInCanvasHandler) } // 绘制矩形 function mousemoveInCanvasHandler(e){ let rectEndX = e.offsetX let rectEndY = e.offsetY // 得到矩形的宽度和高度 let rectWidth = rectEndX - currentPoint.x let rectHeight = rectEndY - currentPoint.y let {width, height} = canvasNode // 将截图区域的数据保存下来 screenshotData= [currentPoint.x, currentPoint.y, rectWidth, rectHeight] ctx.clearRect(0, 0, width, height) // 绘制蒙层 drawMask(0,0,width, height); drawScreenShot(width, height,rectWidth, rectHeight) } // 绘制截图 function drawScreenShot( canvasWidth, canvasHeight,rectWidth,rectHeight){ // 在原图形之外画出一个矩形 ctx.globalCompositeOperation = "destination-out"; ctx.fillStyle='#000' ctx.fillRect(currentPoint.x, currentPoint.y,rectWidth,rectHeight) ctx.globalCompositeOperation ='destination-over' // 绘制截图区域的矩形 ctx.drawImage(img, 0, 0,canvasWidth, canvasHeight,0,0,canvasWidth, canvasHeight ); } // 鼠标抬起的时候要移除移动事件和抬起事件 function mouseupInCanvasHandler(e){ canvasNode.removeEventListener('mousemove', mousemoveInCanvasHandler) canvasNode.removeEventListener('mouseup', mouseupInCanvasHandler) drawScreenShotImg(screenshotData) } // 绘制一个截图区域的信息在另外一个画布上,并且将他显示出来 function drawScreenShotImg(screenshotData){ // screenshotData是截图的开始和结束坐标 let drawData = ctx.getImageData(...screenshotData) canvasSetWH(canvas2Box,screenshotData[2],screenshotData[3]) // 先清空画布,注意清空的大小,否者会造成叠加(清除不干净) ctx2.clearRect(0,0, currentPoint.x, currentPoint.y) // 将截图区域绘制到canvas2上 ctx2.putImageData(drawData,0,0) } // 读取文件 function readFile(e){ let file = e.target.files[0] // 得到图片的类型,等会下载的时候需要 console.log('file.type', file.type) fileType = file.type getImageWH(file, function(width, height) { // 将宽度和高度传给canvasSetWH函数,显示在页面上 canvasSetWH(canvasNode,width, height) ctx.drawImage(img, 0, 0,width, height ); // 调用绘制蒙层的方法 drawMask(0,0,width, height); }); } // 返回文件(图片的宽和高) function getImageWH(file, callback) { // 创建一个FileReader实例 const reader = new FileReader(); // 当文件读取完成时触发 reader.onload = function(e) { // e 这个对象中包含这个图片相关的属性 console.log('e这个对象', e) // 创建一个新的Image对象 // 设置Image的src为读取到的文件内容 img.src = e.target.result; // 当图片加载时触发 img.onload = function() { // 调用回调函数,返回图像源,图片的宽度,高度 callback(img.width, img.height); }; }; // 开始读取文件内容,以DataURL的形式 // reader.onload 方法的执行需要调用下面这个 reader.readAsDataURL reader.readAsDataURL(file); } function canvasSetWH(canvasNode,width, height){ canvasNode.width = width canvasNode.height = height canvasNode.style.display = "block" } // 绘制蒙层 function drawMask(x, y, width, height, opactity) { ctx.fillStyle = "rgba(0,0,0,0.5)"; ctx.fillRect(x, y, width, height); } </script> </html>
尾声
终于写完了,这周过得很充实。。。 如果觉得写的不错,求未来的老板们点个赞,感谢!