vue-cropper.vue 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149
  1. <template>
  2. <div class="vue-cropper" ref="cropper" @mouseover="scaleImg" @mouseout="cancelScale">
  3. <div class="cropper-box" v-if="imgs">
  4. <div
  5. class="cropper-box-canvas"
  6. v-show="!loading"
  7. :style="{
  8. 'width': trueWidth + 'px',
  9. 'height': trueHeight + 'px',
  10. 'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ x / scale + 'px,' + y / scale + 'px,' + '0)'
  11. + 'rotateZ('+ rotate * 90 +'deg)'
  12. }"
  13. >
  14. <img :src="imgs" alt="cropper-img" ref="cropperImg">
  15. </div>
  16. </div>
  17. <div
  18. class="cropper-drag-box"
  19. :class="{'cropper-move': move && !crop, 'cropper-crop': crop, 'cropper-modal': cropping}"
  20. @mousedown="startMove"
  21. @touchstart="startMove"
  22. ></div>
  23. <div
  24. v-show="cropping"
  25. class="cropper-crop-box"
  26. :style="{
  27. 'width': cropW + 'px',
  28. 'height': cropH + 'px',
  29. 'transform': 'translate3d('+ cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'
  30. }"
  31. >
  32. <span class="cropper-view-box">
  33. <img
  34. :style="{
  35. 'width': trueWidth + 'px',
  36. 'height': trueHeight + 'px',
  37. 'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ (x - cropOffsertX) / scale + 'px,' + (y - cropOffsertY) / scale + 'px,' + '0)'
  38. + 'rotateZ('+ rotate * 90 +'deg)'
  39. }"
  40. :src="imgs"
  41. alt="cropper-img"
  42. >
  43. </span>
  44. <span class="cropper-face cropper-move" @mousedown="cropMove" @touchstart="cropMove"></span>
  45. <span
  46. class="crop-info"
  47. v-if="info"
  48. :style="{'top': cropInfo.top}"
  49. >{{ cropInfo.width }} × {{ cropInfo.height }}</span>
  50. <span v-if="!fixedBox">
  51. <span
  52. class="crop-line line-w"
  53. @mousedown="changeCropSize($event, false, true, 0, 1)"
  54. @touchstart="changeCropSize($event, false, true, 0, 1)"
  55. ></span>
  56. <span
  57. class="crop-line line-a"
  58. @mousedown="changeCropSize($event, true, false, 1, 0)"
  59. @touchstart="changeCropSize($event, true, false, 1, 0)"
  60. ></span>
  61. <span
  62. class="crop-line line-s"
  63. @mousedown="changeCropSize($event, false, true, 0, 2)"
  64. @touchstart="changeCropSize($event, false, true, 0, 2)"
  65. ></span>
  66. <span
  67. class="crop-line line-d"
  68. @mousedown="changeCropSize($event, true, false, 2, 0)"
  69. @touchstart="changeCropSize($event, true, false, 2, 0)"
  70. ></span>
  71. <span
  72. class="crop-point point1"
  73. @mousedown="changeCropSize($event, true, true, 1, 1)"
  74. @touchstart="changeCropSize($event, true, true, 1, 1)"
  75. ></span>
  76. <span
  77. class="crop-point point2"
  78. @mousedown="changeCropSize($event, false, true, 0, 1)"
  79. @touchstart="changeCropSize($event, false, true, 0, 1)"
  80. ></span>
  81. <span
  82. class="crop-point point3"
  83. @mousedown="changeCropSize($event, true, true, 2, 1)"
  84. @touchstart="changeCropSize($event, true, true, 2, 1)"
  85. ></span>
  86. <span
  87. class="crop-point point4"
  88. @mousedown="changeCropSize($event, true, false, 1, 0)"
  89. @touchstart="changeCropSize($event, true, false, 1, 0)"
  90. ></span>
  91. <span
  92. class="crop-point point5"
  93. @mousedown="changeCropSize($event, true, false, 2, 0)"
  94. @touchstart="changeCropSize($event, true, false, 2, 0)"
  95. ></span>
  96. <span
  97. class="crop-point point6"
  98. @mousedown="changeCropSize($event, true, true, 1, 2)"
  99. @touchstart="changeCropSize($event, true, true, 1, 2)"
  100. ></span>
  101. <span
  102. class="crop-point point7"
  103. @mousedown="changeCropSize($event, false, true, 0, 2)"
  104. @touchstart="changeCropSize($event, false, true, 0, 2)"
  105. ></span>
  106. <span
  107. class="crop-point point8"
  108. @mousedown="changeCropSize($event, true, true, 2, 2)"
  109. @touchstart="changeCropSize($event, true, true, 2, 2)"
  110. ></span>
  111. </span>
  112. </div>
  113. </div>
  114. </template>
  115. <script>
  116. import { defineComponent } from 'vue'
  117. import exifmin from "./exif-js-min";
  118. export default defineComponent({
  119. data: function() {
  120. return {
  121. // 容器高宽
  122. w: 0,
  123. h: 0,
  124. // 图片缩放比例
  125. scale: 1,
  126. // 图片偏移x轴
  127. x: 0,
  128. // 图片偏移y轴
  129. y: 0,
  130. // 图片加载
  131. loading: true,
  132. // 图片真实宽度
  133. trueWidth: 0,
  134. // 图片真实高度
  135. trueHeight: 0,
  136. move: true,
  137. // 移动的x
  138. moveX: 0,
  139. // 移动的y
  140. moveY: 0,
  141. // 开启截图
  142. crop: false,
  143. // 正在截图
  144. cropping: false,
  145. // 裁剪框大小
  146. cropW: 0,
  147. cropH: 0,
  148. cropOldW: 0,
  149. cropOldH: 0,
  150. // 判断是否能够改变
  151. canChangeX: false,
  152. canChangeY: false,
  153. // 改变的基准点
  154. changeCropTypeX: 1,
  155. changeCropTypeY: 1,
  156. // 裁剪框的坐标轴
  157. cropX: 0,
  158. cropY: 0,
  159. cropChangeX: 0,
  160. cropChangeY: 0,
  161. cropOffsertX: 0,
  162. cropOffsertY: 0,
  163. // 支持的滚动事件
  164. support: "",
  165. // 移动端手指缩放
  166. touches: [],
  167. touchNow: false,
  168. // 图片旋转
  169. rotate: 0,
  170. isIos: false,
  171. orientation: 0,
  172. imgs: "",
  173. // 图片缩放系数
  174. coe: 0.2,
  175. // 是否正在多次缩放
  176. scaling: false,
  177. scalingSet: "",
  178. coeStatus: "",
  179. // 控制emit触发频率
  180. isCanShow: true
  181. };
  182. },
  183. props: {
  184. img: {
  185. type: [String, Blob, null, File],
  186. default: ""
  187. },
  188. // 输出图片压缩比
  189. outputSize: {
  190. type: Number,
  191. default: 1
  192. },
  193. outputType: {
  194. type: String,
  195. default: "jpeg"
  196. },
  197. info: {
  198. type: Boolean,
  199. default: true
  200. },
  201. // 是否开启滚轮放大缩小
  202. canScale: {
  203. type: Boolean,
  204. default: true
  205. },
  206. // 是否自成截图框
  207. autoCrop: {
  208. type: Boolean,
  209. default: false
  210. },
  211. autoCropWidth: {
  212. type: [Number, String],
  213. default: 0
  214. },
  215. autoCropHeight: {
  216. type: [Number, String],
  217. default: 0
  218. },
  219. // 是否开启固定宽高比
  220. fixed: {
  221. type: Boolean,
  222. default: false
  223. },
  224. // 宽高比 w/h
  225. fixedNumber: {
  226. type: Array,
  227. default: () => {
  228. return [1, 1];
  229. }
  230. },
  231. // 固定大小 禁止改变截图框大小
  232. fixedBox: {
  233. type: Boolean,
  234. default: false
  235. },
  236. // 输出截图是否缩放
  237. full: {
  238. type: Boolean,
  239. default: false
  240. },
  241. // 是否可以拖动图片
  242. canMove: {
  243. type: Boolean,
  244. default: true
  245. },
  246. // 是否可以拖动截图框
  247. canMoveBox: {
  248. type: Boolean,
  249. default: true
  250. },
  251. // 上传图片按照原始比例显示
  252. original: {
  253. type: Boolean,
  254. default: false
  255. },
  256. // 截图框能否超过图片
  257. centerBox: {
  258. type: Boolean,
  259. default: false
  260. },
  261. // 是否根据dpr输出高清图片
  262. high: {
  263. type: Boolean,
  264. default: true
  265. },
  266. // 截图框展示宽高类型
  267. infoTrue: {
  268. type: Boolean,
  269. default: false
  270. },
  271. // 可以压缩图片宽高 默认不超过200
  272. maxImgSize: {
  273. type: [Number, String],
  274. default: 2000
  275. },
  276. // 倍数 可渲染当前截图框的n倍 0 - 1000;
  277. enlarge: {
  278. type: [Number, String],
  279. default: 1
  280. },
  281. // 自动预览的固定宽度
  282. preW: {
  283. type: [Number, String],
  284. default: 0
  285. },
  286. /*
  287. 图片布局方式 mode 实现和css背景一样的效果
  288. contain 居中布局 默认不会缩放 保证图片在容器里面 mode: 'contain'
  289. cover 拉伸布局 填充整个容器 mode: 'cover'
  290. 如果仅有一个数值被给定,这个数值将作为宽度值大小,高度值将被设定为auto。 mode: '50px'
  291. 如果有两个数值被给定,第一个将作为宽度值大小,第二个作为高度值大小。 mode: '50px 60px'
  292. */
  293. mode: {
  294. type: String,
  295. default: "contain"
  296. },
  297. //限制最小区域,可传1以上的数字和字符串,限制长宽都是这么大
  298. // 也可以传数组[90,90]
  299. limitMinSize: {
  300. type: [Number, Array, String],
  301. default: () => {
  302. return 10;
  303. },
  304. validator: function (value) {
  305. if (Array.isArray(value)) {
  306. return Number(value[0]) >= 0 && Number(value[1]) >= 0;
  307. } else {
  308. return Number(value) >= 0;
  309. }
  310. },
  311. },
  312. },
  313. computed: {
  314. cropInfo() {
  315. let obj = {};
  316. obj.top = this.cropOffsertY > 21 ? "-21px" : "0px";
  317. obj.width = this.cropW > 0 ? this.cropW : 0;
  318. obj.height = this.cropH > 0 ? this.cropH : 0;
  319. if (this.infoTrue) {
  320. let dpr = 1;
  321. if (this.high && !this.full) {
  322. dpr = window.devicePixelRatio;
  323. }
  324. if ((this.enlarge !== 1) & !this.full) {
  325. dpr = Math.abs(Number(this.enlarge));
  326. }
  327. obj.width = obj.width * dpr;
  328. obj.height = obj.height * dpr;
  329. if (this.full) {
  330. obj.width = obj.width / this.scale;
  331. obj.height = obj.height / this.scale;
  332. }
  333. }
  334. obj.width = obj.width.toFixed(0);
  335. obj.height = obj.height.toFixed(0);
  336. return obj;
  337. },
  338. isIE() {
  339. var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
  340. const isIE = !!window.ActiveXObject || 'ActiveXObject' in window; //判断是否IE浏览器
  341. return isIE;
  342. },
  343. passive () {
  344. return this.isIE ? null : {
  345. passive: false
  346. }
  347. }
  348. },
  349. watch: {
  350. // 如果图片改变, 重新布局
  351. img() {
  352. // 当传入图片时, 读取图片信息同时展示
  353. this.checkedImg();
  354. },
  355. imgs(val) {
  356. if (val === "") {
  357. return;
  358. }
  359. this.reload();
  360. },
  361. cropW() {
  362. this.showPreview();
  363. },
  364. cropH() {
  365. this.showPreview();
  366. },
  367. cropOffsertX() {
  368. this.showPreview();
  369. },
  370. cropOffsertY() {
  371. this.showPreview();
  372. },
  373. scale(val, oldVal) {
  374. this.showPreview();
  375. },
  376. x() {
  377. this.showPreview();
  378. },
  379. y() {
  380. this.showPreview();
  381. },
  382. autoCrop(val) {
  383. if (val) {
  384. this.goAutoCrop();
  385. }
  386. },
  387. // 修改了自动截图框
  388. autoCropWidth() {
  389. if (this.autoCrop) {
  390. this.goAutoCrop();
  391. }
  392. },
  393. autoCropHeight() {
  394. if (this.autoCrop) {
  395. this.goAutoCrop();
  396. }
  397. },
  398. mode() {
  399. this.checkedImg();
  400. },
  401. rotate() {
  402. this.showPreview();
  403. if (this.autoCrop) {
  404. this.goAutoCrop(this.cropW, this.cropH);
  405. } else {
  406. if (this.cropW > 0 || this.cropH > 0) {
  407. this.goAutoCrop(this.cropW, this.cropH);
  408. }
  409. }
  410. },
  411. },
  412. methods: {
  413. getVersion (name) {
  414. var arr = navigator.userAgent.split(' ');
  415. var chromeVersion = '';
  416. let result = 0;
  417. const reg = new RegExp(name, 'i')
  418. for(var i=0;i < arr.length;i++){
  419. if(reg.test(arr[i]))
  420. chromeVersion = arr[i]
  421. }
  422. if(chromeVersion){
  423. result = chromeVersion.split('/')[1].split('.');
  424. } else {
  425. result = ['0', '0', '0'];
  426. }
  427. return result
  428. },
  429. checkOrientationImage(img, orientation, width, height) {
  430. // 如果是 chrome内核版本在81 safari 在 605 以上不处理图片旋转
  431. // alert(navigator.userAgent)
  432. if (this.getVersion('chrome')[0] >= 81) {
  433. orientation = -1
  434. } else {
  435. if (this.getVersion('safari')[0] >= 605 ) {
  436. const safariVersion = this.getVersion('version')
  437. if (safariVersion[0] > 13 && safariVersion[1] > 1) {
  438. orientation = -1
  439. }
  440. } else {
  441. // 判断 ios 版本进行处理
  442. // 针对 ios 版本大于 13.4的系统不做图片旋转
  443. const isIos = navigator.userAgent.toLowerCase().match(/cpu iphone os (.*?) like mac os/)
  444. if (isIos) {
  445. let version = isIos[1]
  446. version = version.split('_')
  447. if (version[0] > 13 || (version[0] >= 13 && version[1] >= 4)) {
  448. orientation = -1
  449. }
  450. }
  451. }
  452. }
  453. // alert(`当前处理的orientation${orientation}`)
  454. let canvas = document.createElement("canvas");
  455. let ctx = canvas.getContext("2d");
  456. ctx.save();
  457. switch (orientation) {
  458. case 2:
  459. canvas.width = width;
  460. canvas.height = height;
  461. // horizontal flip
  462. ctx.translate(width, 0);
  463. ctx.scale(-1, 1);
  464. break;
  465. case 3:
  466. canvas.width = width;
  467. canvas.height = height;
  468. //180 graus
  469. ctx.translate(width / 2, height / 2);
  470. ctx.rotate((180 * Math.PI) / 180);
  471. ctx.translate(-width / 2, -height / 2);
  472. break;
  473. case 4:
  474. canvas.width = width;
  475. canvas.height = height;
  476. // vertical flip
  477. ctx.translate(0, height);
  478. ctx.scale(1, -1);
  479. break;
  480. case 5:
  481. // vertical flip + 90 rotate right
  482. canvas.height = width;
  483. canvas.width = height;
  484. ctx.rotate(0.5 * Math.PI);
  485. ctx.scale(1, -1);
  486. break;
  487. case 6:
  488. canvas.width = height;
  489. canvas.height = width;
  490. //90 graus
  491. ctx.translate(height / 2, width / 2);
  492. ctx.rotate((90 * Math.PI) / 180);
  493. ctx.translate(-width / 2, -height / 2);
  494. break;
  495. case 7:
  496. // horizontal flip + 90 rotate right
  497. canvas.height = width;
  498. canvas.width = height;
  499. ctx.rotate(0.5 * Math.PI);
  500. ctx.translate(width, -height);
  501. ctx.scale(-1, 1);
  502. break;
  503. case 8:
  504. canvas.height = width;
  505. canvas.width = height;
  506. //-90 graus
  507. ctx.translate(height / 2, width / 2);
  508. ctx.rotate((-90 * Math.PI) / 180);
  509. ctx.translate(-width / 2, -height / 2);
  510. break;
  511. default:
  512. canvas.width = width;
  513. canvas.height = height;
  514. }
  515. ctx.drawImage(img, 0, 0, width, height);
  516. ctx.restore();
  517. canvas.toBlob(
  518. blob => {
  519. let data = URL.createObjectURL(blob);
  520. URL.revokeObjectURL(this.imgs)
  521. this.imgs = data;
  522. },
  523. "image/" + this.outputType,
  524. 1
  525. );
  526. },
  527. // checkout img
  528. checkedImg() {
  529. if (this.img === null || this.img === '') {
  530. this.imgs = ''
  531. this.clearCrop()
  532. return
  533. }
  534. this.loading = true;
  535. this.scale = 1;
  536. this.rotate = 0;
  537. this.clearCrop();
  538. let img = new Image();
  539. img.onload = () => {
  540. if (this.img === "") {
  541. this.$emit("img-load", "error");
  542. return false;
  543. }
  544. let width = img.width;
  545. let height = img.height;
  546. exifmin.getData(img).then(data => {
  547. this.orientation = data.orientation || 1;
  548. let max = Number(this.maxImgSize);
  549. if (!this.orientation && (width < max) & (height < max)) {
  550. this.imgs = this.img;
  551. return;
  552. }
  553. if (width > max) {
  554. height = (height / width) * max;
  555. width = max;
  556. }
  557. if (height > max) {
  558. width = (width / height) * max;
  559. height = max;
  560. }
  561. this.checkOrientationImage(img, this.orientation, width, height);
  562. });
  563. };
  564. img.onerror = () => {
  565. this.$emit("img-load", "error");
  566. };
  567. // 判断如果不是base64图片 再添加crossOrigin属性,否则会导致iOS低版本(10.2)无法显示图片
  568. if (this.img.substr(0, 4) !== "data") {
  569. img.crossOrigin = "";
  570. }
  571. if (this.isIE) {
  572. var xhr = new XMLHttpRequest();
  573. xhr.onload = function() {
  574. var url = URL.createObjectURL(this.response);
  575. img.src = url;
  576. };
  577. xhr.open("GET", this.img, true);
  578. xhr.responseType = "blob";
  579. xhr.send();
  580. } else {
  581. img.src = this.img;
  582. }
  583. },
  584. // 当按下鼠标键
  585. startMove(e) {
  586. e.preventDefault();
  587. // 如果move 为true 表示当前可以拖动
  588. if (this.move && !this.crop) {
  589. if (!this.canMove) {
  590. return false;
  591. }
  592. // 开始移动
  593. this.moveX = ('clientX' in e ? e.clientX : e.touches[0].clientX) - this.x;
  594. this.moveY = ('clientY' in e ? e.clientY : e.touches[0].clientY) - this.y;
  595. if (e.touches) {
  596. window.addEventListener("touchmove", this.moveImg);
  597. window.addEventListener("touchend", this.leaveImg);
  598. if (e.touches.length == 2) {
  599. // 记录手指刚刚放上去
  600. this.touches = e.touches;
  601. window.addEventListener("touchmove", this.touchScale);
  602. window.addEventListener("touchend", this.cancelTouchScale);
  603. }
  604. } else {
  605. window.addEventListener("mousemove", this.moveImg);
  606. window.addEventListener("mouseup", this.leaveImg);
  607. }
  608. // 触发图片移动事件
  609. this.$emit("img-moving", {
  610. moving: true,
  611. axis: this.getImgAxis()
  612. });
  613. } else {
  614. // 截图ing
  615. this.cropping = true;
  616. // 绑定截图事件
  617. window.addEventListener("mousemove", this.createCrop);
  618. window.addEventListener("mouseup", this.endCrop);
  619. window.addEventListener("touchmove", this.createCrop);
  620. window.addEventListener("touchend", this.endCrop);
  621. this.cropOffsertX = e.offsetX
  622. ? e.offsetX
  623. : e.touches[0].pageX - this.$refs.cropper.offsetLeft;
  624. this.cropOffsertY = e.offsetY
  625. ? e.offsetY
  626. : e.touches[0].pageY - this.$refs.cropper.offsetTop;
  627. this.cropX = 'clientX' in e ? e.clientX : e.touches[0].clientX;
  628. this.cropY = 'clientY' in e ? e.clientY : e.touches[0].clientY;
  629. this.cropChangeX = this.cropOffsertX;
  630. this.cropChangeY = this.cropOffsertY;
  631. this.cropW = 0;
  632. this.cropH = 0;
  633. }
  634. },
  635. // 移动端缩放
  636. touchScale(e) {
  637. e.preventDefault();
  638. let scale = this.scale;
  639. // 记录变化量
  640. // 第一根手指
  641. var oldTouch1 = {
  642. x: this.touches[0].clientX,
  643. y: this.touches[0].clientY
  644. };
  645. var newTouch1 = {
  646. x: e.touches[0].clientX,
  647. y: e.touches[0].clientY
  648. };
  649. // 第二根手指
  650. var oldTouch2 = {
  651. x: this.touches[1].clientX,
  652. y: this.touches[1].clientY
  653. };
  654. var newTouch2 = {
  655. x: e.touches[1].clientX,
  656. y: e.touches[1].clientY
  657. };
  658. var oldL = Math.sqrt(
  659. Math.pow(oldTouch1.x - oldTouch2.x, 2) +
  660. Math.pow(oldTouch1.y - oldTouch2.y, 2)
  661. );
  662. var newL = Math.sqrt(
  663. Math.pow(newTouch1.x - newTouch2.x, 2) +
  664. Math.pow(newTouch1.y - newTouch2.y, 2)
  665. );
  666. var cha = newL - oldL;
  667. // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
  668. // 1px - 0.2
  669. var coe = 1;
  670. coe =
  671. coe / this.trueWidth > coe / this.trueHeight
  672. ? coe / this.trueHeight
  673. : coe / this.trueWidth;
  674. coe = coe > 0.1 ? 0.1 : coe;
  675. var num = coe * cha;
  676. if (!this.touchNow) {
  677. this.touchNow = true;
  678. if (cha > 0) {
  679. scale += Math.abs(num);
  680. } else if (cha < 0) {
  681. scale > Math.abs(num) ? (scale -= Math.abs(num)) : scale;
  682. }
  683. this.touches = e.touches;
  684. setTimeout(() => {
  685. this.touchNow = false;
  686. }, 8);
  687. if (!this.checkoutImgAxis(this.x, this.y, scale)) {
  688. return false;
  689. }
  690. this.scale = scale;
  691. }
  692. },
  693. cancelTouchScale(e) {
  694. window.removeEventListener("touchmove", this.touchScale);
  695. },
  696. // 移动图片
  697. moveImg(e) {
  698. e.preventDefault();
  699. if (e.touches && e.touches.length === 2) {
  700. this.touches = e.touches;
  701. window.addEventListener("touchmove", this.touchScale);
  702. window.addEventListener("touchend", this.cancelTouchScale);
  703. window.removeEventListener("touchmove", this.moveImg);
  704. return false;
  705. }
  706. let nowX = 'clientX' in e ? e.clientX : e.touches[0].clientX;
  707. let nowY = 'clientY' in e ? e.clientY : e.touches[0].clientY;
  708. let changeX, changeY;
  709. changeX = nowX - this.moveX;
  710. changeY = nowY - this.moveY;
  711. this.$nextTick(() => {
  712. if (this.centerBox) {
  713. let axis = this.getImgAxis(changeX, changeY, this.scale);
  714. let cropAxis = this.getCropAxis();
  715. let imgW = this.trueHeight * this.scale;
  716. let imgH = this.trueWidth * this.scale;
  717. let maxLeft, maxTop, maxRight, maxBottom;
  718. switch (this.rotate) {
  719. case 1:
  720. case -1:
  721. case 3:
  722. case -3:
  723. maxLeft =
  724. this.cropOffsertX -
  725. (this.trueWidth * (1 - this.scale)) / 2 +
  726. (imgW - imgH) / 2;
  727. maxTop =
  728. this.cropOffsertY -
  729. (this.trueHeight * (1 - this.scale)) / 2 +
  730. (imgH - imgW) / 2;
  731. maxRight = maxLeft - imgW + this.cropW;
  732. maxBottom = maxTop - imgH + this.cropH;
  733. break;
  734. default:
  735. maxLeft =
  736. this.cropOffsertX - (this.trueWidth * (1 - this.scale)) / 2;
  737. maxTop =
  738. this.cropOffsertY - (this.trueHeight * (1 - this.scale)) / 2;
  739. maxRight = maxLeft - imgH + this.cropW;
  740. maxBottom = maxTop - imgW + this.cropH;
  741. break;
  742. }
  743. // 图片左边 图片不能超过截图框
  744. if (axis.x1 >= cropAxis.x1) {
  745. changeX = maxLeft;
  746. }
  747. // 图片上边 图片不能超过截图框
  748. if (axis.y1 >= cropAxis.y1) {
  749. changeY = maxTop;
  750. }
  751. // 图片右边
  752. if (axis.x2 <= cropAxis.x2) {
  753. changeX = maxRight;
  754. }
  755. // 图片下边
  756. if (axis.y2 <= cropAxis.y2) {
  757. changeY = maxBottom;
  758. }
  759. }
  760. this.x = changeX;
  761. this.y = changeY;
  762. // 触发图片移动事件
  763. this.$emit("img-moving", {
  764. moving: true,
  765. axis: this.getImgAxis()
  766. });
  767. });
  768. },
  769. // 移动图片结束
  770. leaveImg(e) {
  771. window.removeEventListener("mousemove", this.moveImg);
  772. window.removeEventListener("touchmove", this.moveImg);
  773. window.removeEventListener("mouseup", this.leaveImg);
  774. window.removeEventListener("touchend", this.leaveImg);
  775. // 触发图片移动事件
  776. this.$emit("img-moving", {
  777. moving: false,
  778. axis: this.getImgAxis()
  779. });
  780. },
  781. // 缩放图片
  782. scaleImg() {
  783. if (this.canScale) {
  784. window.addEventListener(this.support, this.changeSize, this.passive);
  785. }
  786. },
  787. // 移出框
  788. cancelScale() {
  789. if (this.canScale) {
  790. window.removeEventListener(this.support, this.changeSize);
  791. }
  792. },
  793. // 改变大小函数
  794. changeSize(e) {
  795. e.preventDefault();
  796. let scale = this.scale;
  797. var change = e.deltaY || e.wheelDelta;
  798. // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
  799. var isFirefox = navigator.userAgent.indexOf("Firefox");
  800. change = isFirefox > 0 ? change * 30 : change;
  801. // 修复ie的滚动缩放
  802. if (this.isIE) {
  803. change = -change;
  804. }
  805. // 1px - 0.2
  806. var coe = this.coe;
  807. coe =
  808. coe / this.trueWidth > coe / this.trueHeight
  809. ? coe / this.trueHeight
  810. : coe / this.trueWidth;
  811. var num = coe * change;
  812. num < 0
  813. ? (scale += Math.abs(num))
  814. : scale > Math.abs(num)
  815. ? (scale -= Math.abs(num))
  816. : scale;
  817. // 延迟0.1s 每次放大大或者缩小的范围
  818. let status = num < 0 ? "add" : "reduce";
  819. if (status !== this.coeStatus) {
  820. this.coeStatus = status;
  821. this.coe = 0.2;
  822. }
  823. if (!this.scaling) {
  824. this.scalingSet = setTimeout(() => {
  825. this.scaling = false;
  826. this.coe = this.coe += 0.01;
  827. }, 50);
  828. }
  829. this.scaling = true;
  830. if (!this.checkoutImgAxis(this.x, this.y, scale)) {
  831. return false;
  832. }
  833. this.scale = scale;
  834. },
  835. // 修改图片大小函数
  836. changeScale(num) {
  837. let scale = this.scale;
  838. num = num || 1;
  839. var coe = 20;
  840. coe =
  841. coe / this.trueWidth > coe / this.trueHeight
  842. ? coe / this.trueHeight
  843. : coe / this.trueWidth;
  844. num = num * coe;
  845. num > 0
  846. ? (scale += Math.abs(num))
  847. : scale > Math.abs(num)
  848. ? (scale -= Math.abs(num))
  849. : scale;
  850. if (!this.checkoutImgAxis(this.x, this.y, scale)) {
  851. return false;
  852. }
  853. this.scale = scale;
  854. },
  855. // 创建截图框
  856. createCrop(e) {
  857. e.preventDefault();
  858. // 移动生成大小
  859. var nowX = 'clientX' in e ? e.clientX : e.touches ? e.touches[0].clientX : 0;
  860. var nowY = 'clientY' in e ? e.clientY : e.touches ? e.touches[0].clientY : 0;
  861. this.$nextTick(() => {
  862. var fw = nowX - this.cropX;
  863. var fh = nowY - this.cropY;
  864. if (fw > 0) {
  865. this.cropW =
  866. fw + this.cropChangeX > this.w ? this.w - this.cropChangeX : fw;
  867. this.cropOffsertX = this.cropChangeX;
  868. } else {
  869. this.cropW =
  870. this.w - this.cropChangeX + Math.abs(fw) > this.w
  871. ? this.cropChangeX
  872. : Math.abs(fw);
  873. this.cropOffsertX =
  874. this.cropChangeX + fw > 0 ? this.cropChangeX + fw : 0;
  875. }
  876. if (!this.fixed) {
  877. if (fh > 0) {
  878. this.cropH =
  879. fh + this.cropChangeY > this.h ? this.h - this.cropChangeY : fh;
  880. this.cropOffsertY = this.cropChangeY;
  881. } else {
  882. this.cropH =
  883. this.h - this.cropChangeY + Math.abs(fh) > this.h
  884. ? this.cropChangeY
  885. : Math.abs(fh);
  886. this.cropOffsertY =
  887. this.cropChangeY + fh > 0 ? this.cropChangeY + fh : 0;
  888. }
  889. } else {
  890. var fixedHeight =
  891. (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1];
  892. if (fixedHeight + this.cropOffsertY > this.h) {
  893. this.cropH = this.h - this.cropOffsertY;
  894. this.cropW =
  895. (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0];
  896. if (fw > 0) {
  897. this.cropOffsertX = this.cropChangeX;
  898. } else {
  899. this.cropOffsertX = this.cropChangeX - this.cropW;
  900. }
  901. } else {
  902. this.cropH = fixedHeight;
  903. }
  904. this.cropOffsertY = this.cropOffsertY;
  905. }
  906. });
  907. },
  908. // 改变截图框大小
  909. changeCropSize(e, w, h, typeW, typeH) {
  910. e.preventDefault();
  911. window.addEventListener("mousemove", this.changeCropNow);
  912. window.addEventListener("mouseup", this.changeCropEnd);
  913. window.addEventListener("touchmove", this.changeCropNow);
  914. window.addEventListener("touchend", this.changeCropEnd);
  915. this.canChangeX = w;
  916. this.canChangeY = h;
  917. this.changeCropTypeX = typeW;
  918. this.changeCropTypeY = typeH;
  919. this.cropX = 'clientX' in e ? e.clientX : e.touches[0].clientX;
  920. this.cropY = 'clientY' in e ? e.clientY : e.touches[0].clientY;
  921. this.cropOldW = this.cropW;
  922. this.cropOldH = this.cropH;
  923. this.cropChangeX = this.cropOffsertX;
  924. this.cropChangeY = this.cropOffsertY;
  925. if (this.fixed) {
  926. if (this.canChangeX && this.canChangeY) {
  927. this.canChangeY = 0;
  928. }
  929. }
  930. this.$emit('change-crop-size', {
  931. width: this.cropW,
  932. height: this.cropH
  933. })
  934. },
  935. // 正在改变
  936. changeCropNow(e) {
  937. e.preventDefault();
  938. var nowX = 'clientX' in e ? e.clientX : e.touches ? e.touches[0].clientX : 0;
  939. var nowY = 'clientY' in e ? e.clientY : e.touches ? e.touches[0].clientY : 0;
  940. // 容器的宽高
  941. let wrapperW = this.w;
  942. let wrapperH = this.h;
  943. // 不能超过的坐标轴
  944. let minX = 0;
  945. let minY = 0;
  946. if (this.centerBox) {
  947. let axis = this.getImgAxis();
  948. let imgW = axis.x2;
  949. let imgH = axis.y2;
  950. minX = axis.x1 > 0 ? axis.x1 : 0;
  951. minY = axis.y1 > 0 ? axis.y1 : 0;
  952. if (wrapperW > imgW) {
  953. wrapperW = imgW;
  954. }
  955. if (wrapperH > imgH) {
  956. wrapperH = imgH;
  957. }
  958. }
  959. const [minCropW, minCropH] = this.checkCropLimitSize()
  960. this.$nextTick(() => {
  961. var fw = nowX - this.cropX;
  962. var fh = nowY - this.cropY;
  963. if (this.canChangeX) {
  964. if (this.changeCropTypeX === 1) {
  965. if (this.cropOldW - fw < minCropW) {
  966. this.cropW = minCropW
  967. this.cropOffsertX = this.cropOldW + this.cropChangeX - minX - minCropW
  968. } else if (this.cropOldW - fw > 0) {
  969. this.cropW =
  970. wrapperW - this.cropChangeX - fw <= wrapperW - minX
  971. ? this.cropOldW - fw
  972. : this.cropOldW + this.cropChangeX - minX;
  973. this.cropOffsertX =
  974. wrapperW - this.cropChangeX - fw <= wrapperW - minX
  975. ? this.cropChangeX + fw
  976. : minX;
  977. } else {
  978. this.cropW =
  979. Math.abs(fw) + this.cropChangeX <= wrapperW
  980. ? Math.abs(fw) - this.cropOldW
  981. : wrapperW - this.cropOldW - this.cropChangeX;
  982. this.cropOffsertX = this.cropChangeX + this.cropOldW;
  983. }
  984. } else if (this.changeCropTypeX === 2) {
  985. if (this.cropOldW + fw < minCropW) {
  986. this.cropW = minCropW
  987. } else if (this.cropOldW + fw > 0) {
  988. this.cropW =
  989. this.cropOldW + fw + this.cropOffsertX <= wrapperW
  990. ? this.cropOldW + fw
  991. : wrapperW - this.cropOffsertX;
  992. this.cropOffsertX = this.cropChangeX;
  993. } else {
  994. // 右侧坐标抽 超过左侧
  995. this.cropW =
  996. wrapperW - this.cropChangeX + Math.abs(fw + this.cropOldW) <=
  997. wrapperW - minX
  998. ? Math.abs(fw + this.cropOldW)
  999. : this.cropChangeX - minX;
  1000. this.cropOffsertX =
  1001. wrapperW - this.cropChangeX + Math.abs(fw + this.cropOldW) <=
  1002. wrapperW - minX
  1003. ? this.cropChangeX - Math.abs(fw + this.cropOldW)
  1004. : minX;
  1005. }
  1006. }
  1007. }
  1008. if (this.canChangeY) {
  1009. if (this.changeCropTypeY === 1) {
  1010. if (this.cropOldH - fh < minCropH) {
  1011. this.cropH = minCropH
  1012. this.cropOffsertY = this.cropOldH + this.cropChangeY - minY - minCropH
  1013. } else if (this.cropOldH - fh > 0) {
  1014. this.cropH =
  1015. wrapperH - this.cropChangeY - fh <= wrapperH - minY
  1016. ? this.cropOldH - fh
  1017. : this.cropOldH + this.cropChangeY - minY;
  1018. this.cropOffsertY =
  1019. wrapperH - this.cropChangeY - fh <= wrapperH - minY
  1020. ? this.cropChangeY + fh
  1021. : minY;
  1022. } else {
  1023. this.cropH =
  1024. Math.abs(fh) + this.cropChangeY <= wrapperH
  1025. ? Math.abs(fh) - this.cropOldH
  1026. : wrapperH - this.cropOldH - this.cropChangeY;
  1027. this.cropOffsertY = this.cropChangeY + this.cropOldH;
  1028. }
  1029. } else if (this.changeCropTypeY === 2) {
  1030. if (this.cropOldH + fh < minCropH) {
  1031. this.cropH = minCropH
  1032. } else if (this.cropOldH + fh > 0) {
  1033. this.cropH =
  1034. this.cropOldH + fh + this.cropOffsertY <= wrapperH
  1035. ? this.cropOldH + fh
  1036. : wrapperH - this.cropOffsertY;
  1037. this.cropOffsertY = this.cropChangeY;
  1038. } else {
  1039. this.cropH =
  1040. wrapperH - this.cropChangeY + Math.abs(fh + this.cropOldH) <=
  1041. wrapperH - minY
  1042. ? Math.abs(fh + this.cropOldH)
  1043. : this.cropChangeY - minY;
  1044. this.cropOffsertY =
  1045. wrapperH - this.cropChangeY + Math.abs(fh + this.cropOldH) <=
  1046. wrapperH - minY
  1047. ? this.cropChangeY - Math.abs(fh + this.cropOldH)
  1048. : minY;
  1049. }
  1050. }
  1051. }
  1052. if (this.canChangeX && this.fixed) {
  1053. var fixedHeight =
  1054. (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1];
  1055. if (fixedHeight < minCropH) {
  1056. this.cropH = minCropH
  1057. this.cropW = this.fixedNumber[0] * minCropH / this.fixedNumber[1]
  1058. // 这里需要去修改 offsetX的值,去调整因为高度变化而导致的宽度变化
  1059. if (this.changeCropTypeX === 1) {
  1060. this.cropOffsertX = this.cropChangeX + (this.cropOldW - this.cropW)
  1061. }
  1062. } else if (fixedHeight + this.cropOffsertY > wrapperH) {
  1063. this.cropH = wrapperH - this.cropOffsertY;
  1064. this.cropW =
  1065. (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0];
  1066. if (this.changeCropTypeX === 1) {
  1067. this.cropOffsertX = this.cropChangeX + (this.cropOldW - this.cropW)
  1068. }
  1069. } else {
  1070. this.cropH = fixedHeight;
  1071. }
  1072. }
  1073. if (this.canChangeY && this.fixed) {
  1074. var fixedWidth =
  1075. (this.cropH / this.fixedNumber[1]) * this.fixedNumber[0];
  1076. if (fixedWidth < minCropW) {
  1077. this.cropW = minCropW
  1078. this.cropH = this.fixedNumber[1] * minCropW / this.fixedNumber[0];
  1079. this.cropOffsertY = this.cropOldH + this.cropChangeY - this.cropH
  1080. } else if (fixedWidth + this.cropOffsertX > wrapperW) {
  1081. this.cropW = wrapperW - this.cropOffsertX;
  1082. this.cropH =
  1083. (this.cropW / this.fixedNumber[0]) * this.fixedNumber[1];
  1084. } else {
  1085. this.cropW = fixedWidth;
  1086. }
  1087. }
  1088. });
  1089. },
  1090. checkCropLimitSize () {
  1091. let { cropW, cropH, limitMinSize } = this;
  1092. let limitMinNum = new Array;
  1093. if (!Array.isArray(limitMinSize)) {
  1094. limitMinNum = [limitMinSize, limitMinSize]
  1095. } else {
  1096. limitMinNum = limitMinSize
  1097. }
  1098. //限制最小宽度和高度
  1099. cropW = parseFloat(limitMinNum[0])
  1100. cropH = parseFloat(limitMinNum[1])
  1101. return [cropW, cropH]
  1102. },
  1103. // 结束改变
  1104. changeCropEnd(e) {
  1105. window.removeEventListener("mousemove", this.changeCropNow);
  1106. window.removeEventListener("mouseup", this.changeCropEnd);
  1107. window.removeEventListener("touchmove", this.changeCropNow);
  1108. window.removeEventListener("touchend", this.changeCropEnd);
  1109. },
  1110. // 根据比例x/y,最小宽度,最小高度,现有宽度,现有高度,得到应该有的宽度和高度
  1111. calculateSize(x, y, minX, minY, w, h) {
  1112. const ratio = x / y;
  1113. let width = w;
  1114. let height = h;
  1115. // 先根据最小宽度来计算高度
  1116. if (width < minX) {
  1117. width = minX;
  1118. height = Math.ceil(width / ratio);
  1119. }
  1120. // 如果计算出来的高度小于最小高度,则根据最小高度来重新计算宽度和高度
  1121. if (height < minY) {
  1122. height = minY;
  1123. width = Math.ceil(height * ratio);
  1124. // 如果重新计算的宽度仍然小于最小宽度,则使用最小宽度,并重新计算高度
  1125. if (width < minX) {
  1126. width = minX;
  1127. height = Math.ceil(width / ratio);
  1128. }
  1129. }
  1130. // 如果计算出来的宽度或高度小于输入的宽度或高度,则分别使用输入的宽度或高度
  1131. if (width < w) {
  1132. width = w;
  1133. height = Math.ceil(width / ratio);
  1134. }
  1135. if (height < h) {
  1136. height = h;
  1137. width = Math.ceil(height * ratio);
  1138. }
  1139. return { width, height };
  1140. },
  1141. // 创建完成
  1142. endCrop() {
  1143. if (this.cropW === 0 && this.cropH === 0) {
  1144. this.cropping = false;
  1145. }
  1146. let [minCropW, minCropH] = this.checkCropLimitSize();
  1147. const { width, height } = this.fixed ? this.calculateSize(
  1148. this.fixedNumber[0],
  1149. this.fixedNumber[1],
  1150. minCropW,
  1151. minCropH,
  1152. this.cropW,
  1153. this.cropH
  1154. ) : { width: minCropW, height: minCropH }
  1155. if (width > this.cropW) {
  1156. this.cropW = width;
  1157. if (this.cropOffsertX + width > this.w) {
  1158. this.cropOffsertX = this.w - width;
  1159. }
  1160. }
  1161. if (height > this.cropH) {
  1162. this.cropH = height;
  1163. if (this.cropOffsertY + height > this.h) {
  1164. this.cropOffsertY = this.h - height;
  1165. }
  1166. }
  1167. window.removeEventListener("mousemove", this.createCrop);
  1168. window.removeEventListener("mouseup", this.endCrop);
  1169. window.removeEventListener("touchmove", this.createCrop);
  1170. window.removeEventListener("touchend", this.endCrop);
  1171. },
  1172. // 开始截图
  1173. startCrop() {
  1174. this.crop = true;
  1175. },
  1176. // 停止截图
  1177. stopCrop() {
  1178. this.crop = false;
  1179. },
  1180. // 清除截图
  1181. clearCrop() {
  1182. this.cropping = false;
  1183. this.cropW = 0;
  1184. this.cropH = 0;
  1185. },
  1186. // 截图移动
  1187. cropMove(e) {
  1188. e.preventDefault();
  1189. if (!this.canMoveBox) {
  1190. this.crop = false;
  1191. this.startMove(e);
  1192. return false;
  1193. }
  1194. if (e.touches && e.touches.length === 2) {
  1195. this.crop = false;
  1196. this.startMove(e);
  1197. this.leaveCrop();
  1198. return false;
  1199. }
  1200. window.addEventListener("mousemove", this.moveCrop);
  1201. window.addEventListener("mouseup", this.leaveCrop);
  1202. window.addEventListener("touchmove", this.moveCrop);
  1203. window.addEventListener("touchend", this.leaveCrop);
  1204. let x = 'clientX' in e ? e.clientX : e.touches[0].clientX;
  1205. let y = 'clientY' in e ? e.clientY : e.touches[0].clientY;
  1206. let newX, newY;
  1207. newX = x - this.cropOffsertX;
  1208. newY = y - this.cropOffsertY;
  1209. this.cropX = newX;
  1210. this.cropY = newY;
  1211. // 触发截图框移动事件
  1212. this.$emit("crop-moving", {
  1213. moving: true,
  1214. axis: this.getCropAxis()
  1215. });
  1216. },
  1217. moveCrop(e, isMove) {
  1218. let nowX = 0;
  1219. let nowY = 0;
  1220. if (e) {
  1221. e.preventDefault();
  1222. nowX = 'clientX' in e ? e.clientX : e.touches[0].clientX;
  1223. nowY = 'clientY' in e ? e.clientY : e.touches[0].clientY;
  1224. }
  1225. this.$nextTick(() => {
  1226. let cx, cy;
  1227. let fw = nowX - this.cropX;
  1228. let fh = nowY - this.cropY;
  1229. if (isMove) {
  1230. fw = this.cropOffsertX;
  1231. fh = this.cropOffsertY;
  1232. }
  1233. // 不能超过外层容器
  1234. if (fw <= 0) {
  1235. cx = 0;
  1236. } else if (fw + this.cropW > this.w) {
  1237. cx = this.w - this.cropW;
  1238. } else {
  1239. cx = fw;
  1240. }
  1241. if (fh <= 0) {
  1242. cy = 0;
  1243. } else if (fh + this.cropH > this.h) {
  1244. cy = this.h - this.cropH;
  1245. } else {
  1246. cy = fh;
  1247. }
  1248. // 不能超过图片
  1249. if (this.centerBox) {
  1250. let axis = this.getImgAxis();
  1251. // 横坐标判断
  1252. if (cx <= axis.x1) {
  1253. cx = axis.x1;
  1254. }
  1255. if (cx + this.cropW > axis.x2) {
  1256. cx = axis.x2 - this.cropW;
  1257. }
  1258. // 纵坐标纵轴
  1259. if (cy <= axis.y1) {
  1260. cy = axis.y1;
  1261. }
  1262. if (cy + this.cropH > axis.y2) {
  1263. cy = axis.y2 - this.cropH;
  1264. }
  1265. }
  1266. this.cropOffsertX = cx;
  1267. this.cropOffsertY = cy;
  1268. // 触发截图框移动事件
  1269. this.$emit("crop-moving", {
  1270. moving: true,
  1271. axis: this.getCropAxis()
  1272. });
  1273. });
  1274. },
  1275. // 算出不同场景下面 图片相对于外层容器的坐标轴
  1276. getImgAxis(x, y, scale) {
  1277. x = x || this.x;
  1278. y = y || this.y;
  1279. scale = scale || this.scale;
  1280. // 如果设置了截图框在图片内, 那么限制截图框不能超过图片的坐标
  1281. // 图片的坐标
  1282. let obj = {
  1283. x1: 0,
  1284. x2: 0,
  1285. y1: 0,
  1286. y2: 0
  1287. };
  1288. let imgW = this.trueWidth * scale;
  1289. let imgH = this.trueHeight * scale;
  1290. switch (this.rotate) {
  1291. case 0:
  1292. obj.x1 = x + (this.trueWidth * (1 - scale)) / 2;
  1293. obj.x2 = obj.x1 + this.trueWidth * scale;
  1294. obj.y1 = y + (this.trueHeight * (1 - scale)) / 2;
  1295. obj.y2 = obj.y1 + this.trueHeight * scale;
  1296. break;
  1297. case 1:
  1298. case -1:
  1299. case 3:
  1300. case -3:
  1301. obj.x1 = x + (this.trueWidth * (1 - scale)) / 2 + (imgW - imgH) / 2;
  1302. obj.x2 = obj.x1 + this.trueHeight * scale;
  1303. obj.y1 = y + (this.trueHeight * (1 - scale)) / 2 + (imgH - imgW) / 2;
  1304. obj.y2 = obj.y1 + this.trueWidth * scale;
  1305. break;
  1306. default:
  1307. obj.x1 = x + (this.trueWidth * (1 - scale)) / 2;
  1308. obj.x2 = obj.x1 + this.trueWidth * scale;
  1309. obj.y1 = y + (this.trueHeight * (1 - scale)) / 2;
  1310. obj.y2 = obj.y1 + this.trueHeight * scale;
  1311. break;
  1312. }
  1313. return obj;
  1314. },
  1315. // 获取截图框的坐标轴
  1316. getCropAxis() {
  1317. let obj = {
  1318. x1: 0,
  1319. x2: 0,
  1320. y1: 0,
  1321. y2: 0
  1322. };
  1323. obj.x1 = this.cropOffsertX;
  1324. obj.x2 = obj.x1 + this.cropW;
  1325. obj.y1 = this.cropOffsertY;
  1326. obj.y2 = obj.y1 + this.cropH;
  1327. return obj;
  1328. },
  1329. leaveCrop(e) {
  1330. window.removeEventListener("mousemove", this.moveCrop);
  1331. window.removeEventListener("mouseup", this.leaveCrop);
  1332. window.removeEventListener("touchmove", this.moveCrop);
  1333. window.removeEventListener("touchend", this.leaveCrop);
  1334. // 触发截图框移动事件
  1335. this.$emit("crop-moving", {
  1336. moving: false,
  1337. axis: this.getCropAxis()
  1338. });
  1339. },
  1340. getCropChecked(cb) {
  1341. let canvas = document.createElement("canvas");
  1342. let img = new Image();
  1343. let rotate = this.rotate;
  1344. let trueWidth = this.trueWidth;
  1345. let trueHeight = this.trueHeight;
  1346. let cropOffsertX = this.cropOffsertX;
  1347. let cropOffsertY = this.cropOffsertY;
  1348. img.onload = () => {
  1349. if (this.cropW !== 0) {
  1350. let ctx = canvas.getContext("2d");
  1351. let dpr = 1;
  1352. if (this.high & !this.full) {
  1353. dpr = window.devicePixelRatio;
  1354. }
  1355. if ((this.enlarge !== 1) & !this.full) {
  1356. dpr = Math.abs(Number(this.enlarge));
  1357. }
  1358. let width = this.cropW * dpr;
  1359. let height = this.cropH * dpr;
  1360. let imgW = trueWidth * this.scale * dpr;
  1361. let imgH = trueHeight * this.scale * dpr;
  1362. // 图片x轴偏移
  1363. let dx =
  1364. (this.x - cropOffsertX + (this.trueWidth * (1 - this.scale)) / 2) *
  1365. dpr;
  1366. // 图片y轴偏移
  1367. let dy =
  1368. (this.y - cropOffsertY + (this.trueHeight * (1 - this.scale)) / 2) *
  1369. dpr;
  1370. //保存状态
  1371. setCanvasSize(width, height);
  1372. ctx.save();
  1373. switch (rotate) {
  1374. case 0:
  1375. if (!this.full) {
  1376. ctx.drawImage(img, dx, dy, imgW, imgH);
  1377. } else {
  1378. // 输出原图比例截图
  1379. setCanvasSize(width / this.scale, height / this.scale);
  1380. ctx.drawImage(
  1381. img,
  1382. dx / this.scale,
  1383. dy / this.scale,
  1384. imgW / this.scale,
  1385. imgH / this.scale
  1386. );
  1387. }
  1388. break;
  1389. case 1:
  1390. case -3:
  1391. if (!this.full) {
  1392. // 换算图片旋转后的坐标弥补
  1393. dx = dx + (imgW - imgH) / 2;
  1394. dy = dy + (imgH - imgW) / 2;
  1395. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1396. ctx.drawImage(img, dy, -dx - imgH, imgW, imgH);
  1397. } else {
  1398. setCanvasSize(width / this.scale, height / this.scale);
  1399. // 换算图片旋转后的坐标弥补
  1400. dx =
  1401. dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2;
  1402. dy =
  1403. dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2;
  1404. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1405. ctx.drawImage(
  1406. img,
  1407. dy,
  1408. -dx - imgH / this.scale,
  1409. imgW / this.scale,
  1410. imgH / this.scale
  1411. );
  1412. }
  1413. break;
  1414. case 2:
  1415. case -2:
  1416. if (!this.full) {
  1417. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1418. ctx.drawImage(img, -dx - imgW, -dy - imgH, imgW, imgH);
  1419. } else {
  1420. setCanvasSize(width / this.scale, height / this.scale);
  1421. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1422. dx = dx / this.scale;
  1423. dy = dy / this.scale;
  1424. ctx.drawImage(
  1425. img,
  1426. -dx - imgW / this.scale,
  1427. -dy - imgH / this.scale,
  1428. imgW / this.scale,
  1429. imgH / this.scale
  1430. );
  1431. }
  1432. break;
  1433. case 3:
  1434. case -1:
  1435. if (!this.full) {
  1436. // 换算图片旋转后的坐标弥补
  1437. dx = dx + (imgW - imgH) / 2;
  1438. dy = dy + (imgH - imgW) / 2;
  1439. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1440. ctx.drawImage(img, -dy - imgW, dx, imgW, imgH);
  1441. } else {
  1442. setCanvasSize(width / this.scale, height / this.scale);
  1443. // 换算图片旋转后的坐标弥补
  1444. dx =
  1445. dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2;
  1446. dy =
  1447. dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2;
  1448. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1449. ctx.drawImage(
  1450. img,
  1451. -dy - imgW / this.scale,
  1452. dx,
  1453. imgW / this.scale,
  1454. imgH / this.scale
  1455. );
  1456. }
  1457. break;
  1458. default:
  1459. if (!this.full) {
  1460. ctx.drawImage(img, dx, dy, imgW, imgH);
  1461. } else {
  1462. // 输出原图比例截图
  1463. setCanvasSize(width / this.scale, height / this.scale);
  1464. ctx.drawImage(
  1465. img,
  1466. dx / this.scale,
  1467. dy / this.scale,
  1468. imgW / this.scale,
  1469. imgH / this.scale
  1470. );
  1471. }
  1472. }
  1473. ctx.restore();
  1474. } else {
  1475. let width = trueWidth * this.scale;
  1476. let height = trueHeight * this.scale;
  1477. let ctx = canvas.getContext("2d");
  1478. ctx.save();
  1479. switch (rotate) {
  1480. case 0:
  1481. setCanvasSize(width, height);
  1482. ctx.drawImage(img, 0, 0, width, height);
  1483. break;
  1484. case 1:
  1485. case -3:
  1486. // 旋转90度 或者-270度 宽度和高度对调
  1487. setCanvasSize(height, width);
  1488. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1489. ctx.drawImage(img, 0, -height, width, height);
  1490. break;
  1491. case 2:
  1492. case -2:
  1493. setCanvasSize(width, height);
  1494. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1495. ctx.drawImage(img, -width, -height, width, height);
  1496. break;
  1497. case 3:
  1498. case -1:
  1499. setCanvasSize(height, width);
  1500. ctx.rotate((rotate * 90 * Math.PI) / 180);
  1501. ctx.drawImage(img, -width, 0, width, height);
  1502. break;
  1503. default:
  1504. setCanvasSize(width, height);
  1505. ctx.drawImage(img, 0, 0, width, height);
  1506. }
  1507. ctx.restore();
  1508. }
  1509. cb(canvas);
  1510. };
  1511. // 判断图片是否是base64
  1512. var s = this.img.substr(0, 4);
  1513. if (s !== "data") {
  1514. img.crossOrigin = "Anonymous";
  1515. }
  1516. img.src = this.imgs;
  1517. function setCanvasSize(width, height) {
  1518. canvas.width = Math.round(width);
  1519. canvas.height = Math.round(height);
  1520. }
  1521. },
  1522. // 获取转换成base64 的图片信息
  1523. getCropData(cb) {
  1524. this.getCropChecked(data => {
  1525. cb(data.toDataURL("image/" + this.outputType, this.outputSize));
  1526. });
  1527. },
  1528. //canvas获取为blob对象
  1529. getCropBlob(cb) {
  1530. this.getCropChecked(data => {
  1531. data.toBlob(
  1532. blob => cb(blob),
  1533. "image/" + this.outputType,
  1534. this.outputSize
  1535. );
  1536. });
  1537. },
  1538. // 自动预览函数
  1539. showPreview() {
  1540. // 优化不要多次触发
  1541. if (this.isCanShow) {
  1542. this.isCanShow = false;
  1543. setTimeout(() => {
  1544. this.isCanShow = true;
  1545. }, 16);
  1546. } else {
  1547. return false;
  1548. }
  1549. let w = this.cropW;
  1550. let h = this.cropH;
  1551. let scale = this.scale;
  1552. var obj = {};
  1553. obj.div = {
  1554. width: `${w}px`,
  1555. height: `${h}px`
  1556. };
  1557. let transformX = (this.x - this.cropOffsertX) / scale;
  1558. let transformY = (this.y - this.cropOffsertY) / scale;
  1559. let transformZ = 0;
  1560. obj.w = w;
  1561. obj.h = h;
  1562. obj.url = this.imgs;
  1563. obj.img = {
  1564. width: `${this.trueWidth}px`,
  1565. height: `${this.trueHeight}px`,
  1566. transform: `scale(${scale})translate3d(${transformX}px, ${transformY}px, ${transformZ}px)rotateZ(${this
  1567. .rotate * 90}deg)`
  1568. };
  1569. obj.html = `
  1570. <div class="show-preview" style="width: ${obj.w}px; height: ${
  1571. obj.h
  1572. }px,; overflow: hidden">
  1573. <div style="width: ${w}px; height: ${h}px">
  1574. <img src=${obj.url} style="width: ${this.trueWidth}px; height: ${
  1575. this.trueHeight
  1576. }px; transform:
  1577. scale(${scale})translate3d(${transformX}px, ${transformY}px, ${transformZ}px)rotateZ(${this
  1578. .rotate * 90}deg)">
  1579. </div>
  1580. </div>`;
  1581. this.$emit("real-time", obj);
  1582. },
  1583. // reload 图片布局函数
  1584. reload() {
  1585. let img = new Image();
  1586. img.onload = () => {
  1587. // 读取图片的信息原始信息, 解析是否需要旋转
  1588. // 读取图片的旋转信息
  1589. // 得到外层容器的宽度高度
  1590. this.w = parseFloat(window.getComputedStyle(this.$refs.cropper).width);
  1591. this.h = parseFloat(window.getComputedStyle(this.$refs.cropper).height);
  1592. // 存入图片真实高度
  1593. this.trueWidth = img.width;
  1594. this.trueHeight = img.height;
  1595. // 判断是否需要压缩大图
  1596. if (!this.original) {
  1597. // 判断布局方式 mode
  1598. this.scale = this.checkedMode();
  1599. } else {
  1600. this.scale = 1;
  1601. }
  1602. this.$nextTick(() => {
  1603. this.x =
  1604. -(this.trueWidth - this.trueWidth * this.scale) / 2 +
  1605. (this.w - this.trueWidth * this.scale) / 2;
  1606. this.y =
  1607. -(this.trueHeight - this.trueHeight * this.scale) / 2 +
  1608. (this.h - this.trueHeight * this.scale) / 2;
  1609. this.loading = false;
  1610. // // 获取是否开启了自动截图
  1611. if (this.autoCrop) {
  1612. this.goAutoCrop();
  1613. }
  1614. // 图片加载成功的回调
  1615. this.$emit("img-load", "success");
  1616. setTimeout(() => {
  1617. this.showPreview();
  1618. }, 20);
  1619. });
  1620. };
  1621. img.onerror = () => {
  1622. this.$emit("img-load", "error");
  1623. };
  1624. img.src = this.imgs;
  1625. },
  1626. // 背景布局的函数
  1627. checkedMode() {
  1628. let scale = 1;
  1629. // 通过字符串分割
  1630. let imgW = this.trueWidth;
  1631. let imgH = this.trueHeight;
  1632. const arr = this.mode.split(" ");
  1633. switch (arr[0]) {
  1634. case "contain":
  1635. if (this.trueWidth > this.w) {
  1636. // 如果图片宽度大于容器宽度
  1637. scale = this.w / this.trueWidth;
  1638. }
  1639. if (this.trueHeight * scale > this.h) {
  1640. scale = this.h / this.trueHeight;
  1641. }
  1642. break;
  1643. case "cover":
  1644. // 扩展布局 默认填充满整个容器
  1645. // 图片宽度大于容器
  1646. imgW = this.w;
  1647. scale = imgW / this.trueWidth;
  1648. imgH = imgH * scale;
  1649. // 如果扩展之后高度小于容器的外层高度 继续扩展高度
  1650. if (imgH < this.h) {
  1651. imgH = this.h;
  1652. scale = imgH / this.trueHeight;
  1653. }
  1654. break;
  1655. default:
  1656. try {
  1657. let str = arr[0];
  1658. if (str.search("px") !== -1) {
  1659. str = str.replace("px", "");
  1660. imgW = parseFloat(str);
  1661. const scaleX = imgW / this.trueWidth;
  1662. let scaleY = 1;
  1663. let strH = arr[1];
  1664. if (strH.search("px") !== -1) {
  1665. strH = strH.replace("px", "");
  1666. imgH = parseFloat(strH);
  1667. scaleY = imgH / this.trueHeight;
  1668. }
  1669. scale = Math.min(scaleX,scaleY)
  1670. }
  1671. if (str.search("%") !== -1) {
  1672. str = str.replace("%", "");
  1673. imgW = (parseFloat(str) / 100) * this.w;
  1674. scale = imgW / this.trueWidth;
  1675. }
  1676. if (arr.length === 2 && str === "auto") {
  1677. let str2 = arr[1];
  1678. if (str2.search("px") !== -1) {
  1679. str2 = str2.replace("px", "");
  1680. imgH = parseFloat(str2);
  1681. scale = imgH / this.trueHeight;
  1682. }
  1683. if (str2.search("%") !== -1) {
  1684. str2 = str2.replace("%", "");
  1685. imgH = (parseFloat(str2) / 100) * this.h;
  1686. scale = imgH / this.trueHeight;
  1687. }
  1688. }
  1689. } catch (error) {
  1690. scale = 1;
  1691. }
  1692. }
  1693. return scale;
  1694. },
  1695. // 自动截图函数
  1696. goAutoCrop(cw, ch) {
  1697. if (this.imgs === '' || this.imgs === null) return
  1698. this.clearCrop();
  1699. this.cropping = true;
  1700. let maxWidth = this.w;
  1701. let maxHeight = this.h;
  1702. if (this.centerBox) {
  1703. const switchWH = Math.abs(this.rotate) % 2 > 0
  1704. let imgW = (switchWH ? this.trueHeight : this.trueWidth) * this.scale;
  1705. let imgH = (switchWH ? this.trueWidth : this.trueHeight) * this.scale;
  1706. maxWidth = imgW < maxWidth ? imgW : maxWidth;
  1707. maxHeight = imgH < maxHeight ? imgH : maxHeight;
  1708. }
  1709. // 截图框默认大小
  1710. // 如果为0 那么计算容器大小 默认为80%
  1711. var w = cw ? cw : parseFloat(this.autoCropWidth);
  1712. var h = ch ? ch : parseFloat(this.autoCropHeight);
  1713. if (w === 0 || h === 0) {
  1714. w = maxWidth * 0.8;
  1715. h = maxHeight * 0.8;
  1716. }
  1717. w = w > maxWidth ? maxWidth : w;
  1718. h = h > maxHeight ? maxHeight : h;
  1719. if (this.fixed) {
  1720. h = (w / this.fixedNumber[0]) * this.fixedNumber[1];
  1721. }
  1722. // 如果比例之后 高度大于h
  1723. if (h > this.h) {
  1724. h = this.h;
  1725. w = (h / this.fixedNumber[1]) * this.fixedNumber[0];
  1726. }
  1727. this.changeCrop(w, h);
  1728. },
  1729. // 手动改变截图框大小函数
  1730. changeCrop(w, h) {
  1731. if (this.centerBox) {
  1732. // 修复初始化时候在centerBox=true情况下
  1733. let axis = this.getImgAxis();
  1734. if (w > axis.x2 - axis.x1) {
  1735. // 宽度超标
  1736. w = axis.x2 - axis.x1;
  1737. h = (w / this.fixedNumber[0]) * this.fixedNumber[1];
  1738. }
  1739. if (h > axis.y2 - axis.y1) {
  1740. // 高度超标
  1741. h = axis.y2 - axis.y1;
  1742. w = (h / this.fixedNumber[1]) * this.fixedNumber[0];
  1743. }
  1744. }
  1745. // 判断是否大于容器
  1746. this.cropW = w;
  1747. this.cropH = h;
  1748. this.checkCropLimitSize()
  1749. this.$nextTick(() => {
  1750. // 居中走一走
  1751. this.cropOffsertX = (this.w - this.cropW) / 2;
  1752. this.cropOffsertY = (this.h - this.cropH) / 2;
  1753. if (this.centerBox) {
  1754. this.moveCrop(null, true);
  1755. }
  1756. });
  1757. },
  1758. // 重置函数, 恢复组件置初始状态
  1759. refresh() {
  1760. let img = this.img;
  1761. this.imgs = "";
  1762. this.scale = 1;
  1763. this.crop = false;
  1764. this.rotate = 0;
  1765. this.w = 0;
  1766. this.h = 0;
  1767. this.trueWidth = 0;
  1768. this.trueHeight = 0;
  1769. this.clearCrop();
  1770. this.$nextTick(() => {
  1771. this.checkedImg();
  1772. });
  1773. },
  1774. // 向左边旋转
  1775. rotateLeft() {
  1776. this.rotate = this.rotate <= -3 ? 0 : this.rotate - 1;
  1777. },
  1778. // 向右边旋转
  1779. rotateRight() {
  1780. this.rotate = this.rotate >= 3 ? 0 : this.rotate + 1;
  1781. },
  1782. // 清除旋转
  1783. rotateClear() {
  1784. this.rotate = 0;
  1785. },
  1786. // 图片坐标点校验
  1787. checkoutImgAxis(x, y, scale) {
  1788. x = x || this.x;
  1789. y = y || this.y;
  1790. scale = scale || this.scale;
  1791. let canGo = true;
  1792. // 开始校验 如果说缩放之后的坐标在截图框外 则阻止缩放
  1793. if (this.centerBox) {
  1794. let axis = this.getImgAxis(x, y, scale);
  1795. let cropAxis = this.getCropAxis();
  1796. // 左边的横坐标 图片不能超过截图框
  1797. if (axis.x1 >= cropAxis.x1) {
  1798. canGo = false;
  1799. }
  1800. // 右边横坐标
  1801. if (axis.x2 <= cropAxis.x2) {
  1802. canGo = false;
  1803. }
  1804. // 纵坐标上面
  1805. if (axis.y1 >= cropAxis.y1) {
  1806. canGo = false;
  1807. }
  1808. // 纵坐标下面
  1809. if (axis.y2 <= cropAxis.y2) {
  1810. canGo = false;
  1811. }
  1812. }
  1813. return canGo;
  1814. }
  1815. },
  1816. mounted() {
  1817. this.support =
  1818. "onwheel" in document.createElement("div")
  1819. ? "wheel"
  1820. : document.onmousewheel !== undefined
  1821. ? "mousewheel"
  1822. : "DOMMouseScroll";
  1823. let that = this;
  1824. var u = navigator.userAgent;
  1825. this.isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
  1826. // 兼容blob
  1827. if (!HTMLCanvasElement.prototype.toBlob) {
  1828. Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
  1829. value: function(callback, type, quality) {
  1830. var binStr = atob(this.toDataURL(type, quality).split(",")[1]),
  1831. len = binStr.length,
  1832. arr = new Uint8Array(len);
  1833. for (var i = 0; i < len; i++) {
  1834. arr[i] = binStr.charCodeAt(i);
  1835. }
  1836. callback(new Blob([arr], { type: that.type || "image/png" }));
  1837. }
  1838. });
  1839. }
  1840. this.showPreview();
  1841. this.checkedImg();
  1842. },
  1843. unmounted() {
  1844. window.removeEventListener("mousemove", this.moveCrop);
  1845. window.removeEventListener("mouseup", this.leaveCrop);
  1846. window.removeEventListener("touchmove", this.moveCrop);
  1847. window.removeEventListener("touchend", this.leaveCrop);
  1848. this.cancelScale()
  1849. }
  1850. });
  1851. </script>
  1852. <style scoped lang="css">
  1853. .vue-cropper {
  1854. position: relative;
  1855. width: 100%;
  1856. height: 100%;
  1857. box-sizing: border-box;
  1858. user-select: none;
  1859. -webkit-user-select: none;
  1860. -moz-user-select: none;
  1861. -ms-user-select: none;
  1862. direction: ltr;
  1863. touch-action: none;
  1864. text-align: left;
  1865. background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
  1866. }
  1867. .cropper-box,
  1868. .cropper-box-canvas,
  1869. .cropper-drag-box,
  1870. .cropper-crop-box,
  1871. .cropper-face {
  1872. position: absolute;
  1873. top: 0;
  1874. right: 0;
  1875. bottom: 0;
  1876. left: 0;
  1877. user-select: none;
  1878. }
  1879. .cropper-box-canvas img {
  1880. position: relative;
  1881. text-align: left;
  1882. user-select: none;
  1883. transform: none;
  1884. max-width: none;
  1885. max-height: none;
  1886. }
  1887. .cropper-box {
  1888. overflow: hidden;
  1889. }
  1890. .cropper-move {
  1891. cursor: move;
  1892. }
  1893. .cropper-crop {
  1894. cursor: crosshair;
  1895. }
  1896. .cropper-modal {
  1897. background: rgba(0, 0, 0, 0.5);
  1898. }
  1899. .cropper-crop-box {
  1900. /*border: 2px solid #39f;*/
  1901. }
  1902. .cropper-view-box {
  1903. display: block;
  1904. overflow: hidden;
  1905. width: 100%;
  1906. height: 100%;
  1907. outline: 1px solid #39f;
  1908. outline-color: rgba(51, 153, 255, 0.75);
  1909. user-select: none;
  1910. }
  1911. .cropper-view-box img {
  1912. user-select: none;
  1913. text-align: left;
  1914. max-width: none;
  1915. max-height: none;
  1916. }
  1917. .cropper-face {
  1918. top: 0;
  1919. left: 0;
  1920. background-color: #fff;
  1921. opacity: 0.1;
  1922. }
  1923. .crop-info {
  1924. position: absolute;
  1925. left: 0px;
  1926. min-width: 65px;
  1927. text-align: center;
  1928. color: white;
  1929. line-height: 20px;
  1930. background-color: rgba(0, 0, 0, 0.8);
  1931. font-size: 12px;
  1932. }
  1933. .crop-line {
  1934. position: absolute;
  1935. display: block;
  1936. width: 100%;
  1937. height: 100%;
  1938. opacity: 0.1;
  1939. }
  1940. .line-w {
  1941. top: -3px;
  1942. left: 0;
  1943. height: 5px;
  1944. cursor: n-resize;
  1945. }
  1946. .line-a {
  1947. top: 0;
  1948. left: -3px;
  1949. width: 5px;
  1950. cursor: w-resize;
  1951. }
  1952. .line-s {
  1953. bottom: -3px;
  1954. left: 0;
  1955. height: 5px;
  1956. cursor: s-resize;
  1957. }
  1958. .line-d {
  1959. top: 0;
  1960. right: -3px;
  1961. width: 5px;
  1962. cursor: e-resize;
  1963. }
  1964. .crop-point {
  1965. position: absolute;
  1966. width: 8px;
  1967. height: 8px;
  1968. opacity: 0.75;
  1969. background-color: #39f;
  1970. border-radius: 100%;
  1971. }
  1972. .point1 {
  1973. top: -4px;
  1974. left: -4px;
  1975. cursor: nw-resize;
  1976. }
  1977. .point2 {
  1978. top: -5px;
  1979. left: 50%;
  1980. margin-left: -3px;
  1981. cursor: n-resize;
  1982. }
  1983. .point3 {
  1984. top: -4px;
  1985. right: -4px;
  1986. cursor: ne-resize;
  1987. }
  1988. .point4 {
  1989. top: 50%;
  1990. left: -4px;
  1991. margin-top: -3px;
  1992. cursor: w-resize;
  1993. }
  1994. .point5 {
  1995. top: 50%;
  1996. right: -4px;
  1997. margin-top: -3px;
  1998. cursor: e-resize;
  1999. }
  2000. .point6 {
  2001. bottom: -5px;
  2002. left: -4px;
  2003. cursor: sw-resize;
  2004. }
  2005. .point7 {
  2006. bottom: -5px;
  2007. left: 50%;
  2008. margin-left: -3px;
  2009. cursor: s-resize;
  2010. }
  2011. .point8 {
  2012. bottom: -5px;
  2013. right: -4px;
  2014. cursor: se-resize;
  2015. }
  2016. @media screen and (max-width: 500px) {
  2017. .crop-point {
  2018. position: absolute;
  2019. width: 20px;
  2020. height: 20px;
  2021. opacity: 0.45;
  2022. background-color: #39f;
  2023. border-radius: 100%;
  2024. }
  2025. .point1 {
  2026. top: -10px;
  2027. left: -10px;
  2028. }
  2029. .point2,
  2030. .point4,
  2031. .point5,
  2032. .point7 {
  2033. display: none;
  2034. }
  2035. .point3 {
  2036. top: -10px;
  2037. right: -10px;
  2038. }
  2039. .point4 {
  2040. top: 0;
  2041. left: 0;
  2042. }
  2043. .point6 {
  2044. bottom: -10px;
  2045. left: -10px;
  2046. }
  2047. .point8 {
  2048. bottom: -10px;
  2049. right: -10px;
  2050. }
  2051. }
  2052. </style>