你们好,我是六六。在学习裁剪功能的过程当中,发现有不少文章讲的不是那么清晰易懂,让六六绕了不少弯路,因此今天呢,为了让你们再也不绕弯,六六要详详细细的手把手教你们写一个裁剪功能出来。web
裁剪功能的核心之中固然是canvas这个技术,若是不懂的能够去mdn上过一遍这个,不用看的太深,了解它是干啥的,怎么干就好了。以个人大白话说,就是在坐标系内可以操做每一个像素点的。接下来,咱们先来了解一下关于裁剪核心的api和相关知识:算法
用法:绘制一张图片到canvas元素里面
image:image对象或者是canvas对象
x,y:为坐标的起始点
实例:
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.src = 'images/backdrop.png'; // 图片地址
img.onload = function(){
ctx.drawImage(img,0,0); // 拿到image对象,画入canvas上
}
}
其实把,这个api有九个参数,借助官网的图:
复制代码
用法:返回一个imageData对象,包含一个data像素数组,一个width为宽,一个height为高的
复制代码
关于data数据(以个人大白话理解来讲,专业说法在上面),其实就是描述每一个像素点的信息
(数据很是大),咱们都知道rgba吧,前三个数字表明像素点的颜色,第四个数字表明透明度,
因此data数据呢,就是整个选取部分的像素点信息的集合。每一个像素点有四个值,
依次push到data数据中。
复制代码
如上图所示,咱们就知道第一个像素点的颜色就是rgba(114,112,113,255/255),那么他的位置
就是位于坐标(0,0)。假如选取的canvas大小为500\*500的,
那么这个data数组的大小就是500\*500\*4。
复制代码
用法:在场景中写入像素数据
myImageData:就是imageData对象
dx,dy:就是场景的坐标起点
复制代码
用法:方法创造Blob对象,用以展现canvas上的图片;这个图片文件能够被缓存或保存到本地
callback:回调函数,可得到一个单独的Blob对象参数
type:DOMString类型,指定图片格式,默认格式为image/png。
encoderOptions :Number类型,值在0与1之间,当请求图片格式为image/jpeg
或者image/webp时用来指定图片展现质量。若是这个参数的值不在指定类型与范围以内,
则使用默认值,其他参数将被忽略。
复制代码
首先咱们知道裁剪功能是须要运动,那么确定会用到动画。
h5提供了咱们一个终极动画解决的函数,就是requestanimationframe.
复制代码
思路很容易懂,接下来咱们就来一步一步实现。上面的每一个api必须熟练掌握,不熟悉的回头再看一看。下面就是代码加gif图片演示,基本上每句代码都是有备注的。canvas
思路:
首先经过input元素咱们能够获取到img对象
,在图片加载出来后就能够画入画图中,
并能够循环动画。
复制代码
<template>
<div id="app">
<canvas width='500' height='500' id='canvas'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
</div>
</template>
<script>
export default{
data(){
return{
ctx:'',
img:''
}
},
mounted(){
// 获取canvas对象
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 讲ctx存入data中
this.ctx=ctx
},
methods:{
// 读取图片
handleChange (e) {
const that = this
// 建立一个文件读取流
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
// 文件加载完成后能够读入
reader.onload = function () {
// this.reslut 为图片信息,就开始调用drawImg方法将图片写入canvas中
that.drawImg(this.result)
}
},
// 建立一个图片对象 画到画布上
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
// 循环动画
window.requestAnimationFrame(that.animate)
}
},
animate(){
// 清除画布,在下一次循环会画入从新
this.ctx.clearRect(0,0,500,500)
// 画照片
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
// 循环动画
window.requestAnimationFrame(this.animate)
}
}
}
</script>
<style scoped>
canvas{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
</style>
复制代码
思路:借助三个事件,鼠标按下,移动,抬起。在移动事件
里面经过offsetX和offsetY获取鼠标在canvas内的坐标
。而后建立一个函数根据鼠标的坐标用来画出这个裁剪框,
放入animate函数里面循环。
复制代码
演示: 后端
<template>
<div id="app">
<canvas width='500' height='500' id='canvas'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
</div>
</template>
<script>
/* eslint-disable */
export default{
data(){
return{
ctx:'',
img:'',
rectLeft:150,
rectTop:150,
rectWidth:200,
rectHeight:200
}
},
mounted(){
const that=this
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
this.ctx=ctx
// 新增----绑定点击事件,根据鼠标移动坐标画出裁剪框
this.down=canvas.addEventListener('mousedown',()=>{
canvas.onmousemove=function(e){
let x=e.offsetX
let y=e.offsetY
x <= 100 ? that.rectLeft=0: that.rectLeft = x - 100
y <= 100 ? that.rectTop =0: that.rectTop = y - 100
x >= 400 ? that.rectLeft = 300 : that.rectLeft = x - 100
y >= 400 ? that.rectTop = 300 : that.rectTop = y - 100
if(x<=100){
that.rectLeft=0
}
if(y<=100){
that.rectTop=0
}
}
})
canvas.addEventListener('mouseup',()=>{
console.log('抬起了')
canvas.onmousemove=null
})
},
methods:{
handleChange (e) {
const that = this
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
reader.onload = function () {
that.drawImg(this.result)
}
},
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
window.requestAnimationFrame(that.animate)
}
},
animate(){
this.ctx.clearRect(0,0,500,500)
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
// 新增---画裁剪框
this.drawRect()
window.requestAnimationFrame(this.animate)
},
// 新增----画出裁剪框
drawRect(){
this.ctx.beginPath()
this.ctx.lineWidth = 2
this.ctx.strokeStyle = 'rgba(0,0,0,0.6)'
this.ctx.strokeRect(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
}
}
}
</script>
<style scoped>
canvas{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
input{
position: absolute;
top:10%;
left:50%;
transform: translate(-50%,-50%);
}
</style>
复制代码
为了更好的提升用户体验感,不得不得进行视觉优化:
思路:在每次循环动画的开始,咱们能够先获取裁剪框
内的imageData,就是获取照片原色彩,
以后经过算法使用putImageData方法将整个canvas对象色彩变成灰色,
而后再把以前获取
裁剪框内的彩色在使用putImageData绘制上去便可。
复制代码
<template>
<div id="app">
<canvas width='500' height='500' id='canvas'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
</div>
</template>
<script>
/* eslint-disable */
export default{
data(){
return{
ctx:'',
img:'',
rectLeft:150,
rectTop:150,
rectWidth:200,
rectHeight:200,
chooseRgb:[]
}
},
mounted(){
const that=this
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
this.ctx=ctx
this.down=canvas.addEventListener('mousedown',()=>{
canvas.onmousemove=function(e){
let x=e.offsetX
let y=e.offsetY
x <= 100 ? that.rectLeft=0: that.rectLeft = x - 100
y <= 100 ? that.rectTop =0: that.rectTop = y - 100
x >= 400 ? that.rectLeft = 300 : that.rectLeft = x - 100
y >= 400 ? that.rectTop = 300 : that.rectTop = y - 100
if(x<=100){
that.rectLeft=0
}
if(y<=100){
that.rectTop=0
}
}
})
canvas.addEventListener('mouseup',()=>{
console.log('抬起了')
canvas.onmousemove=null
})
},
methods:{
handleChange (e) {
const that = this
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
reader.onload = function () {
that.drawImg(this.result)
}
},
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
window.requestAnimationFrame(that.animate)
}
},
animate(){
this.ctx.clearRect(0,0,500,500)
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
this.drawChoose()
this.drawHui()
this.drawRect()
window.requestAnimationFrame(this.animate)
},
drawRect(){
this.ctx.beginPath()
this.ctx.lineWidth = 2
this.ctx.strokeStyle = 'rgba(0,0,0,0.6)'
this.ctx.strokeRect(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
},
// 新增----获取裁剪框的色彩色彩
drawChoose () {
const data = this.ctx.getImageData(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
this.chooseRgb = data
},
//新增---- 所有图片变灰色而且画上彩色的
drawHui () {
const data = this.ctx.getImageData(0, 0, 500, 500)
for (let i = 0; i < data.data.length; i += 4) {
const grey = (data.data[i] + data.data[i + 1] + data.data[i + 2]) / 3
data.data[i] = data.data[i + 1] = data.data[i + 2] = grey
}
// 将变成灰色的像素绘制上去
this.ctx.putImageData(data, 0, 0)
// 将彩色的裁剪框绘制上去
this.ctx.putImageData(this.chooseRgb, this.rectLeft, this.rectTop)
},
}
}
</script>
<style scoped>
canvas{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
input{
position: absolute;
top:10%;
left:50%;
transform: translate(-50%,-50%);
}
</style>
复制代码
思路:须要建立一个新的canvas对象
,随后调用putImageData将裁剪的像素对象画上去便可
复制代码
<template>
<div id="app">
<canvas width='500' height='500' class='canvas1'></canvas>
<!-- 新增的canvas元素-->
<canvas width='200' height='200' class='canvas2'></canvas>
<input type="file"
@change="handleChange"
multiple='true'
accept='image/*'
>
<button @click='caijian'>点击裁剪</button>
</div>
</template>
<script>
/* eslint-disable */
export default{
data(){
return{
ctx:'',
img:'',
rectLeft:150,
rectTop:150,
rectWidth:200,
rectHeight:200,
chooseRgb:[]
}
},
mounted(){
const that=this
const canvas = document.querySelector('.canvas1')
const ctx = canvas.getContext('2d')
this.ctx=ctx
this.down=canvas.addEventListener('mousedown',()=>{
canvas.onmousemove=function(e){
let x=e.offsetX
let y=e.offsetY
x <= 100 ? that.rectLeft=0: that.rectLeft = x - 100
y <= 100 ? that.rectTop =0: that.rectTop = y - 100
x >= 400 ? that.rectLeft = 300 : that.rectLeft = x - 100
y >= 400 ? that.rectTop = 300 : that.rectTop = y - 100
if(x<=100){
that.rectLeft=0
}
if(y<=100){
that.rectTop=0
}
}
})
canvas.addEventListener('mouseup',()=>{
console.log('抬起了')
canvas.onmousemove=null
})
},
methods:{
handleChange (e) {
const that = this
const reader = new FileReader()
reader.readAsDataURL(e.target.files[0])
reader.onload = function () {
that.drawImg(this.result)
}
},
drawImg (imgData) {
const that = this
const img = new Image()
img.src = imgData
this.img = img
img.onload = function () {
window.requestAnimationFrame(that.animate)
}
},
animate(){
this.ctx.clearRect(0,0,500,500)
this.ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, 0, 0, 500, 500)
this.drawChoose()
this.drawHui()
this.drawRect()
window.requestAnimationFrame(this.animate)
},
drawRect(){
this.ctx.beginPath()
this.ctx.lineWidth = 2
this.ctx.strokeStyle = 'rgba(0,0,0,0.6)'
this.ctx.strokeRect(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
},
drawChoose () {
const data = this.ctx.getImageData(this.rectLeft, this.rectTop, this.rectWidth, this.rectHeight)
this.chooseRgb = data
},
drawHui () {
const data = this.ctx.getImageData(0, 0, 500, 500)
for (let i = 0; i < data.data.length; i += 4) {
const grey = (data.data[i] + data.data[i + 1] + data.data[i + 2]) / 3
data.data[i] = data.data[i + 1] = data.data[i + 2] = grey
}
this.ctx.putImageData(data, 0, 0)
this.ctx.putImageData(this.chooseRgb, this.rectLeft, this.rectTop)
},
// 新增一个canvas元素 用来存储裁剪的部分,以及上传时须要建立这个元素。
caijian(){
const canvas=document.querySelector('.canvas2')
const ctx = canvas.getContext("2d")
canvas.width = 200
canvas.height = 200
ctx.putImageData(this.chooseRgb, 0, 0)
}
}
}
</script>
<style scoped>
.canvas1{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
border:1px solid red
}
.canvas2{
position: absolute;
top:50%;
left:75%;
transform: translate(-50%,-50%);
border:1px solid red
}
input{
position: absolute;
top:10%;
left:50%;
transform: translate(-50%,-50%);
}
button{
position: absolute;
top:10%;
left:70%;
transform: translate(-50%,-50%);
}
</style>
复制代码
思路:就是经过canvas.toBlob这个api实现的,
下面代码是我以前上传到服务器的时候写的,因为如今木有服务器,
就不能演示了。
复制代码
// 须要接受canvas元素对象,上一部分建立的
uploadImg (canvas) {
// 异步获取blob对象
canvas.toBlob(async (blob) => {
// 实例化一个FormData
let fd = new FormData()
fd.append('file', blob)
fd.name = new Date().getTime()
// 传送数据
const result = await this.$http({
method: 'post',
url: 'api/users/upload',
headers: {
'Content-Type': 'multipart/form-data'
},
data: fd
})
if (result.data.success) {
this.isShow = false
this.$bus.$emit('on1', result.data.url)
this.$alert.success('更换头像成功', 1000)
}
}, 'image/png')
}
}
复制代码
整个裁剪过程已经讲完了,上传那一块仍是有疑惑的同窗,能够参考个人这篇文章api
Vue先后端开发仿蘑菇街商城项目 这里面有上传头像的先后端代码,你们能够去参考一下。仍是有疑惑的能够给我留言哈。数组
这个月末以前,我争取开发一下裁剪的插件,共你们使用,毕竟造轮子仍是颇有趣的。但愿你们都能来捧场~缓存