裁剪头像的需求十分常见,主要目的是为了统一用户头像的尺寸,避免因为用户上传的图片尺寸大小不一致导致页面布局出现问题
高效实现需求的方法,就是避免重复造轮子,在这里推荐使用 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 Result import 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