Bootstrap

基于Vue+Canvas实现图片的裁切

基于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'))
}

在这里插入图片描述

;