lxiaoxiao-cropper.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. <template name="cropper">
  2. <view>
  3. <image :src="imgSrc.imgSrc" class="my-avatar" mode="aspectFill" :style="imgStyle"></image>
  4. <!-- 上传图片 -->
  5. <canvas canvas-id="avatar-canvas" id="avatar-canvas" class="my-canvas" :style="{top: styleTop, height: cvsStyleHeight}"
  6. disable-scroll="false"></canvas>
  7. <!-- 截取边框 -->
  8. <canvas canvas-id="oper-canvas" id="oper-canvas" class="oper-canvas" :style="{top: styleTop, height: cvsStyleHeight}"
  9. disable-scroll="false" @touchstart="fStart" @touchmove="fMove" @touchend="fEnd"></canvas>
  10. <view class="oper-wrapper" :style="{display: styleDisplay}">
  11. <view class="btn-wrapper" v-if="showOper">
  12. <view @click="fClose" hover-class="hover">取消</view>
  13. <view @click="fUpload" hover-class="hover">选取</view>
  14. </view>
  15. </view>
  16. </view>
  17. </template>
  18. <script>
  19. const tabHeight = 70;
  20. export default {
  21. name: "cropper",
  22. data() {
  23. return {
  24. cvsStyleHeight: '0px',
  25. styleDisplay: 'none',
  26. styleTop: '-10000px',
  27. prvTop: '-10000px',
  28. imgStyle: {},
  29. selStyle: {},
  30. showOper: true,
  31. imgSrc: {
  32. imgSrc: ''
  33. },
  34. qlty: 0.9,
  35. postWidthFirst: {},
  36. };
  37. },
  38. watch: {
  39. avatarSrc() {
  40. this.imgSrc.imgSrc = this.avatarSrc;
  41. }
  42. },
  43. props: {
  44. avatarSrc: '',
  45. avatarStyle: '',
  46. selWidth: '',
  47. selHeight: '',
  48. expWidth: '',
  49. expHeight: '',
  50. minScale: '',
  51. maxScale: '',
  52. canScale: '',
  53. noTop: '',
  54. quality: '',
  55. index: ''
  56. },
  57. created() {
  58. this.ctxCanvas = uni.createCanvasContext('avatar-canvas', this);
  59. this.ctxCanvasOper = uni.createCanvasContext('oper-canvas', this);
  60. this.qlty = parseInt(this.quality) || 0.9;
  61. this.imgSrc.imgSrc = this.avatarSrc;
  62. this.letScale = this.canScale === 'false' ? 0 : 1;
  63. this.indx = this.index || undefined;
  64. this.mnScale = this.minScale || 0.3;
  65. this.mxScale = this.maxScale || 4;
  66. this.noBar = this.noTop ? false : true;
  67. if (!!this.noBar) {
  68. this.moreHeight = 0;
  69. this.fWindowResize();
  70. } else {
  71. uni.showTabBar({
  72. complete: (res) => {
  73. this.moreHeight = (res.errMsg === 'showTabBar:ok') ? 50 : 0;
  74. this.fWindowResize();
  75. }
  76. });
  77. }
  78. },
  79. methods: {
  80. fWindowResize() {
  81. let sysInfo = uni.getSystemInfoSync();
  82. this.platform = sysInfo.platform;
  83. this.pixelRatio = sysInfo.pixelRatio;
  84. this.windowWidth = sysInfo.windowWidth;
  85. // #ifdef H5
  86. this.drawTop = sysInfo.windowTop;
  87. this.windowHeight = sysInfo.windowHeight + sysInfo.windowBottom;
  88. this.cvsStyleHeight = this.windowHeight - tabHeight + 'px';
  89. // #endif
  90. // #ifdef MP-WEIXIN
  91. this.windowHeight = sysInfo.windowHeight;
  92. this.cvsStyleHeight = this.windowHeight - tabHeight + 'px';
  93. this.cvsStyleHeight = this.windowHeight - tabHeight + 'px'
  94. // #endif
  95. this.pxRatio = this.windowWidth / 750;
  96. let style = this.avatarStyle;
  97. this.imgStyle = style;
  98. this.expWidth && (this.exportWidth = this.expWidth.indexOf('rpx') >= 0 ? parseInt(this.expWidth) * this.pxRatio :
  99. parseInt(this.expWidth));
  100. this.expHeight && (this.exportHeight = this.expHeight.indexOf('rpx') >= 0 ? parseInt(this.expHeight) * this.pxRatio :
  101. parseInt(this.expHeight));
  102. if (this.styleDisplay === 'flex') {
  103. this.fDrawInit(true);
  104. }
  105. this.fHideImg();
  106. },
  107. fSelect() {
  108. let self=this;
  109. if (this.fSelecting) return;
  110. this.fSelecting = true;
  111. setTimeout(() => {
  112. this.fSelecting = false;
  113. }, 500);
  114. uni.chooseImage({
  115. count: 1,
  116. sizeType: ['original', 'compressed'],
  117. sourceType: ['camera', 'album'],
  118. success: (r) => {
  119. uni.showLoading({
  120. mask: true
  121. });
  122. let path = this.imgPath = r.tempFilePaths[0];
  123. uni.getImageInfo({
  124. src: path,
  125. success: r => {
  126. this.imgWidth = r.width;
  127. this.imgHeight = r.height;
  128. this.path = path;
  129. if (!this.hasSel) {
  130. let style = this.selStyle || {};
  131. if (this.selWidth && this.selHeight) {
  132. let selWidth = this.selWidth.indexOf('rpx') >= 0 ? parseInt(this.selWidth) * this.pxRatio :
  133. parseInt(this.selWidth),
  134. selHeight = this.selHeight.indexOf('rpx') >= 0 ? parseInt(this.selHeight) * this.pxRatio :
  135. parseInt(this.selHeight);
  136. style.width = parseInt(selWidth);
  137. style.height = parseInt(selHeight);
  138. style.top = parseInt((this.windowHeight - style.height - tabHeight) / 2);
  139. style.left = parseInt((this.windowWidth - style.width) / 2);
  140. } else {
  141. uni.showModal({
  142. title: '裁剪框的宽或高没有设置',
  143. showCancel: false
  144. })
  145. return;
  146. }
  147. this.selStyle = style;
  148. }
  149. if (!!self.noBar) {
  150. self.fDrawInit(true);
  151. } else {
  152. uni.hideTabBar({
  153. complete: () => {
  154. self.fDrawInit(true);
  155. }
  156. });
  157. }
  158. },
  159. fail: () => {
  160. uni.showToast({
  161. title: "error3",
  162. duration: 2000,
  163. })
  164. },
  165. complete() {
  166. uni.hideLoading();
  167. }
  168. });
  169. }
  170. })
  171. },
  172. fUpload() {
  173. if (this.fUploading) return;
  174. this.fUploading = true;
  175. setTimeout(() => {
  176. this.fUploading = false;
  177. }, 1000)
  178. let style = this.selStyle,
  179. x = parseInt(style.left),
  180. y = parseInt(style.top),
  181. width = parseInt(style.width),
  182. height = parseInt(style.height),
  183. expWidth = this.exportWidth || width,
  184. expHeight = this.exportHeight || height;
  185. this.styleDisplay = 'none';
  186. this.styleTop = '-10000px';
  187. this.hasSel = false;
  188. this.fHideImg();
  189. uni.canvasToTempFilePath({
  190. x: x,
  191. y: y,
  192. width: width,
  193. height: height,
  194. destWidth: expWidth,
  195. destHeight: expHeight,
  196. canvasId: 'avatar-canvas',
  197. fileType: 'png',
  198. quality: this.qlty,
  199. success: (r) => {
  200. console.log(r,'裁剪后的图片')
  201. this.$emit("uploadImg", r.tempFilePath);
  202. },
  203. fail: (res) => {
  204. uni.showToast({
  205. title: "error1",
  206. duration: 2000,
  207. })
  208. },
  209. complete: () => {
  210. this.noBar || uni.showTabBar();
  211. }
  212. }, this);
  213. },
  214. fDrawInit(ini = false) {
  215. let allWidth = this.windowWidth,
  216. allHeight = this.windowHeight,
  217. imgWidth = this.imgWidth,
  218. imgHeight = this.imgHeight,
  219. imgRadio = imgWidth / imgHeight,
  220. useWidth = allWidth,
  221. useHeight = allHeight - tabHeight,
  222. pixelRatio = this.pixelRatio,
  223. selWidth = parseInt(this.selStyle.width),
  224. selHeight = parseInt(this.selStyle.height);
  225. this.fixWidth = 0;
  226. this.fixHeight = 0;
  227. if (this.fixWidth) {
  228. useWidth = selWidth;
  229. useHeight = useWidth / imgRadio;
  230. } else if (this.fixHeight) {
  231. useHeight = selHeight;
  232. useWidth = useHeight * imgRadio;
  233. } else if (imgRadio < 1) {
  234. useWidth = selWidth;
  235. useHeight = parseInt(useWidth / imgRadio);
  236. } else {
  237. useHeight = selHeight;
  238. useWidth = parseInt(useHeight * imgRadio);
  239. }
  240. this.scaleSize = 1;
  241. this.rotateDeg = 0;
  242. this.posWidth = parseInt((allWidth - useWidth) / 2);
  243. this.posHeight = parseInt((allHeight - useHeight - tabHeight) / 2);
  244. this.useWidth = useWidth;
  245. this.useHeight = useHeight;
  246. let style = this.selStyle,
  247. left = parseInt(style.left),
  248. top = parseInt(style.top),
  249. width = parseInt(style.width),
  250. height = parseInt(style.height),
  251. canvas = this.canvas,
  252. canvasOper = this.canvasOper,
  253. ctxCanvas = this.ctxCanvas,
  254. ctxCanvasOper = this.ctxCanvasOper;
  255. ctxCanvasOper.setFillStyle('rgba(0,0,0, 0.5)');
  256. ctxCanvasOper.fillRect(0, 0, this.windowWidth, top);
  257. ctxCanvasOper.fillRect(0, top, left, height);
  258. ctxCanvasOper.fillRect(0, top + height, this.windowWidth, this.windowHeight - height - tabHeight - top);
  259. ctxCanvasOper.fillRect(left + width, top, this.windowWidth - width - left, height);
  260. ctxCanvasOper.setLineWidth(1);
  261. ctxCanvasOper.setStrokeStyle('rgba(255, 255, 255,1)'); //细线的颜色
  262. ctxCanvasOper.strokeRect(left, top, width, height);
  263. // #ifdef H5
  264. ctxCanvasOper.draw();
  265. // #endif
  266. ctxCanvasOper.setLineWidth(3);
  267. ctxCanvasOper.setStrokeStyle('rgba(255, 255, 255, 1)'); //粗线的颜色
  268. ctxCanvasOper.moveTo(left + 20, top);
  269. ctxCanvasOper.lineTo(left, top);
  270. ctxCanvasOper.lineTo(left, top + 20);
  271. ctxCanvasOper.moveTo(left + width - 20, top);
  272. ctxCanvasOper.lineTo(left + width, top);
  273. ctxCanvasOper.lineTo(left + width, top + 20);
  274. ctxCanvasOper.moveTo(left + 20, top + height);
  275. ctxCanvasOper.lineTo(left, top + height);
  276. ctxCanvasOper.lineTo(left, top + height - 20);
  277. ctxCanvasOper.moveTo(left + width - 20, top + height);
  278. ctxCanvasOper.lineTo(left + width, top + height);
  279. ctxCanvasOper.lineTo(left + width, top + height - 20);
  280. ctxCanvasOper.stroke();
  281. this.postFirst = {
  282. left: left,
  283. top: top,
  284. width: width,
  285. height: selWidth,
  286. posWidth: this.posWidth,
  287. posHeight: this.posHeight
  288. };
  289. // #ifdef MP-WEIXIN
  290. ctxCanvasOper.draw(false, () => {
  291. if (ini) {
  292. this.styleDisplay = 'flex';
  293. this.styleTop = '0';
  294. ctxCanvas.setFillStyle('black');
  295. this.fDrawImage();
  296. }
  297. });
  298. // #endif
  299. // #ifdef H5
  300. ctxCanvasOper.draw(true, () => {
  301. if (ini) {
  302. this.styleDisplay = 'flex';
  303. this.styleTop = this.drawTop + 'px';
  304. ctxCanvas.setFillStyle('black');
  305. this.fDrawImage();
  306. }
  307. });
  308. // #endif
  309. this.$emit("avtinit");
  310. },
  311. fDrawImage() {
  312. let tm_now = Date.now();
  313. if ((tm_now - this.drawTm )< 20) return;
  314. this.drawTm = tm_now;
  315. let ctxCanvas = this.ctxCanvas;
  316. ctxCanvas.fillRect(0, 0, this.windowWidth, this.windowHeight - tabHeight);
  317. //中心点坐标
  318. ctxCanvas.translate(this.posWidth + this.useWidth / 2, this.posHeight + this.useHeight / 2);
  319. //比例缩放
  320. ctxCanvas.scale(this.scaleSize, this.scaleSize);
  321. ctxCanvas.drawImage(this.imgPath, -this.useWidth / 2, -this.useHeight / 2, this.useWidth, this.useHeight);
  322. ctxCanvas.draw(false);
  323. },
  324. fHideImg() {
  325. this.prvImg = '';
  326. this.prvTop = '-10000px';
  327. this.showOper = true;
  328. this.prvImgData = null;
  329. this.target = null;
  330. },
  331. fClose() {
  332. this.styleDisplay = 'none';
  333. this.styleTop = '-10000px';
  334. this.hasSel = false;
  335. this.fHideImg();
  336. this.noBar || uni.showTabBar();
  337. },
  338. // #ifdef MP-WEIXIN
  339. fStart(e) {
  340. let touches = e.touches,
  341. touch0 = touches[0],
  342. touch1 = touches[1];
  343. this.touch0 = touch0;
  344. this.touch1 = touch1;
  345. if (touch1) {
  346. let x = touch1.x - touch0.x,
  347. y = touch1.y - touch0.y;
  348. this.fgDistance = Math.sqrt(x * x + y * y);
  349. }
  350. },
  351. // #endif
  352. // #ifdef H5
  353. fStart(e) {
  354. let touches = e.touches,
  355. touch0 = touches[0],
  356. touch1 = touches[1];
  357. this.touch0 = touch0;
  358. this.touch1 = touch1;
  359. if (touch1) {
  360. let x = touch1.clientX - touch0.clientX,
  361. y = touch1.clientY - touch0.clientY;
  362. this.fgDistance = Math.sqrt(x * x + y * y);
  363. }
  364. },
  365. // #endif
  366. // #ifdef MP-WEIXIN
  367. fMove(e) {
  368. let touches = e.touches,
  369. touch0 = touches[0],
  370. touch1 = touches[1];
  371. if (touch1) {
  372. let x = touch1.x - touch0.x,
  373. y = touch1.y - touch0.y,
  374. fgDistance = Math.sqrt(x * x + y * y),
  375. scaleSize = 0.005 * (fgDistance - this.fgDistance),
  376. beScaleSize = this.scaleSize + scaleSize;
  377. do {
  378. if (!this.letScale) break;
  379. if (beScaleSize < this.mnScale) break;
  380. if (beScaleSize > this.mxScale) break;
  381. this.scaleSize = beScaleSize;
  382. } while (0);
  383. this.fgDistance = fgDistance;
  384. if (touch1.x !== touch0.x && this.letRotate) {
  385. x = (this.touch1.y - this.touch0.y) / (this.touch1.x - this.touch0.x);
  386. y = (touch1.y - touch0.y) / (touch1.x - touch0.x);
  387. this.rotateDeg += Math.atan((y - x) / (1 + x * y)) * 180 / Math.PI;
  388. this.touch0 = touch0;
  389. this.touch1 = touch1;
  390. }
  391. this.fDrawImage();
  392. } else if (this.touch0) {
  393. let x = touch0.x - this.touch0.x,
  394. y = touch0.y - this.touch0.y,
  395. beX = this.posWidth + x,
  396. beY = this.posHeight + y;
  397. if (Math.abs(x) < 100 && !this.lckWidth) this.posWidth = beX;
  398. if (Math.abs(y) < 100 && !this.lckHeight) this.posHeight = beY;
  399. this.touch0 = touch0;
  400. this.fDrawImage();
  401. }
  402. },
  403. // #endif
  404. // #ifdef H5
  405. fMove(e) {
  406. let touches = e.touches,
  407. touch0 = touches[0],
  408. touch1 = touches[1];
  409. if (touch1) {
  410. let x = touch1.clientX - touch0.clientX,
  411. y = touch1.clientY - touch0.clientY,
  412. fgDistance = Math.sqrt(x * x + y * y),
  413. scaleSize = 0.005 * (fgDistance - this.fgDistance),
  414. beScaleSize = this.scaleSize + scaleSize;
  415. do {
  416. if (!this.letScale) break;
  417. if (beScaleSize < this.mnScale) break;
  418. if (beScaleSize > this.mxScale) break;
  419. this.scaleSize = beScaleSize;
  420. } while (0);
  421. this.fgDistance = fgDistance;
  422. if (touch1.x !== touch0.x && this.letRotate) {
  423. x = (this.touch1.clientY - this.touch0.clientY) / (this.touch1.clientX - this.touch0.clientX);
  424. y = (touch1.clientY - touch0.clientY) / (touch1.clientX - touch0.clientX);
  425. this.rotateDeg += Math.atan((y - x) / (1 + x * y)) * 180 / Math.PI;
  426. this.touch0 = touch0;
  427. this.touch1 = touch1;
  428. }
  429. this.fDrawImage();
  430. } else if (this.touch0) {
  431. let x=touch0.clientX - this.touch0.clientX,
  432. y=touch0.clientY - this.touch0.clientY,
  433. // let x = touch0.x - this.touch0.x,
  434. // y = touch0.y - this.touch0.y,
  435. beX = this.posWidth + x,
  436. beY = this.posHeight + y;
  437. if (Math.abs(x) < 100 && !this.lckWidth) this.posWidth = beX;
  438. if (Math.abs(y) < 100 && !this.lckHeight) this.posHeight = beY;
  439. this.touch0 = touch0;
  440. this.fDrawImage();
  441. }
  442. },
  443. // #endif
  444. async fEnd(e) {
  445. let self = this;
  446. let touches = e.touches,
  447. touch0 = touches && touches[0],
  448. touch1 = touches && touches[1];
  449. if (self.scaleSize < 1) {
  450. let style = self.selStyle;
  451. let imgRadio = self.imgWidth / self.imgHeight;
  452. //高长宽短
  453. if (imgRadio < 1 && self.scaleSize * self.useWidth < style.width) {
  454. self.posWidth = style.left;
  455. self.scaleSize = 1
  456. setTimeout(function() {
  457. self.fDrawImage();
  458. }, 100)
  459. } else if (self.scaleSize * self.useHeight < style.width) {
  460. //高短宽长
  461. self.posHeight = style.top;
  462. self.scaleSize = 1
  463. setTimeout(function() {
  464. self.fDrawImage();
  465. }, 100)
  466. }
  467. } else if (this.scaleSize == 1) {
  468. let endWidth = this.posWidth - this.postFirst.posWidth,
  469. firstWidth = this.postFirst.left - this.postFirst.posWidth;
  470. let endHeight = this.posHeight - this.postFirst.posHeight,
  471. firstHigth = this.postFirst.top - this.postFirst.posHeight;
  472. if (endWidth > 0 && this.posWidth > this.postFirst.left) {
  473. //右滑动过长
  474. this.posWidth = this.postFirst.left;
  475. } else if (endWidth < 0 && endWidth < firstWidth) {
  476. //左滑动过长
  477. this.posWidth = -this.postFirst.left + this.postFirst.posWidth * 2;
  478. }
  479. if (endHeight < 0 && this.posHeight < this.postFirst.top) {
  480. //上滑动过长
  481. this.posHeight = -this.postFirst.top + this.postFirst.posHeight * 2 ;
  482. } else if (endHeight > 0 && endHeight > firstHigth) {
  483. //下滑动过长
  484. this.posHeight = this.postFirst.top;
  485. }
  486. setTimeout(function() {
  487. self.fDrawImage();
  488. }, 100);
  489. }
  490. if (touch0) {
  491. this.touch0 = touch0;
  492. } else {
  493. this.touch0 = null;
  494. this.touch1 = null;
  495. }
  496. },
  497. btop(base64) {
  498. return new Promise(function(resolve, reject) {
  499. var arr = base64.split(','),
  500. mime = arr[0].match(/:(.*?);/)[1],
  501. bstr = atob(arr[1]),
  502. n = bstr.length,
  503. u8arr = new Uint8Array(n);
  504. while (n--) {
  505. u8arr[n] = bstr.charCodeAt(n);
  506. }
  507. return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([u8arr], {
  508. type: mime
  509. })));
  510. });
  511. },
  512. }
  513. }
  514. </script>
  515. <style>
  516. .my-canvas {
  517. display: flex;
  518. position: fixed !important;
  519. background: #000000;
  520. left: 0;
  521. z-index: 100;
  522. width: 100%;
  523. }
  524. .my-avatar {
  525. width: 100vw;
  526. height: 100vw;
  527. }
  528. .oper-canvas {
  529. display: flex;
  530. position: fixed !important;
  531. left: 0;
  532. z-index: 101;
  533. width: 100%;
  534. }
  535. .oper-wrapper {
  536. height: 71px;
  537. position: fixed !important;
  538. box-sizing: border-box;
  539. width: 100%;
  540. left: 0;
  541. bottom: 0;
  542. z-index: 200;
  543. flex-direction: row;
  544. }
  545. .btn-wrapper {
  546. background-color: #000000;
  547. color: #ffffff;
  548. display: flex;
  549. height: 100%;
  550. width: 100%;
  551. justify-content: space-around;
  552. align-items: center
  553. }
  554. .btn-wrapper view {
  555. width: 160rpx;
  556. height: 80rpx;
  557. line-height: 80rpx;
  558. text-align: center;
  559. font-size: 16px;
  560. color: #ffffff;
  561. z-index: 300;
  562. }
  563. .hover {
  564. color: #f1f1f1;
  565. }
  566. </style>