简历上给自己挖的坑,面试被拷打,早就该填了T.T
参考:【js canvas实现图片裁剪】 https://www.bilibili.com/video/BV1QK411d7n1/?share_source=copy_web&vd_source=bf743b20b76eab11028ba2fb05f056b4
效果
思路
组成:
上传文件区域
----------------------------------------------
原图canvas | 裁剪出的图片canvas | 下载图片按钮
上传文件区域
<input>
标签,type
为file
原图canvas
在上面有一个半透黑的裁剪区域,可以鼠标拖拽移动,实际上是在原图canvas上画了一个正方形,需要注意不能移出原图的区域。
使用ctx.getImageData()
可以得到指定区域的ImageData对象,是一个像素值的数组对象
裁剪出的图片canvas
使用clipCvx.putImageData(imageData)
可以传入刚刚裁剪得到的ImageData对象,绘制到目标位图中
下载图片按钮
clipCvs.toDataUrl()
获取canvas的转成图片的dataurl格式,用fetch()请求这个url资源,因为我们是在请求一个图片,为了解析正常,我们对响应执行 Body.blob 来设置相应的 MIME 类型。然后创建一个 Object URL,赋给<a download>
标签的href作为下载链接
源码
注释写得very详细了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas裁剪图片</title>
</head>
<body>
<div style="margin-bottom: 10px">
上传图片:<input type="file" onchange="onChange(this.files[0])">
</div>
<canvas id="cvs"></canvas>
<canvas id="clipCvs"></canvas>
<button id="download">下载图片</button>
</body>
<script>
const cvs = document.getElementById('cvs') // 用于显示原图的canvas
const clipCvs = document.getElementById('clipCvs') // 用于拖动的裁剪范围框框 canvas
const download = document.getElementById('download')
const ctx = cvs.getContext('2d')
const clipCtx = clipCvs.getContext('2d')
const img = new Image() // 创建image节点
let size = 150 // 正方形裁剪框框的边长
let maxW = 400 // 用于显示原图的canvs的最大宽度
const p = {
left: 0,
top: 0,
stepX: 0,
stepY: 0
}
/**
* 上传图片
*/
const onChange = (file) => {
onInit(URL.createObjectURL(file))
}
// 加载图片,并初始化裁剪功能
const onInit = (src) => {
clipCvs.width = clipCvs.height = size // 设置正方形的裁剪框框的宽、高
img.src = src
img.onload = () => {
let width = img.width
let height = img.height
// 在maxW的范围内设置的width和height
if (width > maxW) {
height = maxW / width * height
width = maxW
}
cvs.width = width
cvs.height = height
// 让clipcvs初始在图片最中间
render(width / 2 - size / 2, height / 2 - size / 2)
}
}
/**
* 渲染裁剪前canvas
* @param left clipcvs的left定位
* @param top clipcvs的top定位
*/
const render = (left = 0, top = 0) => {
ctx.clearRect(0, 0, cvs.width, cvs.height)
// 先把图片画在canvas里,左上角在目标画布上 X 轴坐标, 左上角在目标画布上 Y 轴坐标
ctx.drawImage(img, 0, 0, cvs.width, cvs.height)
// 为了之后画正方形裁剪框的时候 不超出图片范围
if (left < 0) {
left = 0
}
if (left > cvs.width - size) {
left = cvs.width - size
}
if (top < 0) {
top = 0
}
if (top > cvs.height - size) {
top = cvs.height - size
}
// getImageData -- 从(left,top)坐标,裁剪出size*size大小的图片, 返回值是一个ImageData类型的对象,是一个包含像素值的数组对象
// clipPic 裁剪出的image显示在原图右侧
clipPic(ctx.getImageData(left, top, size, size))
// 绘制裁剪框,即在原来从canvas上画一个半透明的正方形
ctx.beginPath()
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'
ctx.fillRect(left, top, size, size)
// p.left现在为裁剪框框的x轴偏移 ,p.top 为裁剪框框的y轴偏移
p.left = left
p.top = top
}
// 裁剪图片,并显示在右侧
const clipPic = (data) => {
clipCtx.clearRect(0, 0, clipCvs.width, clipCvs.height)
clipCtx.putImageData(data, 0, 0) // putImageData -- 传入一个IamgeData对象,绘制到位图中
}
let isMoving = false
/**
* 监听 可拖动裁剪框框的鼠标按下事件
*/
cvs.onmousedown = (e) => {
// e.pageX 鼠标指针相对于整个文档的 X 轴坐标
// e.pageY 鼠标指针相对于整个文档的 Y 轴坐标
p.stepX = e.pageX - p.left // 在x轴方向移动的距离
p.stepY = e.pageY - p.top // 在y轴方向移动的距离
isMoving = true
}
/**
* 监听 可拖动裁剪框框的鼠标松开事件
* 如果刚刚拖动了,则重新绘制canvas
*/
cvs.onmousemove = (e) => {
if (isMoving) {
render(e.pageX - p.stepX, e.pageY - p.stepY) // 其实算出来的是 p.left + e.pageX2 - e.pageX1, p.top + e.pageY2 - e.pageY1
}
}
/**
* 监听 可拖动裁剪框框的鼠标松开事件 把move状态置为false
*/
document.onmouseup = () => {
isMoving = false
}
/**
* 下载裁剪好的图像
*/
download.onclick = async () => {
// toDataURL 返回一个包含图片展示的 data URI。可以使用 type 参数指定其类型,默认为 PNG 格式。图片的分辨率为 96dpi
// data URI 的格式一般为:data:image/png;base64,iVBORw0KNdqsdZ05vRbvTcvXIyi2YxWpxtFA8a+9FJ/2mNFteBN1MP.....
const res = await fetch(clipCvs.toDataURL('image/png'))
// 可以使用 blob() 从 response 中读取一个Blob对象
const blob = await res.blob()
const a = document.createElement('a')
a.setAttribute('download', 'clip.png') // download属性让浏览器将URL视为下载资源,可以不填值(会自动赋值),值为文件名
a.href = URL.createObjectURL(blob) // 置为a标签的href
a.click()
}
/**
* 页面初始化 初始运行
*/
onInit('./images/boy.jpg')
</script>
</html>