HTML
<div>
<input type="file">
<button class="confirm">放入选中截图</button>
<button class="download">下载选中截图</button>
</div>
<canvas id="cvs"></canvas>
<div class="shot-img-area">
</div>
具体实现
首先初始化参数
const inp = document.querySelector('input'),
shotImgArea = document.querySelector('.shot-img-area'), // 放入截图区域
confirm = document.querySelector('.confirm'),
download = document.querySelector('.download'),
cvs = document.getElementById('cvs'),
imgShot = new imgShotCvs(cvs)
// 配置
const MASK_OPACITY = .7
接下来实现这个ImgShowCvs类
首先初始化参数
class imgShotCvs {
constructor(cvs) {
this.cvs = cvs
this.ctx = cvs.getContext('2d', { willReadFrequently: true })
this.initState()
this.bindEvent()
}
initState() {
this.stPos = []
this.endPos = []
// 拖动截图区域大小
this.shotWidth = 0
this.shotHeight = 0
// 填充图片高度 也是`canvas`高度
this.width = 0
this.height = 0
// 填充的图片
this.img = null
// 截图区域的图片数据
this.shotImgData = []
// 偏移值
const { left, top } = this.cvs.getBoundingClientRect()
this.left = left
this.top = top
}
}
接下来绑定点击事件
注意,在类里绑定事件,this指向是DOM元素,所以用箭头函数定义,做法同React的类组件
bindEvent() {
this.cvs.addEventListener('mousedown', this.onMouseDown)
}
onMouseDown = (e) => {
this.stPos = [e.pageX - this.left, e.pageY - this.top]
this.cvs.addEventListener('mousemove', this.onMouseMove)
this.cvs.addEventListener('mouseup', this.onMouseUp)
}
在鼠标点击时,记录起点,一定要减去偏移值
然后绑定移动和抬起事件
setImg(img) {
this.img = img
this.ctx.drawImage(img, 0, 0)
}
onMouseMove = (e) => {
this.endPos = [e.pageX - this.left, e.pageY - this.top]
const [stX, stY] = this.stPos,
[endX, endY] = this.endPos
// 记录 `终点 - 起点` 得到宽高
this.shotWidth = endX - stX
this.shotHeight = endY - stY
this.clear()
this.drawMask()
this.drawSreenShot()
}
clear() {
this.ctx.clearRect(0, 0, this.width, this.height)
}
drawMask() {
this.ctx.fillStyle = `rgba(0, 0, 0, ${MASK_OPACITY})`
this.ctx.fillRect(0, 0, this.width, this.height)
}
drawSreenShot() {
// 擦除区域模式
this.ctx.globalCompositeOperation = 'destination-out'
// 从鼠标点击起点开始画
this.ctx.fillRect(...this.stPos, this.shotWidth, this.shotHeight)
// 往擦除区域填充
this.ctx.globalCompositeOperation = 'destination-over'
this.setImg(this.img)
}
每次作画都要清除重绘,canvas是有GPU加速的,这点数据频繁清除无伤大雅
然后画个遮罩
清除画板后,使用destination-out模式,填充你选中的区域
再用destination-over模式,放入数据,这样就会把你选中的区域,变成图片,没有选中的区域,则是遮罩的黑色
onMouseUp = () => {
this.setShotImgData()
this.cvs.removeEventListener('mousemove', this.onMouseMove)
this.cvs.removeEventListener('mouseup', this.onMouseUp)
}
setShotImgData() {
if (this.shotWidth === 0 || this.shotHeight === 0) {
return
}
this.shotImgData = this.ctx.getImageData(...this.stPos, this.shotWidth, this.shotHeight)
}
鼠标抬起时,解绑事件
然后把截图区域的图片,保存他的ImgData,里面是ArrayBuffer,每四位代表rgba
然后提供一个方法,用来获取新区域的canvas
getShotImg() {
const cvs = document.createElement('canvas'),
ctx = cvs.getContext('2d')
cvs.width = this.shotWidth
cvs.height = this.shotHeight
ctx.putImageData(this.shotImgData, 0, 0)
return cvs
}
这个类就实现了,接下来就是简单的处理input选择文件了
下载的话,只需要获取canvas的Blob数据,转成url即可
function bindEvent() {
inp.onchange = async function () {
const src = await getBase64(this.files[0]),
img = await getImg(src),
{ width, height } = img
imgShot.setSize(width, height)
imgShot.setImg(img)
imgShot.drawMask()
}
confirm.onclick = function () {
shotImgArea.querySelector('canvas')?.remove()
const cvs = imgShot.getShotImg()
shotImgArea.appendChild(cvs)
}
download.onclick = function () {
const cvs = imgShot.getShotImg()
cvs.toBlob(blob => {
const a = document.createElement('a'),
url = URL.createObjectURL(blob)
a.href = url
a.download = Date.now()
a.click()
})
}
}
function getBase64(blob) {
const fr = new FileReader()
fr.readAsDataURL(blob)
return new Promise((resolve) => {
fr.onload = function () {
resolve(this.result)
}
})
}
function getImg(src) {
const img = new Image()
img.src = src
return new Promise((resolve) => {
img.onload = function () {
resolve(img)
}
})
}