裁剪头像的需求十分常见,主要目的是为了统一用户头像的尺寸,避免因为用户上传的图片尺寸大小不一致导致页面布局出现问题
高效实现需求的方法,就是避免重复造轮子,在这里推荐使用 cropper 实现头像裁剪功能 (原因是 cropper 功能强大、上手简单、文档详细)
cropper 的Gitee地址:vue-cropper
cropper Vue3在线示例:cropper Vue3在线示例

# npm 安装 npm install vue-cropper@next # yarn 安装 yarn add vue-cropper@next 哪个组件需要使用 Vue Cropper,就在哪个组件导入
import 'vue-cropper/dist/index.css' import { VueCropper }  from 'vue-cropper' main.js 文件
import VueCropper from 'vue-cropper' import 'vue-cropper/dist/index.css'  const app = createApp(App) app.use(VueCropper) app.mount('#app') 注意事项:
要为
组件设置宽和高,并用一个外层容器包裹组件
const option = ref({   autoCrop: true, // 是否默认生成截图框   autoCropHeight: '240px', // 默认生成截图框宽度(默认值:容器的 80%, 可选值:0 ~ max), 真正裁剪出来的图片的宽度为 autoCropHeight * 1.25   autoCropWidth: '240px', // 默认生成截图框宽度(默认值:容器的 80%, 可选值:0 ~ max), 真正裁剪出来的图片的宽度为 autoWidth * 1.25   canMove: true, // 上传图片是否可以移动   canScale: true, // 图片是否允许滚轮缩放   centerBox: true, // 截图框是否被限制在图片里面   fixed: true, // 是否固定截图框的宽高比例   fixedBox: true, // 是否固定截图框大小   fixedNumber: [1, 1], // 截图框的宽高比例([ 宽度 , 高度 ])   img: 'https://img2.baidu.com/it/u=2339635883,2403687892&fm=253&fmt=auto&app=138&f=JPEG', // 裁剪图片的地址(可选值:url 地址, base64, blob)   infoTrue: true, // infoTrue为 true 时显示预览图片的宽高信息,infoTrue为 false 时表示显示裁剪框的宽高信息   mode: 'contain', // 截图框可拖动时的方向(可选值:contain , cover, 100px, 100% auto)   origin: false, // 上传的图片是否按照原始比例渲染   outputSize: 1, // 裁剪生成图片的质量(可选值:0.1 ~ 1)   outputType: 'png', // 裁剪生成图片的格式(可选值:png, jpeg, webp) })  // 实时预览 const realTime = (data) => {   // console.log('realTime data =', data)   previews.value = data } window.URL.createObjectURL(blob)来生成 url ,从 Java 的角度来说,相当于重写了 Blob 类的 toString 方法
后端环境:
import cn.edu.scau.controller.vo.Result; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;  import java.io.File; import java.io.IOException; import java.util.Objects; import java.util.UUID;  @RestController @RequestMapping("/user") public class UserController {      @PostMapping("/updateAvatar")     public Resultimport java.io.Serializable;  /**  * 后端统一返回结果  *  * @param   */ public class Result implements Serializable {      private Integer code;     private String message;     private T data;      public static  Result success() {         Result result = new Result<>();         result.code = 200;         result.message = "success";         return result;     }      public static  Result success(T object) {         Result result = new Result<>();         result.data = object;         result.code = 200;         result.message = "success";         return result;     }      public static  Result fail(String message) {         Result result = new Result<>();         result.message = message;         result.code = 500;         return result;     }      public Integer getCode() {         return code;     }      public void setCode(Integer code) {         this.code = code;     }      public String getMessage() {         return message;     }      public void setMessage(String message) {         this.message = message;     }      public T getData() {         return data;     }      public void setData(T data) {         this.data = data;     }      @Override     public String toString() {         return "Result{" +                 "code=" + code +                 ", message='" + message + '\'' +                 ", data=" + data +                 '}';     }  }                                裁剪头像
                               上传图片                         放大(向上滚动鼠标滑轮)                                    缩小(向下滚动鼠标滑轮)                       向左旋转            向右旋转            下载预览图            确定修改                                             实时预览
                                          ![]() 
                                         import axios from 'axios'  const request = axios.create({   baseURL: '/api',   timeout: 60000,   headers: {     'Content-Type': 'application/json;charset=UTF-8'   } })  request.interceptors.request.use(  )  request.interceptors.response.use(response => {   if (response.data) {     return response.data   }   return response }, (error) => {   return Promise.reject(error) })  export default request import '@/assets/main.css'  import {createApp} from 'vue' import {createPinia} from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import zhCn from 'element-plus/es/locale/lang/zh-cn' import * as ElementPlusIconsVue from '@element-plus/icons-vue'  import App from './App.vue' import router from './router' import 'default-passive-events'  const app = createApp(App)  app.use(createPinia()) app.use(ElementPlus, {locale: zhCn}) for (const [key, component] of Object.entries(ElementPlusIconsVue)) {   app.component(key, component) } app.use(router)  app.mount('#app') import {fileURLToPath, URL} from 'node:url'  import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue'  // https://vitejs.dev/config/ export default defineConfig({   plugins: [     vue()   ],   resolve: {     alias: {       '@': fileURLToPath(new URL('./src', import.meta.url))     }   },   server: {     proxy: {       '/api': {         target: 'http://localhost:8001',         changeOrigin: true,         rewrite: (path) => {           return path.replace('/api', '')         }       }     }   } }) 前端:cropper-avatar-frontend
后端:cropper-avatar-backend