谈如何用canvas截取图片,新手教程...
我的想法
今天刚好无事,突然想到在很多地方都会有图片截取的功能,所以想自己也实现一下前端如何截取图片。 通过大量的搜索之后,我发现很多都是用的canvas去做的,刚好对canvas的技术不太熟悉,也顺便学习一下canvas的使用技巧,这里我用的是vue3
思考过程
按照我的想法,需要实现一个截图功能,得需要三个步骤
- 上传图片,并展示
- 截图操作
- 展示截图区域
下面就看一看简单的实现过程吧
页面结构
<div class="chat-canvas">
<canvas class="chat-canvas-box" ref="canvas"></canvas>
<img :src="imgUrl" alt="" ref="img" class="img" />
</div>
<div>
<input type="file" @change="handleFileChange" />
</div>
<div>
<img class="img2" :src="img2Url" alt="" />
</div>
css的话可以自己先随意写一点,能够理解思路就ok了
第一步 图片上传
- 通过input框的上传事件获取到图片文件,并将文件内容转换成url,放在img标签
const handleFileChange = (e) => {
const fileReader = new FileReader()
fileReader.readAsDataURL(e.target.files[0])
fileReader.onload = (e) => {
imgUrl.value = e.target.result
}
}
这里可以用FileReader转换成url链接,在本地展示
第二步 截取操作
- 先初始化一个大小和图片的canvas画框
const canvasInit = () => {
img.value.onload = () => {
canvas.value.width = img.value.width
canvas.value.height = img.value.height
}
canvas.value.addEventListener('mousedown', handleMouseDown, false)
}
这里,我用onload事件是为了获取页面图片展示的宽高一致,如果是一个固定的截图区域,也可以写一个固定的值,canvas的宽高不会影响到后面比例的问题 2. 给canvas添加事件监听mousedown,mousemove,mouseup
// 鼠标按下
const handleMouseDown = (e) => {
canvas.value.addEventListener('mousemove', handleMouseMove, false)
canvas.value.addEventListener('mouseup', handleMouseUp, false)
}
// 鼠标移动
const handleMouseMove = (e) => {
}
// 鼠标抬起
const handleMouseUp = () => {
canvas.value.removeEventListener('mousemove', handleMouseMove, false)
canvas.value.removeEventListener('mouseup', handleMouseUp, false)
}
- 考虑画一个canvas画框
- 首先要确定画框的初始坐标,所以在鼠标按下的时候确定,定义全局变量 startX,startY
- 鼠标移动的时候,获取移动的距离,即可获取到画框的长度和宽度
- 鼠标抬起的时候,事件解绑
const handleMouseDown = (e) => {
startX = e.clientX
startY = e.clientY
canvas.value.addEventListener('mousemove', handleMouseMove, false)
canvas.value.addEventListener('mouseup', handleMouseUp, false)
}
const handleMouseMove = (e) => {
let rectWidth = e.clientX - startX
let rectHeight = e.clientY - startY
let ctx = canvas.value.getContext('2d')
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height)
ctx.fillStyle = '#000'
ctx.strokeRect(startX, startY, rectWidth, rectHeight)
}
注意点: 在每次移动绘画之前需要把之前的画框清除掉
- 画框对应的图片位置,开始绘制,考虑一下,大概就是鼠标抬起之后,就将画框区域内的图片区域绘画出来,然后再渲染到页面上面去就可以了
// 定义绘画函数
const draw = () => {
const canvas2 = document.createElement('canvas')
canvas2.width = 300
canvas2.height = 300
const ctx = canvas2.getContext('2d')
ctx.clearRect(0, 0, 300, 300)
ctx.drawImage(
img.value,
startX,
startY,
rectWidth,
rectHeight,
0,
0,
300,
300,
)
img2Url.value = canvas2.toDataURL()
}
drawImage方法就可以将一张图片绘画在canvas中
注意点 img.value是获取的一个img标签,或者可以通过自己new Image()也可以,这里我也是犯过错的,所以标注了一下,希望大家都能注意一下。
- 出现问题,怎么好像我绘制出来的图片和原图区别很大?啊哦。
- 找出原因,因为在截取的时候,我们对比的是图片展示的宽高比例,但是,实际上应该比较的是原始图片的宽高与画框的大小比例,即需要计算,图片原始宽高/画框宽高=大小比例,所以,我们需要知道图片原生宽高,naturalWidth,naturalHeight,以及得到的比例ratioX,ratioY
// 图片原生宽度
let naturalWidth = 0
// 图片原生高度
let naturalHeight = 0
// 图片缩放比例
let ratioX = 0
let ratioY = 0
naturalWidth = img.value?.naturalWidth
naturalHeight = img.value?.naturalHeight
ratioX = naturalWidth / img.value.width
ratioY = naturalHeight / img.value.height
起点坐标和画框长宽都需要乘上这个比例
最后一步展示截图区域
img2Url.value = canvas2.toDataURL()
展示完整代码
<template>
<div class="chat-canvas">
<canvas class="chat-canvas-box" ref="canvas"></canvas>
<img :src="imgUrl" alt="" ref="img" class="img" />
</div>
<div>
<input type="file" @change="handleFileChange" />
</div>
<div>
<img class="img2" :src="img2Url" alt="" />
</div>
</template>
<script setup lang="ts">
const imgUrl = ref('')
const canvas = ref(null)
const img = ref(null)
const img2Url = ref('')
onMounted(() => {
canvasInit()
})
// 图片原生宽度
let naturalWidth = 0
// 图片原生高度
let naturalHeight = 0
// 图片缩放比例
let ratioX = 0
let ratioY = 0
const canvasInit = () => {
img.value.onload = () => {
naturalWidth = img.value?.naturalWidth
naturalHeight = img.value?.naturalHeight
canvas.value.width = img.value.width
canvas.value.height = img.value.height
ratioX = naturalWidth / img.value.width
ratioY = naturalHeight / img.value.height
}
canvas.value.addEventListener('mousedown', handleMouseDown, false)
}
// 画框开始坐标
let startX = 0
let startY = 0
// 画框宽度,高度
let rectWidth = 0
let rectHeight = 0
// 鼠标按下
const handleMouseDown = (e) => {
startX = e.clientX
startY = e.clientY
canvas.value.addEventListener('mousemove', handleMouseMove, false)
canvas.value.addEventListener('mouseup', handleMouseUp, false)
}
// 鼠标移动
const handleMouseMove = (e) => {
rectWidth = e.clientX - startX
rectHeight = e.clientY - startY
let ctx = canvas.value.getContext('2d')
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height)
ctx.fillStyle = '#000'
ctx.strokeRect(startX, startY, rectWidth, rectHeight)
}
// 鼠标抬起
const handleMouseUp = () => {
canvas.value.removeEventListener('mousemove', handleMouseMove, false)
canvas.value.removeEventListener('mouseup', handleMouseUp, false)
draw()
}
// 绘画
const draw = () => {
const canvas2 = document.createElement('canvas')
canvas2.width = 300
canvas2.height = 300
const ctx = canvas2.getContext('2d')
ctx.clearRect(0, 0, 300, 300)
ctx.drawImage(
img.value,
startX * ratioX,
startY * ratioY,
rectWidth * ratioX,
rectHeight * ratioY,
0,
0,
300,
300,
)
img2Url.value = canvas2.toDataURL()
}
// 图片上传
const handleFileChange = (e) => {
const fileReader = new FileReader()
fileReader.readAsDataURL(e.target.files[0])
fileReader.onload = (e) => {
imgUrl.value = e.target.result
}
}
</script>
<style lang="less">
.chat-canvas {
position: relative;
}
.img {
width: 300px;
height: 450px;
}
.chat-canvas-box {
position: absolute;
top: 0 !important;
bottom: 0 !important;
cursor: crosshair;
// border: 1px solid red;
}
.img2 {
border-radius: 999px;
}
</style>
结语
看着别人实现总归觉得好简单,当自己一点点摸索出来之后,其实还是有很多小细节点可以探讨的,可能这只是在造轮子,但是,对我来说,也是一个独立思考的过程,至少是自己真正写过之后,才会有信心写这篇文章。加油,共勉!
转载自:https://juejin.cn/post/7397324806708805668