基于Vue+Canvas实现图片的裁切
1.查看package.json的依赖,less和less-loader。
2.main.js就是默认生成的。
3.App.vue
<template>
<div id="app">
<clip-image @saveImage="saveImage" v-if="!isSave"></clip-image>
<div class="resultBox" @click="change" v-else>
<img :src="data" alt />
</div>
</div>
</template>
<script>
/* IMPORT CSS */
import "./assets/reset.min.css";
import "./assets/basic.less";
/* IMPORT COMPONENT */
import ClipImage from "./components/ClipImage.vue";
export default {
data() {
return {
isSave: false,
data: ""
};
},
components: {
ClipImage
},
methods: {
saveImage(payload) {
this.isSave = true;
this.data = payload;
},
change() {
this.isSave = false;
}
}
};
</script>
<style lang="less">
.resultBox {
padding: 0.2rem;
}
.resultBox img {
display: block;
box-sizing: border-box;
width: 7.1rem;
height: 7.1rem;
border: 0.02rem solid #ddd;
}
</style>
3.重点说下组件cliplmage.vue组件,下面这行代码的意思是,在项目当中不管是移动端还是PC端,如果想让用户从客户端从本地能够选择一些图片进行上传,需要用到一个type为file的input表单元素,只允许图片格式。file这个样式类默认是隐藏的。
<input type="file" accept="image/*" class="file" ref="file" @change="changeFunc" />
4.操作步骤
①我们需要准备一些数据:画布的大小、MARK遮罩层的大小和位置、上传图片的大小和位置、是否显示MARK…
②上传图片:选中图片,把其绘制到画布中
③实现图片的拖拽Touch:重新绘制图片所在的位置
④实现如图的方法和缩小:重新绘制图片的大小
⑤保存图片的时候:把遮罩层选中的部分(像素)最后生成一张新的图片
5.代码习惯养成
做什么事情之前先不要写代码,先用汉字写提纲大步骤,防止思路出错就乱,如果写不出来一定是基础知识不扎实,如果没有思路就是项目实战少。这两方面都应该提高。
6.实现步骤
封装子组件
①第一步:我们需要准备一些数据:画布的大小、MARK遮罩层的大小和位置、上传图片的大小和位置、是否显示MARK…
<div class="canvasBox">
<canvas class="canvasBox" ></canvas>
<canvas></canvas>
<div class="mark"></div>
</div>
<div class="buttonBox">
<input type="file" accept="image/*" class="file" />
<button >选择图片</button>
<button >放大</button>
<button>缩小</button>
<button >保存图片</button>
</div>
</div>
</template>
<script>
export default {
data(){
let winW=document.documentElement.clientWidth,
font=parseFloat(document.documentElement.style.fontSize),
canvasW=winW-0.4*font;
markW=canvasW*0.7;//是整个canvas大小的百分之七十
return{
//画布大小
CW:canvasW, //canvas画布大小
CH:canvasW, //正方形
//MARK大小和位置
MW:markW,
MH:markW,
ML:(canvasW-markW)/2,
MT:(canvasW-markW)/2,
//上传图片的大小和位置
IW:0, //imagewidth
IH:0,
IL:0,
IT:0,
//是否显示MARK
ISMARK:false
};
}
【注意】
<canvas :width="CW" :height="CH" ref="canvas"></canvas>
- canvas宽高需要用这种方式来指定,不是style。而是单独的width和height属性
- ref指的是非受data组件,受数据影响的组件或视图叫受控组件,ref可以称之为非受控组件。 ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例
[注意]:只要想要在Vue中直接操作DOM元素,就必须用ref属性进行注册
<div class="canvasBox">
<canvas class="canvasBox" ></canvas>
<canvas :width="CW" :height="CH" ref=""></canvas> <!--canvas宽高需要用这种方式来指定,不是style而是单独的width和height属性,ref可以称为非受控组件-->
<div
class="mark"
v-show="ISMARK"
:style="{width:MW + 'px',height:MH+'px',left:ML+'px',top:MT+'px'}"
></div>
</div>
<div class="buttonBox">
<input type="file" accept="image/*" class="file" ref="file"
/>
<button >选择图片</button>
<button >放大</button>
<button>缩小</button>
<button >保存图片</button>
</div>
</div>
</template>
<script>
export default {
data(){
let winW=document.documentElement.clientWidth,
font=parseFloat(document.documentElement.style.fontSize),
canvasW=winW-0.4*font;
markW=canvasW*0.7;//是整个canvas大小的百分之七十
return{
//画布大小
CW:canvasW, //canvas画布大小
CH:canvasW, //正方形
//MARK大小和位置
MW:markW,
MH:markW,
ML:(canvasW-markW)/2,
MT:(canvasW-markW)/2,
//上传图片的大小和位置
IW:0, //imagewidth
IH:0,
IL:0,
IT:0,
//是否显示MARK
ISMARK:false
};
}
②第二步:上传图片:选中图片,把其绘制到画布中
- 当我们点击按钮的时候触发了点击事件的
clickFunc
事件,当选择图片的时候,事件改变触发了change类型的changeFunc
事件,打印changeFunc
方法中的this.$refs.file
,并进入,查看结果,从浏览器控制台中看到files是一个集合,通过files[0]便可拿到当前文件。
<template>
....
<div class="buttonBox">
<input type="file" accept="image/*" class="file" ref="file" @change="changeFunc" />
<button @click="clickFunc">选择图片</button>
<button>放大</button>
<button>缩小</button>
<button>保存图片</button>
</div>
</tempalte>
<script>
methods:{
clickFunc(){
//触发FILE选择文件的操作
this.$refs.file.click();
},
changeFunc(){
//选择了新的图片
this.ISMARK = true;//遮罩层显示
console.log(this.$refs.file)//<input data-v-f63c9b2a="" type="file" accept="image/*" class="file">
console.dir(this.$refs.file)//input.file
}
}
</script>
- 将选择图片转换为base64格式
changeFunc(){
//选择了新的图片
this.ISMARK = true;//遮罩层显示
//console.log(this.$refs.file)//<input data-v-f63c9b2a="" type="file" accept="image/*" class="file">
//console.dir(this.$refs.file)//input.file
let file = this.$refs.file.files[0];
if(!file) return;
//先基于FileReader进行文件的读取=>创建实例=>转换为Base64
let fileExample= new FileReader();//创建实例
fileExample.readAsDataURL(file);//转换为Base64
fileExample.onload = ev =>{
console.log(ev)
}
}
- 显示上传图片
methods:{
clickFunc(){
//触发FILE选择文件的操作
this.$refs.file.click();
},
changeFunc(){
//选择了新的图片
this.ISMARK = true;//遮罩层显示
//console.log(this.$refs.file)//<input data-v-f63c9b2a="" type="file" accept="image/*" class="file">
//console.dir(this.$refs.file)//input.file
let file = this.$refs.file.files[0];
if(!file) return;
//先基于FileReader进行文件的读取=>创建实例=>转换为Base64
let fileExample= new FileReader();//创建实例
fileExample.readAsDataURL(file);//转换为Base64
fileExample.onload=ev=>{
//创建新图片
this.IMAGE = new Image();
this.IMAGE.src =ev.target.result;
this.IMAGE.onload = _ =>{
this.IW = this.IMAGE.width;//获取宽高
this.IH = this.IMAGE.height;
this.IL=(this.CW-this.IW)/2;//画布的宽-图片的宽除以2
this.IT=(this.CH-this.IH)/2;
//绘制图片
this.drawImage();
}
// console.log(ev)
}
},
//由于绘制图片的操作多次用到,所以提成公共方法
drawImage(){
//创建一个2d渲染画布上下文,
this.CTX = this.$refs.canvas.getContext('2d');
//清空画布
this.CTX.clearRect(0,0,this.CW,this.CH);
//绘制图片
this.CTX.drawImage(this.IMAGE,this.IL,this.IT,this.IW,this.IH);
}
}
- 图片按比例缩放全显示
changeFunc(){
//选择了新的图片
this.ISMARK = true;//遮罩层显示
//console.log(this.$refs.file)//<input data-v-f63c9b2a="" type="file" accept="image/*" class="file">
//console.dir(this.$refs.file)//input.file
let file = this.$refs.file.files[0];
if(!file) return;
//先基于FileReader进行文件的读取=>创建实例=>转换为Base64
let fileExample= new FileReader();//创建实例
fileExample.readAsDataURL(file);//转换为Base64
fileExample.onload=ev=>{
//创建新图片
this.IMAGE = new Image();
this.IMAGE.src =ev.target.result;
this.IMAGE.onload = _ =>{
this.IW = this.IMAGE.width;//获取宽高
this.IH = this.IMAGE.height;
//重新按照比例计算宽高
+ let n = 1;
+ if(this.IW>this.IH){
+ n = this.IW/this.CW;
+ this.IW = this.CW;
+ this.IH = this.IH / n;
+ }else{
+ n = this.IH / this.CH;
+ this.IH = this.CH;
+ this.IW = this.IW / n;
}
this.IL=(this.CW-this.IW)/2;//画布的宽-图片的宽除以2
this.IT=(this.CH-this.IH)/2;
//绘制图片
this.drawImage();
}
// console.log(ev)
}
- 放大缩小功能实现
<template>
....
<button @click="scaleFunc(1)">放大</button>
<button @click="scaleFunc(0)">缩小</button>
....
</template>
<script>
methods:{
scaleFunc(flag) {
if (!this.IMAGE) return;
//为防止变形,按照图片比例进行缩放
let n = this.IW / this.IH,
n1=20,
n2=n1/n;
if (flag) {
this.IW += n1;
this.IH += n2;
} else {
this.IW -= n1;
this.IH -= n2;
}
//重新绘制图片
this.drawImage();
}
}
</script>
- 查看移动端事件源
<template>
<div class="canvasBox" @touchstart="startFunc" @touchmove="moveFunc">
</template>
<script>
startFunc(ev){
//按下的时候获取手指坐标
console.log(ev)
},
moveFunc(ev){
},
</script>
- 图片拖拽功能
startFunc(ev){
//按下的时候获取手指坐标
console.log(ev)
let point = ev.changedTouches[0];
this.startX=point.clientX;
this.startY=point.clientY;
},
moveFunc(ev){
let point = ev.changedTouches[0];
this.changeX=point.clientX-this.startX;
this.changeY=point.clientY-this.startY;
//为了防止移动端事件的误操作
if(Math.abs(this.changeX)>10||Math.abs(this.changeY)>10){
this.IL+=this.changeX;
this.IT+=this.changeY;
this.drawImage();
//下一次再滑动的结果,让当前最新的值减去上一次才是罪行偏移值。
//把当前结束后的结果作为下一次要结束的上一次结果。
this.startX=point.clientX;
this.startY=point.clientY;
}
}
- 把保存的图片转化为base64
saveFunc(){
if(!this.IMAGE) return;
//获取指定区域的像素信息
let imageData = this.CTX.getImageData(this.ML,this.MT,this.MW,this.MH);
//创建新的画布
let canvas2 = document.createElement("canvas"),
canvas2CTX = canvas2.getContext('2d');
canvas2.width=this.MW;
canvas2.height = this.MW;
//把像素信息放置到画布中
canvas2CTX.putImageData(imageData,0,0,0,0,this.MW,this.MH);
//把画布的重点内容生成图片的base64编码
canvas2.toDataURL('image/png');
console.log( canvas2.toDataURL('image/png'))
},
7.在父组件中通过父子间通信实现保存图片,子组件只是实现了裁切,截切后的结果由父组件中自定义的方法来定,把父组件的方法通过自定义事件传给子组件.
/*App.vue*/
<template>
<div id="app">
<clip-image @saveImage="saveImage" v-if="!isSave"></clip-image>
<div class="resultBox" @click="change" v-else>
<img :src="data" alt />
</div>
</div>
</template>
<script>
/* IMPORT CSS */
import "./assets/reset.min.css";
import "./assets/basic.less";
/* IMPORT COMPONENT */
import ClipImage from "./components/ClipImage.vue";
export default {
data() {
return {
isSave: false,
data: ""
};
},
components: {
ClipImage
},
methods: {
saveImage(payload) {
this.isSave = true;
this.data = payload;
},
change() {
this.isSave = false;
}
}
};
</script>
clipImage.vue
saveFunc(){
+ this.$emit("saveImage",canvas2.toDataURL('image/png'))
}