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

在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 这个问题可能是一个编码问题,但是我理解为:如何基于Spring Boot实现Vue+SSM的分页。 实现Vue+SSM的分页,可以按照以下步骤进行: 1. 配置MyBatis和PageHelper,用于MySQL数据库的分页查询; 2. 在Controller中编写接口,用于处理Vue前端请求的数据,并调用Service中的方法实现分页查询; 3. 在Vue前端中,使用Element UI的组件实现分页效果,并绑定相关事件,用于向后端发送分页请求,获取数据并渲染页面; 4. 在Vue前端中,使用Axios库实现向后端发送请求; 5. 在Vue前端中,使用Vue Router实现页面的前端路由跳转。 以上是基于Spring Boot实现Vue+SSM的分页的一个简单实现思路,具体的实现细节需要根据具体的项目需求进行进一步的开发和调试。 ### 回答2: Spring Boot是一种基于Java的开发框架,它可以很方便地构建独立的、生产级别的应用程序。而Vue.js是一个用于构建Web界面的渐进式JavaScript框架,可用于构建单页应用程序(SPA)和移动应用程序。SSM框架由Spring、SpringMVC和MyBatis组成,可以用于开发Java Web应用程序。整合这些技术,可以实现一个分页的Web应用程序。 首先,在Spring Boot项目中,我们可以使用Spring Boot的starter来自动配置SpringMVC和MyBatis,使得开发过程更加简单。使用Vue.js可以实现前端页面的交互,通过Vue.js的组件化开发方式,可以将页面拆分为多个组件,提高代码的可读性和维护性。 在后端,我们可以使用SSM框架来实现数据的持久化、业务逻辑的处理和API的提供。使用MyBatis可以方便地进行数据库的访问,而Spring框架可以提供全面的依赖注入和AOP特性。SpringMVC可以帮助我们处理API请求,将数据传递给前端页面。 在分页方面,我们可以通过MyBatis提供的分页插件来实现分页功能。在API接口中,我们可以通过接收前端传递的页码和每页数据条数,使用MyBatis进行分页查询,将结果返回给Vue.js。在前端页面中,通过Vue.js进行分页组件的渲染和交互。 总之,Spring Boot项目基于Vue.js和SSM框架的分页实现可以提供一个良好的用户体验和高效的数据处理能力。此外,由于这些技术的流行性和稳定性,开发成本也比较低。 ### 回答3: SpringBoot项目基于Vue和SSM框架实现分页功能需要以下几个步骤: 1. 添加Vue.js和axios库到前端项目中,用于向后端请求数据和进行分页操作。 2. 在后端SSM项目中,设计一个接口用于处理分页请求,该接口应该包含当前页码、每页显示的记录数等参数,返回查询结果和总记录数。 3. 在后端Controller中实现该接口,并调用Service层中的查询方法获取分页数据,将结果封装成一个PageInfo对象,该对象包含当前页码、每页显示的记录数、总页数和查询结果。 4. 在前端Vue实例中,定义一个data对象用于存放分页相关数据,包括当前页码、每页显示的记录数、总页数和查询结果。 5. 在前端Vue实例中,定义一个方法用于向后端Controller发送分页请求,并将返回的分页结果保存到data对象中。 6. 在前端Vue模板中,使用v-for指令将分页结果渲染到页面上,并使用v-pagination组件实现分页导航。 7. 在前端Vue实例中,实现用户与分页导航的交互操作,例如点击页码、改变每页显示的记录数等操作。 通过以上步骤,就可以基于Vue和SSM框架实现一个高效的分页功能。使用Vue作为前端展示的框架,相比传统的JSP或Thymeleaf等技术,可以更加灵活和高效地处理页面渲染和交互操作;而SSM框架则提供了一个可靠的后端架构,可以保证数据的完整性和安全性。综合起来,基于Vue和SSM实现分页功能,不仅可以提高开发效率和用户体验,还可以提高系统的稳定性和可维护性。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值