思路:1.先做出一个上传的图片的上传区

  <!-- 上传区 -->
<label for="fileUp">
<div class="upBorder">
<img src="../assets/add.png" alt="" />
<input
ref="fileUp"
type="file"
id="fileUp"
accept="image"
style="display: none"
@change="upload()"
/>
</div>
</label>

  

  upload() {
let that = this;
console.log(this.$refs.fileUp.files);
if (this.$refs.fileUp.files.length != 0) {
const reader = new FileReader();
reader.readAsDataURL(this.$refs.fileUp.files[0]);
reader.onload = function () {
const img = new Image();
img.src = reader.result;
that.fileList.push(reader.result);
that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片
console.log(reader.result);
};
this.upLodaOk = true;
}
},

  给上传图片的input绑定上ref属性然后通过FileReader构造函数获取上传的文件。

2.完成已上传文件的预览区域

 <!-- 预览区域 -->
<div
class="preView"
v-for="(i, index) in fileList"
:key="index"
ref="preList"
>
<div class="fileList" v-if="upLodaOk">
<img
src="../assets/remove.png"
alt=""
class="remove"
@click="removeProp(index)"
/>
<img
:src="fileList[index]"
alt=""
class="img"
@click="cut(index)"
ref="imgitem"
/>
</div>
</div>

  在upload方法中将通过FileReader构造函数获取上传的文件push到fileList数组中然后遍历渲染出已经上传的图片列表,并且给每一个图片绑定ref属性。

3.完成图片删除的功能

<!-- 删除弹窗 -->
<div
class="prop"
:style="{
height: this.windowHeight + 'px',
width: this.windowWidth + 'px',
}"
v-if="show"
>
<div class="text">
<img
src="../assets/remove.png"
alt=""
class="close"
@click="removePropClose()"
/>
<div>要删除这张照片吗</div>
<div class="action">
<button class="btn green" @click="removePropClose()">取消</button>
<button class="btn blue" @click="remove()">确定</button>
</div>
</div>
</div>
   removeProp(index) {
//v-for循环中的ref是个数组,根据index来取每一个对应的dom元素
this.removeIndex = index;
this.show = true;
},
removePropClose() {
this.show = false;
},
remove() {
this.fileList.splice(this.removeIndex, 1);
this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片
console.log(this.$refs.fileUp.value);
this.show = false;
},

点击预览图片上的x会触发删除确认弹窗,在removeProp方法中将要删除的图片的Index接收并存储的removeIndex变量中,remove方法中将fileList数组中对应索引的元素去掉并且重置一下上传属性,也可以在每次上传后重置,并且关闭弹窗

4.完成上传时的剪裁功能

   <!-- 裁剪蒙层 -->
<div
class="prop center"
v-if="cutProp"
:style="{
height: this.windowHeight + 'px',
width: this.windowWidth + 'px',
}"
>
<div v-html="pre" ref="preimg" class="imgContent"></div>
<div class="cutHandler">
<button class="btn green" @click="cancel()">取消</button>
<button class="btn blue" @click="qdcut()">剪裁</button>
</div>
</div>
   cut(index) {
this.selIndex = index;
this.pre = `<img
src="${this.fileList[index]}"
alt=""
class='cutImg'
/>`;
this.cutProp = true;
console.log(this.$refs);
this.$nextTick(function () {
console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点
this.myCropper = new Cropper(this.$refs.preimg.firstChild, {
aspectRatio: 1 / 1,
dragMode: "move",
outputType: "png", //防止图片背景变黑
crop(event) {
console.log(event.detail.x);
console.log(event.detail.y);
console.log(event.detail.width);
console.log(event.detail.height);
console.log(event.detail.rotate);
console.log(event.detail.scaleX);
console.log(event.detail.scaleY);
},
});
});
},
qdcut() {
let cropBox = this.myCropper.getCropBoxData();
console.log(this.myCropper.getCropBoxData()); //打印裁剪数据
let cropCanvas = this.myCropper.getCroppedCanvas({
width: cropBox.width,
height: cropBox.height,
}); //使用画布画出裁剪后的图片
let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据
console.log(imgData);
this.fileList.splice(this.selIndex, 1, imgData);
console.log(this.fileList);
this.cutProp = false;
}, //确定裁剪
cancel() {
this.cutProp = false;
}, //取消裁剪

  因为本次封装的是预览时裁剪的功能,所以裁剪的是点击预览列表中的文件触发的,cut方法将选择的图片的index存储selIndex变量中,然后通过v-html指令在剪裁弹窗中加载出对应的图片来进行裁剪,裁剪使用cropper.js来进行的,注意使用时要在this.$nextTick方法的回调中来进行剪裁函数的初始化,这样才能获取到通过v-html指令插入的图片。

  选择合适的裁剪尺寸后点击确认才加调用qdcut方法,通过cropper.js的内置方法getCropBoxData()获取剪裁的数据,通过getCroppedCanvas()传入对应的数据然后导出剪裁后的图片,将fileList中对应的元素替换即可完成

6.下面附上整个代码,可以直接拿去使用:

  1 <template>
2 <div>
3 <!-- 裁剪蒙层 -->
4 <div
5 class="prop center"
6 v-if="cutProp"
7 :style="{
8 height: this.windowHeight + 'px',
9 width: this.windowWidth + 'px',
10 }"
11 >
12 <div v-html="pre" ref="preimg" class="imgContent"></div>
13 <div class="cutHandler">
14 <button class="btn green" @click="cancel()">取消</button>
15 <button class="btn blue" @click="qdcut()">剪裁</button>
16 </div>
17 </div>
18 <!-- 删除弹窗 -->
19 <div
20 class="prop"
21 :style="{
22 height: this.windowHeight + 'px',
23 width: this.windowWidth + 'px',
24 }"
25 v-if="show"
26 >
27 <div class="text">
28 <img
29 src="../assets/remove.png"
30 alt=""
31 class="close"
32 @click="removePropClose()"
33 />
34 <div>要删除这张照片吗</div>
35 <div class="action">
36 <button class="btn green" @click="removePropClose()">取消</button>
37 <button class="btn blue" @click="remove()">确定</button>
38 </div>
39 </div>
40 </div>
41 <!-- 上传区域 -->
42 <div class="upContent">
43 <!-- 预览区域 -->
44 <div
45 class="preView"
46 v-for="(i, index) in fileList"
47 :key="index"
48 ref="preList"
49 >
50 <div class="fileList" v-if="upLodaOk">
51 <img
52 src="../assets/remove.png"
53 alt=""
54 class="remove"
55 @click="removeProp(index)"
56 />
57 <img
58 :src="fileList[index]"
59 alt=""
60 class="img"
61 @click="cut(index)"
62 ref="imgitem"
63 />
64 </div>
65 </div>
66 <!-- 上传区 -->
67 <label for="fileUp">
68 <div class="upBorder">
69 <img src="../assets/add.png" alt="" />
70 <input
71 ref="fileUp"
72 type="file"
73 id="fileUp"
74 accept="image"
75 style="display: none"
76 @change="upload()"
77 />
78 </div>
79 </label>
80 </div>
81 </div>
82 </template>
83 <script>
84 import Cropper from "cropperjs";
85 import "cropperjs/dist/cropper.css";
86 export default {
87 name: "upload",
88 data() {
89 return {
90 cutProp: false,
91 pre: "", //准备剪裁的图片
92 selIndex: "", //选择照片的索引
93 removeIndex: "", //准备删除的照片的索引
94 show: false, //删除弹出层
95 myCropper: null,
96 afterImg: "",
97 ingData: null,
98 upLodaOk: false, //是否展示预览列表
99 fileList: [], //已经上传图片的列表
100 };
101 },
102 methods: {
103 upload() {
104 let that = this;
105 console.log(this.$refs.fileUp.files);
106 if (this.$refs.fileUp.files.length != 0) {
107 const reader = new FileReader();
108 reader.readAsDataURL(this.$refs.fileUp.files[0]);
109 reader.onload = function () {
110 const img = new Image();
111 img.src = reader.result;
112 that.fileList.push(reader.result);
113 that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片
114 console.log(reader.result);
115 };
116 this.upLodaOk = true;
117 }
118 },
119 removeProp(index) {
120 //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素
121 this.removeIndex = index;
122 this.show = true;
123 },
124 removePropClose() {
125 this.show = false;
126 },
127 remove() {
128 this.fileList.splice(this.removeIndex, 1);
129 this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片
130 console.log(this.$refs.fileUp.value);
131 this.show = false;
132 },
133 cut(index) {
134 this.selIndex = index;
135 this.pre = `<img
136 src="${this.fileList[index]}"
137 alt=""
138 class='cutImg'
139 />`;
140 this.cutProp = true;
141 console.log(this.$refs);
142 this.$nextTick(function () {
143 console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点
144 this.myCropper = new Cropper(this.$refs.preimg.firstChild, {
145 aspectRatio: 1 / 1,
146 dragMode: "move",
147 outputType: "png", //防止图片背景变黑
148 crop(event) {
149 console.log(event.detail.x);
150 console.log(event.detail.y);
151 console.log(event.detail.width);
152 console.log(event.detail.height);
153 console.log(event.detail.rotate);
154 console.log(event.detail.scaleX);
155 console.log(event.detail.scaleY);
156 },
157 });
158 });
159 },
160 qdcut() {
161 let cropBox = this.myCropper.getCropBoxData();
162 console.log(this.myCropper.getCropBoxData()); //打印裁剪数据
163 let cropCanvas = this.myCropper.getCroppedCanvas({
164 width: cropBox.width,
165 height: cropBox.height,
166 }); //使用画布画出裁剪后的图片
167 let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据
168 console.log(imgData);
169 this.fileList.splice(this.selIndex, 1, imgData);
170 console.log(this.fileList);
171 this.cutProp = false;
172 }, //确定裁剪
173 cancel() {
174 this.cutProp = false;
175 }, //取消裁剪
176 },
177 mounted() {},
178 computed: {
179 windowWidth() {
180 return document.documentElement.clientWidth;
181 },
182 windowHeight() {
183 return document.documentElement.clientHeight;
184 },
185 }, //监听屏幕的宽度和高度
186 };
187 </script>
188 <style>
189 .upBorder {
190 width: 8rem;
191 height: 8rem;
192 border: 1px silver dashed;
193 display: flex;
194 justify-content: center;
195 align-items: center;
196 }
197 .upContent {
198 display: flex;
199 justify-content: center;
200 align-items: center;
201 }
202 .img {
203 width: 8rem;
204 height: 8rem;
205 }
206
207 .fileList {
208 position: relative;
209 display: flex;
210 flex-direction: column;
211 justify-content: center;
212 align-items: center;
213 }
214 .remove {
215 position: absolute;
216 width: 1rem;
217 height: 1rem;
218 top: 0rem;
219 right: 0rem;
220 cursor: pointer;
221 }
222 .prop {
223 vertical-align: middle;
224 position: fixed;
225 top: 0;
226 left: 0;
227 z-index: 999;
228 background-color: rgba(0, 0, 0, 0.7);
229 }
230 .text {
231 border-radius: 0.2rem;
232 top: 50%;
233 left: 50%;
234 -webkit-transform: translate3d(-50%, -50%, 0);
235 transform: translate3d(-50%, -50%, 0);
236 position: fixed;
237 z-index: 1000;
238 color: black;
239 text-align: center;
240 background-color: #fff;
241 padding: 2rem 4rem;
242 white-space: nowrap;
243 }
244 .close {
245 position: absolute;
246 top: 0.3rem;
247 right: 0.3rem;
248 width: 1rem;
249 height: 1rem;
250 }
251 .action {
252 display: flex;
253 justify-content: space-between;
254 align-items: center;
255 margin-top: 1rem;
256 }
257 .btn {
258 font-size: 0.12rem;
259 color: #fff;
260 padding: 0.2rem 0.8rem;
261 }
262 .blue {
263 background-color: #1989fa;
264 border: 1px solid #1989fa;
265 }
266 .green {
267 background-color: #07c160;
268 border: 1px solid #07c160;
269 }
270 .cropper-point.point-se {
271 width: 5px;
272 height: 5px;
273 }
274 .cropper {
275 position: fixed;
276 top: 0;
277 z-index: 999;
278 }
279
280 /* .cropper-container{
281 top: 50%;
282 left: 50%;
283 -webkit-transform: translate3d(-50%, -50%, 0);
284 transform: translate3d(-50%, -50%, 0);
285 } */
286 .imgContent {
287 width: 16rem;
288 height: 16rem;
289 display: inline-block;
290 /* top: 50%;
291 left: 50%;
292 -webkit-transform: translate3d(-50%, -50%, 0);
293 transform: translate3d(-50%, -50%, 0); */
294 }
295 .cutImg {
296 display: block;
297 max-width: 100%;
298 }
299 .center {
300 display: flex;
301 flex-direction: column;
302 justify-content: center;
303 align-items: center;
304 }
305 .cropper-bg {
306 background: none;
307 }
308 .cutHandler {
309 margin-top: 2rem;
310 width: 16rem;
311 text-align: center;
312 display: flex;
313 justify-content: space-between;
314 align-items: center;
315 }
316 .cropper-modal {
317 background: rgba(0, 0, 0, 0);
318 }
319 </style>

运行截图:

 H5,PC端都可以使用
 

vue封装原生的可预览裁剪上传图片插件H5,PC端都可以使用的更多相关文章

  1. Jcrop+uploadify+php实现上传头像预览裁剪

    最近由于项目需要,所以做了一个上传头像预览并且可以预览裁剪的功能,大概思路是上传的图片先保存到服务器,然后通过ajax从服务器获取到图片信息,再利用Jcrop插件进行裁剪,之后通过PHP获取到的四个裁 ...

  2. previewImage.js图片预览缩放保存插件

    previewImage.js好用的图片预览缩放保存插件

  3. 上传预览图片的插件jquery-fileupload

    上传预览图片的插件jquery-fileupload github地址:https://github.com/blueimp/jQuery-File-Upload 中文文档:http://www.jq ...

  4. [RN] React Native 使用 图片预览和放大 插件 react-native-image-zoom-viewer 过程中,放大报错问题

    React Native 使用 图片预览和放大 插件 react-native-image-zoom-viewer 过程中,放大报错问题 报错如下: Cannot record touch end w ...

  5. vue项目上传Github预览

    最近在用Vue仿写cnode社区,想要上传到github,并通过Github pages预览,在这个过程中遇到了一些问题,因此写个笔记,以便查阅. 完成Vue项目以后,在上传到github之前,需要修 ...

  6. 在 vue 中使用 vieiwer 图片预览插件

    https://blog.csdn.net/WestLonly/article/details/79801800?utm_source=blogxgwz0 首先,感谢原作者 官网链接 github地址 ...

  7. vue图片点击放大预览

    第一种:viewerjs使用介绍(PC.移动端都兼容) 1.先安装依赖 npm install v-viewer --save 2.main.js内引用并注册调用 //main.js import V ...

  8. webform的原生操作图片预览和上传

    1.使用input标签进行图片操作,input的标签有一个accept属性,accept 属性只能与 <input type="file"> 配合使用.它规定能够通过文 ...

  9. 【VUE】图片预览放大缩小插件

    From: https://www.jianshu.com/p/e3350aa1b0d0 在看项目时,突然看到预览图片的弹窗,感觉好僵硬,不能放大,不能切换,于是便在网上找下关于图片预览的插件,有找到 ...

随机推荐

  1. Linux获取本机公网IP,调整双节点主从服务的RPC调用逻辑

    简单记录一次双节点的之间的服务调用叭 ~ 现有: 服务A的双节点A1.A2 服务B的双节点B1.B2 服务A 和服务B 通过 Netty 实现 RPC 通信,可能会导致比较玄学的问题.如图: 要做到 ...

  2. day02 真正的高并发还得看IO多路复用

    教程说明 C++高性能网络服务保姆级教程 首发地址 day02 真正的高并发还得看IO多路复用 本节目的 使用epoll实现一个高并发的服务器 从单进程讲起 上节从一个基础的socket服务说起我们实 ...

  3. 变量命名 函数命名 方法 Naming cheatsheet

    Naming things is hard. This sheet attempts to make it easier. Although these suggestions can be appl ...

  4. 自己在ubuntu16.04 上用的软件和配置

    软件: 1.WPS2019: 这个不用多说了,真的是比之前的wps好太多了. 2.Chrom的画图插件: http://Draw.io,非常强,Draw.io 是一款在线图表编辑工具, 可以用来编辑工 ...

  5. clion 预编译文件的查看

    看了一圈网上也没有我能一下就能看的懂的配置教程 我就手打一篇给在用clion的同学来参考一下 本文适用于g++编译 cmake Ninja生成器 clion 默认使用的是CMAKE来构建程序 生成器用 ...

  6. Linux-进程工具

    1.进程树 pstree pstree 可以用来显示进程的父子关系,以树形结构显示 格式: pstree [OPTION] [ PID | USER ] 常用选项: -p 显示PID -T 不显示线程 ...

  7. 「Python实用秘技08」一行代码解析地址信息

    本文完整示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/PythonPracticalSkills 这是我的系列文章「Python实用秘技」的第8期 ...

  8. 详解 Java 17 中新推出的密封类

    Java 17推出的新特性Sealed Classes经历了2个Preview版本(JDK 15中的JEP 360.JDK 16中的JEP 397),最终定稿于JDK 17中的JEP 409.Seal ...

  9. go 语言开发2 简易数据库和web代码示例

    数据库开发示例 package dao import ( "github.com/go-xorm/xorm" "fmt" ) type UserInfo str ...

  10. 服务器上详细前后端分离项目搭建(springboot+vue)

    介绍:本文用的经典的前后端分离开源项目ruoyi Gitee链接地址:https://gitee.com/y_project/RuoYi 一.拉取项目: 利用Git把项目拉取到本地,也可以直接利用id ...