实现图片上传预览和裁切功能

一、纯客户端实现上传图片预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>客户端图片上传预览示例</title>
<style>
.img-wrap {
width: 200px;
height: 200px;
border: 1px solid #ccc;
}

img {
max-width: 100%;
}
</style>
</head>
<body>
<h1>客户端图片上传预览示例</h1>
<div class="img-wrap">
<img src="" alt="" id="img" />
</div>
<br />
<!-- <button>头像</button> -->
<input hidden type="file" id="file" onchange="onFileChange()" />
<script>
const img = document.querySelector("#img");
const file = document.querySelector("#file");
// const btn = document.querySelector("button");
// const ipt = document.querySelector("input");
function onFileChange() {
// 得到 file-input 的文件对象
const fileObj = file.files[0];
const data = window.URL.createObjectURL(fileObj);
img.src = data;
}
// btn.addEventListener("click", () => ipt.click());
</script>
</body>
</html>

而在实际的项目中,file 类型的 input 框基本上都是隐藏的,通过另外一个显示的元素的某些事件(比如点击事件)以代码的方式手动触发 input 的点击事件。也就是注释部分。

二、图片裁切

1、安装 cropperjs 插件

1
npm install cropperjs

2、创建图片裁剪组件

update-photo.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<template>
<div class="update-photo">
<img class="img" :src="img" ref="img" />

<div class="toolbar">
<div class="cancel" @click="$emit('close')">取消</div>
<div class="confirm" @click="onConfirm">完成</div>
</div>
</div>
</template>

<script>
import "cropperjs/dist/cropper.css";
import Cropper from "cropperjs";
import { updateUserPhoto } from "@/api/user";

export default {
name: "UpdatePhoto",
components: {},
props: {
img: {
type: [String, Object],
required: true,
},
},
data() {
return {
cropper: null,
};
},
computed: {},
watch: {},
created() {},
mounted() {
const image = this.$refs.img;
this.cropper = new Cropper(image, {
viewMode: 1,
dragMode: "move",
aspectRatio: 1,
// autoCropArea: 1,
cropBoxMovable: false,
cropBoxResizable: false,
background: false,
});
},
methods: {
onConfirm() {
// 基于服务端的裁切使用 getData 方法获取裁切参数
// console.log(this.cropper.getData())

// 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象
this.cropper.getCroppedCanvas().toBlob((blob) => {
this.updateUserPhoto(blob);
});
},

async updateUserPhoto(blob) {
this.$toast.loading({
message: "保存中...",
forbidClick: true, // 禁止背景点击
duration: 0, // 持续展示
});
try {
// 错误的用法
// 如果接口要求 Content-Type 是 application/json
// 则传递普通 JavaScript 对象
// updateUserPhoto({
// photo: blob
// })

// 如果接口要求 Content-Type 是 multipart/form-data
// 则你必须传递 FormData 对象
const formData = new FormData();
formData.append("photo", blob);

const { data } = await updateUserPhoto(formData);

// 关闭弹出层
this.$emit("close");

// 更新视图
this.$emit("update-photo", data.data.photo);

// 提示成功
this.$toast.success("更新成功");
} catch (err) {
this.$toast.fail("更新失败");
}
},
},
};
</script>

<style scoped lang="less">
.update-photo {
background-color: #000;
height: 100%;
.toolbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: space-between;
.cancel,
.confirm {
width: 90px;
height: 90px;
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
}
}
.img {
display: block;
max-width: 100%;
}
</style>

可以看到,图片裁剪组件从父组件接收传递过来的图片地址。在 mounted 根据图片 dom 创建一个 cropper 对象,里面的配置参数我们可以根据需要填写。当点击完成时,使用 getCroppedCanvas 获取裁切的文件对象,并将该对象传递给后端。

3、父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<template>
<van-cell
class="photo-cell"
title="头像"
is-link
center
@click="$refs.file.click()"
>
<van-image class="avatar" fit="cover" round :src="user.photo" />
</van-cell>
<van-popup
v-model="isUpdatePhotoShow"
position="bottom"
style="height: 100%;"
>
<update-photo
v-if="isUpdatePhotoShow"
:img="img"
@close="isUpdatePhotoShow = false"
@update-photo="user.photo = $event"
/>
</van-popup>
</template>
<script>
import { getUserProfile } from "@/api/user";
import UpdatePhoto from "./components/update-photo";
export default {
name: "UserProfile",
components: {
UpdatePhoto,
},
props: {},
data() {
return {
user: {}, // 个人信息
isUpdatePhotoShow: false,
img: null, // 预览的图片
};
},
computed: {},
watch: {},
created() {
this.loadUserProfile();
},
mounted() {},
methods: {
async loadUserProfile() {
try {
const { data } = await getUserProfile();
this.user = data.data;
} catch (err) {
this.$toast("数据获取失败");
}
},

onFileChange() {
// 获取文件对象
const file = this.$refs.file.files[0];

// 基于文章对象获取 blob 数据
this.img = window.URL.createObjectURL(file);

// 展示预览图片弹出层
this.isUpdatePhotoShow = true;

// file-input 如果选了同一个文件不会触发 change 事件
// 解决办法就是每次使用完毕,把它的 value 清空
this.$refs.file.value = "";
},
},
};
</script>
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 congtianfeng
  • 访问人数: | 浏览次数: