image-cropper.js 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  1. Component({
  2. properties: {
  3. /**
  4. * 图片路径
  5. */
  6. 'imgSrc': {
  7. type: String
  8. },
  9. /**
  10. * 裁剪框高度
  11. */
  12. 'height': {
  13. type: Number,
  14. value: 200
  15. },
  16. /**
  17. * 裁剪框宽度
  18. */
  19. 'width': {
  20. type: Number,
  21. value: 200
  22. },
  23. /**
  24. * 裁剪框最小尺寸
  25. */
  26. 'min_width': {
  27. type: Number,
  28. value: 100
  29. },
  30. 'min_height': {
  31. type: Number,
  32. value: 100
  33. },
  34. /**
  35. * 裁剪框最大尺寸
  36. */
  37. 'max_width': {
  38. type: Number,
  39. value: 300
  40. },
  41. 'max_height': {
  42. type: Number,
  43. value: 300
  44. },
  45. /**
  46. * 裁剪框禁止拖动
  47. */
  48. 'disable_width': {
  49. type: Boolean,
  50. value: true
  51. },
  52. 'disable_height': {
  53. type: Boolean,
  54. value: true
  55. },
  56. /**
  57. * 锁定裁剪框比例
  58. */
  59. 'disable_ratio': {
  60. type: Boolean,
  61. value: false
  62. },
  63. /**
  64. * 生成的图片尺寸相对剪裁框的比例
  65. */
  66. 'export_scale': {
  67. type: Number,
  68. value: 3
  69. },
  70. /**
  71. * 生成的图片质量0-1
  72. */
  73. 'quality': {
  74. type: Number,
  75. value: 1
  76. },
  77. 'cut_top': {
  78. type: Number,
  79. value: null
  80. },
  81. 'cut_left': {
  82. type: Number,
  83. value: null
  84. },
  85. /**
  86. * canvas上边距(不设置默认不显示)
  87. */
  88. 'canvas_top': {
  89. type: Number,
  90. value: null
  91. },
  92. /**
  93. * canvas左边距(不设置默认不显示)
  94. */
  95. 'canvas_left': {
  96. type: Number,
  97. value: null
  98. },
  99. /**
  100. * 图片宽度
  101. */
  102. 'img_width': {
  103. type: null,
  104. value: null
  105. },
  106. /**
  107. * 图片高度
  108. */
  109. 'img_height': {
  110. type: null,
  111. value: null
  112. },
  113. /**
  114. * 图片缩放比
  115. */
  116. 'scale': {
  117. type: Number,
  118. value: 1
  119. },
  120. /**
  121. * 图片旋转角度
  122. */
  123. 'angle': {
  124. type: Number,
  125. value: 0
  126. },
  127. /**
  128. * 最小缩放比
  129. */
  130. 'min_scale': {
  131. type: Number,
  132. value: 0.5
  133. },
  134. /**
  135. * 最大缩放比
  136. */
  137. 'max_scale': {
  138. type: Number,
  139. value: 2
  140. },
  141. /**
  142. * 是否禁用旋转
  143. */
  144. 'disable_rotate': {
  145. type: Boolean,
  146. value: true
  147. },
  148. /**
  149. * 是否限制移动范围(剪裁框只能在图片内)
  150. */
  151. 'limit_move': {
  152. type: Boolean,
  153. value: false
  154. }
  155. },
  156. data: {
  157. el: 'image-cropper', //暂时无用
  158. info: wx.getSystemInfoSync(),
  159. MOVE_THROTTLE: null, //触摸移动节流settimeout
  160. MOVE_THROTTLE_FLAG: true, //节流标识
  161. INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
  162. INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
  163. TIME_BG: null, //背景变暗延时函数
  164. TIME_CUT_CENTER: null,
  165. _touch_img_relative: [{
  166. x: 0,
  167. y: 0
  168. }], //鼠标和图片中心的相对位置
  169. _flag_cut_touch: false, //是否是拖动裁剪框
  170. _hypotenuse_length: 0, //双指触摸时斜边长度
  171. _flag_img_endtouch: false, //是否结束触摸
  172. _flag_bright: true, //背景是否亮
  173. _canvas_overflow: true, //canvas缩略图是否在屏幕外面
  174. _canvas_width: 200,
  175. _canvas_height: 200,
  176. origin_x: 0.5, //图片旋转中心
  177. origin_y: 0.5, //图片旋转中心
  178. _cut_animation: false, //是否开启图片和裁剪框过渡
  179. _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距
  180. _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距
  181. watch: {
  182. //监听截取框宽高变化
  183. width(value, that) {
  184. if (value < that.data.min_width) {
  185. that.setData({
  186. width: that.data.min_width
  187. });
  188. }
  189. that._computeCutSize();
  190. },
  191. height(value, that) {
  192. if (value < that.data.min_height) {
  193. that.setData({
  194. height: that.data.min_height
  195. });
  196. }
  197. that._computeCutSize();
  198. },
  199. angle(value, that) {
  200. //停止居中裁剪框,继续修改图片位置
  201. that._moveStop();
  202. if (that.data.limit_move) {
  203. if (that.data.angle % 90) {
  204. that.setData({
  205. angle: Math.round(that.data.angle / 90) * 90
  206. });
  207. return;
  208. }
  209. }
  210. },
  211. _cut_animation(value, that) {
  212. //开启过渡300毫秒之后自动关闭
  213. clearTimeout(that.data._cut_animation_time);
  214. if (value) {
  215. that.data._cut_animation_time = setTimeout(() => {
  216. that.setData({
  217. _cut_animation: false
  218. });
  219. }, 300)
  220. }
  221. },
  222. limit_move(value, that) {
  223. if (value) {
  224. if (that.data.angle % 90) {
  225. that.setData({
  226. angle: Math.round(that.data.angle / 90) * 90
  227. });
  228. }
  229. that._imgMarginDetectionScale();
  230. !that.data._canvas_overflow && that._draw();
  231. }
  232. },
  233. canvas_top(value, that) {
  234. that._canvasDetectionPosition();
  235. },
  236. canvas_left(value, that) {
  237. that._canvasDetectionPosition();
  238. },
  239. imgSrc(value, that) {
  240. that.pushImg();
  241. },
  242. cut_top(value, that) {
  243. that._cutDetectionPosition();
  244. if (that.data.limit_move) {
  245. !that.data._canvas_overflow && that._draw();
  246. }
  247. },
  248. cut_left(value, that) {
  249. that._cutDetectionPosition();
  250. if (that.data.limit_move) {
  251. !that.data._canvas_overflow && that._draw();
  252. }
  253. }
  254. }
  255. },
  256. attached() {
  257. this.data.info = wx.getSystemInfoSync();
  258. //启用数据监听
  259. this._watcher();
  260. this.data.INIT_IMGWIDTH = this.data.img_width;
  261. this.data.INIT_IMGHEIGHT = this.data.img_height;
  262. this.setData({
  263. _canvas_height: this.data.height,
  264. _canvas_width: this.data.width,
  265. });
  266. this._initCanvas();
  267. this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc);
  268. //根据开发者设置的图片目标尺寸计算实际尺寸
  269. this._initImageSize();
  270. //设置裁剪框大小>设置图片尺寸>绘制canvas
  271. this._computeCutSize();
  272. //检查裁剪框是否在范围内
  273. this._cutDetectionPosition();
  274. //检查canvas是否在范围内
  275. this._canvasDetectionPosition();
  276. //初始化完成
  277. this.triggerEvent('load', {
  278. cropper: this
  279. });
  280. },
  281. methods: {
  282. /**
  283. * 上传图片
  284. */
  285. upload() {
  286. let that = this;
  287. wx.chooseMedia({
  288. count: 1,
  289. mediaType: ['image'],
  290. sourceType: ['album'],
  291. // camera: 'back',
  292. success(res) {
  293. const tempFilePaths = res.tempFiles[0].tempFilePath;
  294. that.pushImg(tempFilePaths);
  295. wx.showLoading({
  296. title: '加载中...'
  297. })
  298. }
  299. })
  300. },
  301. /**
  302. * 返回图片信息
  303. */
  304. getImg(getCallback) {
  305. this._draw(() => {
  306. wx.canvasToTempFilePath({
  307. width: this.data.width * this.data.export_scale,
  308. height: Math.round(this.data.height * this.data.export_scale),
  309. destWidth: this.data.width * this.data.export_scale,
  310. destHeight: Math.round(this.data.height) * this.data.export_scale,
  311. fileType: 'jpg',
  312. quality: this.data.quality,
  313. canvasId: this.data.el,
  314. success: (res) => {
  315. console.log("裁剪图片1", res.tempFilePath);
  316. getCallback({
  317. url: res.tempFilePath,
  318. width: this.data.width * this.data.export_scale,
  319. height: this.data.height * this.data.export_scale
  320. });
  321. }
  322. }, this)
  323. });
  324. },
  325. /**
  326. * 返回图片数据data
  327. */
  328. getImgData(getCallback) {
  329. this._draw(() => {
  330. wx.canvasGetImageData({
  331. canvasId: this.data.el,
  332. x: 0,
  333. y: 0,
  334. width: this.data.width * this.data.export_scale,
  335. height: Math.round(this.data.height * this.data.export_scale),
  336. destWidth: this.data.width * this.data.export_scale,
  337. destHeight: Math.round(this.data.height) * this.data.export_scale,
  338. quality: this.data.quality,
  339. success: (res) => {
  340. getCallback({
  341. data: res.data,
  342. width: this.data.width * this.data.export_scale,
  343. height: this.data.height * this.data.export_scale
  344. });
  345. }
  346. }, this)
  347. });
  348. },
  349. convertToStandardRGB(getCallback) {
  350. try {
  351. this.getImgData((res) => {
  352. console.log("获取像素数据成功", res.data);
  353. const rgbData = this._drawRGB565(res.data, res.width, res.height);
  354. console.log("转换后的 RGB 数据", rgbData.length, rgbData.byteLength);
  355. // 将转换后的 RGB 数据绘制回 Canvas
  356. wx.canvasPutImageData({
  357. canvasId: this.data.el,
  358. data: rgbData,
  359. x: 0,
  360. y: 0,
  361. width: res.width,
  362. height: res.height,
  363. success: (res2) => {
  364. console.log("绘制 RGB 数据成功", res2);
  365. wx.canvasToTempFilePath({
  366. width: res.width,
  367. height: res.height,
  368. destWidth: res.width,
  369. destHeight: res.height,
  370. fileType: 'jpg',
  371. quality: this.data.quality,
  372. canvasId: this.data.el,
  373. success: (res) => {
  374. console.log("转换后的图片路径", res.tempFilePath);
  375. getCallback({
  376. url: res.tempFilePath,
  377. width: res.width,
  378. height: res.height
  379. });
  380. },
  381. fail: (err) => {
  382. console.error('转换失败:', err);
  383. }
  384. }, this);
  385. },
  386. fail: (err) => {
  387. console.error('绘制 RGB 数据失败:', err);
  388. }
  389. }, this);
  390. });
  391. } catch (e) {
  392. console.log(e)
  393. }
  394. },
  395. // 将 16 位 RGB 565 数据绘制回 Canvas
  396. _drawRGB565(data, width, height, callback) {
  397. const ctx = wx.createCanvasContext(this.data.el, this);
  398. const imageData = ctx.createImageData(width, height);
  399. console.log("转换前的 RGB 数据", data.length, data.byteLength);
  400. for (let i = 0, j = 0; i < data.length; i++) {
  401. const pixel = data[i];
  402. const r = (pixel >> 11) & 0x1F;
  403. const g = (pixel >> 5) & 0x3F;
  404. const b = pixel & 0x1F;
  405. imageData.data[j++] = (r << 3) | (r >> 2); // R
  406. imageData.data[j++] = (g << 2) | (g >> 4); // G
  407. imageData.data[j++] = (b << 3) | (b >> 2); // B
  408. imageData.data[j++] = 255; // Alpha 设置为 255,确保没有透明通道
  409. }
  410. console.log("转换后的 RGB 数据", imageData.length, imageData.byteLength);
  411. ctx.putImageData(imageData, 0, 0);
  412. ctx.draw(false, callback);
  413. },
  414. /**
  415. * 设置图片动画
  416. * {
  417. * x:10,//图片在原有基础上向下移动10px
  418. * y:10,//图片在原有基础上向右移动10px
  419. * angle:10,//图片在原有基础上旋转10deg
  420. * scale:0.5,//图片在原有基础上增加0.5倍
  421. * }
  422. */
  423. setTransform(transform) {
  424. if (!transform) return;
  425. if (!this.data.disable_rotate) {
  426. this.setData({
  427. angle: transform.angle ? this.data.angle + transform.angle : this.data.angle
  428. });
  429. }
  430. var scale = this.data.scale;
  431. if (transform.scale) {
  432. scale = this.data.scale + transform.scale;
  433. scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
  434. scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
  435. }
  436. this.data.scale = scale;
  437. let cutX = this.data.cut_left;
  438. let cutY = this.data.cut_top;
  439. if (transform.cutX) {
  440. this.setData({
  441. cut_left: cutX + transform.cutX
  442. });
  443. this.data.watch.cut_left(null, this);
  444. }
  445. if (transform.cutY) {
  446. this.setData({
  447. cut_top: cutY + transform.cutY
  448. });
  449. this.data.watch.cut_top(null, this);
  450. }
  451. this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top;
  452. this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left;
  453. //图像边缘检测,防止截取到空白
  454. this._imgMarginDetectionScale();
  455. //停止居中裁剪框,继续修改图片位置
  456. this._moveDuring();
  457. this.setData({
  458. scale: this.data.scale,
  459. _img_top: this.data._img_top,
  460. _img_left: this.data._img_left
  461. });
  462. !this.data._canvas_overflow && this._draw();
  463. //可以居中裁剪框了
  464. this._moveStop(); //结束操作
  465. },
  466. /**
  467. * 设置剪裁框位置
  468. */
  469. setCutXY(x, y) {
  470. this.setData({
  471. cut_top: y,
  472. cut_left: x
  473. });
  474. },
  475. /**
  476. * 设置剪裁框尺寸
  477. */
  478. setCutSize(w, h) {
  479. this.setData({
  480. width: w,
  481. height: h
  482. });
  483. this._computeCutSize();
  484. },
  485. /**
  486. * 设置剪裁框和图片居中
  487. */
  488. setCutCenter() {
  489. let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
  490. let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
  491. //顺序不能变
  492. this.setData({
  493. _img_top: this.data._img_top - this.data.cut_top + cut_top,
  494. cut_top: cut_top, //截取的框上边距
  495. _img_left: this.data._img_left - this.data.cut_left + cut_left,
  496. cut_left: cut_left, //截取的框左边距
  497. });
  498. },
  499. _setCutCenter() {
  500. let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
  501. let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
  502. this.setData({
  503. cut_top: cut_top, //截取的框上边距
  504. cut_left: cut_left, //截取的框左边距
  505. });
  506. },
  507. /**
  508. * 设置剪裁框宽度-即将废弃
  509. */
  510. setWidth(width) {
  511. this.setData({
  512. width: width
  513. });
  514. this._computeCutSize();
  515. },
  516. /**
  517. * 设置剪裁框高度-即将废弃
  518. */
  519. setHeight(height) {
  520. this.setData({
  521. height: height
  522. });
  523. this._computeCutSize();
  524. },
  525. /**
  526. * 是否锁定旋转
  527. */
  528. setDisableRotate(value) {
  529. this.data.disable_rotate = value;
  530. },
  531. /**
  532. * 是否限制移动
  533. */
  534. setLimitMove(value) {
  535. this.setData({
  536. _cut_animation: true,
  537. limit_move: !!value
  538. });
  539. },
  540. /**
  541. * 初始化图片,包括位置、大小、旋转角度
  542. */
  543. imgReset() {
  544. this.setData({
  545. scale: 1,
  546. angle: 0,
  547. _img_top: wx.getSystemInfoSync().windowHeight / 2,
  548. _img_left: wx.getSystemInfoSync().windowWidth / 2,
  549. })
  550. },
  551. /**
  552. * 加载(更换)图片
  553. */
  554. pushImg(src) {
  555. if (src) {
  556. this.setData({
  557. imgSrc: src
  558. });
  559. //发现是手动赋值直接返回,交给watch处理
  560. return;
  561. }
  562. // getImageInfo接口传入 src: '' 会导致内存泄漏
  563. if (!this.data.imgSrc) return;
  564. wx.getImageInfo({
  565. src: this.data.imgSrc,
  566. success: (res) => {
  567. this.data.imageObject = res;
  568. //图片非本地路径需要换成本地路径
  569. if (this.data.imgSrc.search(/tmp/) == -1) {
  570. this.setData({
  571. imgSrc: res.path
  572. });
  573. }
  574. //计算最后图片尺寸
  575. this._imgComputeSize();
  576. if (this.data.limit_move) {
  577. //限制移动,不留空白处理
  578. this._imgMarginDetectionScale();
  579. }
  580. this._draw();
  581. },
  582. fail: (err) => {
  583. this.setData({
  584. imgSrc: ''
  585. });
  586. }
  587. });
  588. },
  589. imageLoad(e) {
  590. setTimeout(() => {
  591. this.triggerEvent('imageload', this.data.imageObject);
  592. }, 1000)
  593. },
  594. /**
  595. * 设置图片放大缩小
  596. */
  597. setScale(scale) {
  598. if (!scale) return;
  599. this.setData({
  600. scale: scale
  601. });
  602. !this.data._canvas_overflow && this._draw();
  603. },
  604. /**
  605. * 设置图片旋转角度
  606. */
  607. setAngle(angle) {
  608. if (!angle) return;
  609. this.setData({
  610. _cut_animation: true,
  611. angle: angle
  612. });
  613. this._imgMarginDetectionScale();
  614. !this.data._canvas_overflow && this._draw();
  615. },
  616. _initCanvas() {
  617. //初始化canvas
  618. if (!this.data.ctx) {
  619. console.log("创建canvas0");
  620. this.data.ctx = wx.createCanvasContext("image-cropper", this);
  621. }
  622. },
  623. /**
  624. * 根据开发者设置的图片目标尺寸计算实际尺寸
  625. */
  626. _initImageSize() {
  627. //处理宽高特殊单位 %>px
  628. if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) {
  629. let width = this.data.INIT_IMGWIDTH.replace("%", "");
  630. this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width;
  631. }
  632. if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) {
  633. let height = this.data.img_height.replace("%", "");
  634. this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height;
  635. }
  636. },
  637. /**
  638. * 检测剪裁框位置是否在允许的范围内(屏幕内)
  639. */
  640. _cutDetectionPosition() {
  641. let _cutDetectionPositionTop = () => {
  642. //检测上边距是否在范围内
  643. if (this.data.cut_top < 0) {
  644. this.setData({
  645. cut_top: 0
  646. });
  647. }
  648. if (this.data.cut_top > this.data.info.windowHeight - this.data.height) {
  649. this.setData({
  650. cut_top: this.data.info.windowHeight - this.data.height
  651. });
  652. }
  653. },
  654. _cutDetectionPositionLeft = () => {
  655. //检测左边距是否在范围内
  656. if (this.data.cut_left < 0) {
  657. this.setData({
  658. cut_left: 0
  659. });
  660. }
  661. if (this.data.cut_left > this.data.info.windowWidth - this.data.width) {
  662. this.setData({
  663. cut_left: this.data.info.windowWidth - this.data.width
  664. });
  665. }
  666. };
  667. //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
  668. if (this.data.cut_top == null && this.data.cut_left == null) {
  669. this._setCutCenter();
  670. } else if (this.data.cut_top != null && this.data.cut_left != null) {
  671. _cutDetectionPositionTop();
  672. _cutDetectionPositionLeft();
  673. } else if (this.data.cut_top != null && this.data.cut_left == null) {
  674. _cutDetectionPositionTop();
  675. this.setData({
  676. cut_left: (this.data.info.windowWidth - this.data.width) / 2
  677. });
  678. } else if (this.data.cut_top == null && this.data.cut_left != null) {
  679. _cutDetectionPositionLeft();
  680. this.setData({
  681. cut_top: (this.data.info.windowHeight - this.data.height) / 2
  682. });
  683. }
  684. },
  685. /**
  686. * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染
  687. * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外
  688. */
  689. _canvasDetectionPosition() {
  690. if (this.data.canvas_top == null && this.data.canvas_left == null) {
  691. this.data._canvas_overflow = false;
  692. this.setData({
  693. canvas_top: -5000,
  694. canvas_left: -5000
  695. });
  696. } else if (this.data.canvas_top != null && this.data.canvas_left != null) {
  697. if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) {
  698. this.data._canvas_overflow = true;
  699. } else {
  700. this.data._canvas_overflow = false;
  701. }
  702. } else if (this.data.canvas_top != null && this.data.canvas_left == null) {
  703. this.setData({
  704. canvas_left: 0
  705. });
  706. } else if (this.data.canvas_top == null && this.data.canvas_left != null) {
  707. this.setData({
  708. canvas_top: 0
  709. });
  710. if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) {
  711. this.data._canvas_overflow = true;
  712. } else {
  713. this.data._canvas_overflow = false;
  714. }
  715. }
  716. },
  717. /**
  718. * 图片边缘检测-位置
  719. */
  720. _imgMarginDetectionPosition(scale) {
  721. if (!this.data.limit_move) return;
  722. let left = this.data._img_left;
  723. let top = this.data._img_top;
  724. var scale = scale || this.data.scale;
  725. let img_width = this.data.img_width;
  726. let img_height = this.data.img_height;
  727. if (this.data.angle / 90 % 2) {
  728. img_width = this.data.img_height;
  729. img_height = this.data.img_width;
  730. }
  731. left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2;
  732. left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2;
  733. top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2;
  734. top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2;
  735. this.setData({
  736. _img_left: left,
  737. _img_top: top,
  738. scale: scale
  739. })
  740. },
  741. /**
  742. * 图片边缘检测-缩放
  743. */
  744. _imgMarginDetectionScale() {
  745. if (!this.data.limit_move) return;
  746. let scale = this.data.scale;
  747. let img_width = this.data.img_width;
  748. let img_height = this.data.img_height;
  749. if (this.data.angle / 90 % 2) {
  750. img_width = this.data.img_height;
  751. img_height = this.data.img_width;
  752. }
  753. if (img_width * scale < this.data.width) {
  754. scale = this.data.width / img_width;
  755. }
  756. if (img_height * scale < this.data.height) {
  757. scale = Math.max(scale, this.data.height / img_height);
  758. }
  759. this._imgMarginDetectionPosition(scale);
  760. },
  761. _setData(obj) {
  762. let data = {};
  763. for (var key in obj) {
  764. if (this.data[key] != obj[key]) {
  765. data[key] = obj[key];
  766. }
  767. }
  768. this.setData(data);
  769. return data;
  770. },
  771. /**
  772. * 计算图片尺寸
  773. */
  774. _imgComputeSize() {
  775. let img_width = this.data.img_width,
  776. img_height = this.data.img_height;
  777. if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
  778. //默认按图片最小边 = 对应裁剪框尺寸
  779. img_width = this.data.imageObject.width;
  780. img_height = this.data.imageObject.height;
  781. if (img_width / img_height > this.data.width / this.data.height) {
  782. img_height = this.data.height;
  783. img_width = this.data.imageObject.width / this.data.imageObject.height * img_height;
  784. } else {
  785. img_width = this.data.width;
  786. img_height = this.data.imageObject.height / this.data.imageObject.width * img_width;
  787. }
  788. } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
  789. img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT;
  790. } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) {
  791. img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH;
  792. }
  793. this.setData({
  794. img_width: img_width,
  795. img_height: img_height
  796. });
  797. },
  798. //改变截取框大小
  799. _computeCutSize() {
  800. if (this.data.width > this.data.info.windowWidth) {
  801. this.setData({
  802. width: this.data.info.windowWidth,
  803. });
  804. } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) {
  805. this.setData({
  806. cut_left: this.data.info.windowWidth - this.data.cut_left,
  807. });
  808. };
  809. if (this.data.height > this.data.info.windowHeight) {
  810. this.setData({
  811. height: this.data.info.windowHeight,
  812. });
  813. } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) {
  814. this.setData({
  815. cut_top: this.data.info.windowHeight - this.data.cut_top,
  816. });
  817. } !this.data._canvas_overflow && this._draw();
  818. },
  819. //开始触摸
  820. _start(event) {
  821. this.data._flag_img_endtouch = false;
  822. if (event.touches.length == 1) {
  823. //单指拖动
  824. this.data._touch_img_relative[0] = {
  825. x: (event.touches[0].clientX - this.data._img_left),
  826. y: (event.touches[0].clientY - this.data._img_top)
  827. }
  828. } else {
  829. //双指放大
  830. let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX);
  831. let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY);
  832. this.data._touch_img_relative = [{
  833. x: (event.touches[0].clientX - this.data._img_left),
  834. y: (event.touches[0].clientY - this.data._img_top)
  835. }, {
  836. x: (event.touches[1].clientX - this.data._img_left),
  837. y: (event.touches[1].clientY - this.data._img_top)
  838. }];
  839. this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
  840. } !this.data._canvas_overflow && this._draw();
  841. },
  842. _move_throttle() {
  843. //安卓需要节流
  844. if (this.data.info.platform == 'android') {
  845. clearTimeout(this.data.MOVE_THROTTLE);
  846. this.data.MOVE_THROTTLE = setTimeout(() => {
  847. this.data.MOVE_THROTTLE_FLAG = true;
  848. }, 1000 / 40)
  849. return this.data.MOVE_THROTTLE_FLAG;
  850. } else {
  851. this.data.MOVE_THROTTLE_FLAG = true;
  852. }
  853. },
  854. _move(event) {
  855. if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return;
  856. this.data.MOVE_THROTTLE_FLAG = false;
  857. this._move_throttle();
  858. this._moveDuring();
  859. if (event.touches.length == 1) {
  860. //单指拖动
  861. let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x),
  862. top = (event.touches[0].clientY - this.data._touch_img_relative[0].y);
  863. //图像边缘检测,防止截取到空白
  864. this.data._img_left = left;
  865. this.data._img_top = top;
  866. this._imgMarginDetectionPosition();
  867. this.setData({
  868. _img_left: this.data._img_left,
  869. _img_top: this.data._img_top
  870. });
  871. } else {
  872. //双指放大
  873. let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)),
  874. height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)),
  875. hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
  876. scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length),
  877. current_deg = 0;
  878. scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
  879. scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
  880. //图像边缘检测,防止截取到空白
  881. this.data.scale = scale;
  882. this._imgMarginDetectionScale();
  883. //双指旋转(如果没禁用旋转)
  884. let _touch_img_relative = [{
  885. x: (event.touches[0].clientX - this.data._img_left),
  886. y: (event.touches[0].clientY - this.data._img_top)
  887. }, {
  888. x: (event.touches[1].clientX - this.data._img_left),
  889. y: (event.touches[1].clientY - this.data._img_top)
  890. }];
  891. if (!this.data.disable_rotate) {
  892. let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x);
  893. let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x);
  894. let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x);
  895. let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x);
  896. //当前旋转的角度
  897. let first_deg = first_atan - first_atan_old,
  898. second_deg = second_atan - second_atan_old;
  899. if (first_deg != 0) {
  900. current_deg = first_deg;
  901. } else if (second_deg != 0) {
  902. current_deg = second_deg;
  903. }
  904. }
  905. this.data._touch_img_relative = _touch_img_relative;
  906. this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
  907. //更新视图
  908. this.setData({
  909. angle: this.data.angle + current_deg,
  910. scale: this.data.scale
  911. });
  912. } !this.data._canvas_overflow && this._draw();
  913. },
  914. //结束操作
  915. _end(event) {
  916. this.data._flag_img_endtouch = true;
  917. this._moveStop();
  918. },
  919. //点击中间剪裁框处理
  920. _click(event) {
  921. if (!this.data.imgSrc) {
  922. //调起上传
  923. this.upload();
  924. return;
  925. }
  926. this._draw(() => {
  927. let x = event.detail ? event.detail.x : event.touches[0].clientX;
  928. let y = event.detail ? event.detail.y : event.touches[0].clientY;
  929. if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) {
  930. //生成图片并回调
  931. wx.canvasToTempFilePath({
  932. width: this.data.width * this.data.export_scale,
  933. height: Math.round(this.data.height * this.data.export_scale),
  934. destWidth: this.data.width * this.data.export_scale,
  935. destHeight: Math.round(this.data.height) * this.data.export_scale,
  936. fileType: 'jpg',
  937. quality: this.data.quality,
  938. canvasId: this.data.el,
  939. success: (res) => {
  940. console.log("裁剪图片", res.tempFilePath);
  941. this.triggerEvent('tapcut', {
  942. url: res.tempFilePath,
  943. width: this.data.width * this.data.export_scale,
  944. height: this.data.height * this.data.export_scale
  945. });
  946. }
  947. }, this)
  948. }
  949. });
  950. },
  951. //渲染
  952. _draw(callback) {
  953. if (!this.data.imgSrc) return;
  954. let draw = () => {
  955. //图片实际大小
  956. let img_width = this.data.img_width * this.data.scale * this.data.export_scale;
  957. let img_height = this.data.img_height * this.data.scale * this.data.export_scale;
  958. //canvas和图片的相对距离
  959. var xpos = this.data._img_left - this.data.cut_left;
  960. var ypos = this.data._img_top - this.data.cut_top;
  961. //旋转画布
  962. this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale);
  963. this.data.ctx.rotate(this.data.angle * Math.PI / 180);
  964. this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height);
  965. this.data.ctx.draw(false, () => {
  966. callback && callback();
  967. });
  968. }
  969. if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) {
  970. //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方
  971. this.setData({
  972. _canvas_height: this.data.height,
  973. _canvas_width: this.data.width,
  974. }, () => {
  975. //延迟40毫秒防止点击过快出现拉伸或裁剪过多
  976. setTimeout(() => {
  977. draw();
  978. }, 40);
  979. });
  980. } else {
  981. draw();
  982. }
  983. },
  984. //裁剪框处理
  985. _cutTouchMove(e) {
  986. if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) {
  987. if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return;
  988. //节流
  989. this.data.MOVE_THROTTLE_FLAG = false;
  990. this._move_throttle();
  991. let width = this.data.width,
  992. height = this.data.height,
  993. cut_top = this.data.cut_top,
  994. cut_left = this.data.cut_left,
  995. size_correct = () => {
  996. width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width;
  997. height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height;
  998. },
  999. size_inspect = () => {
  1000. if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) {
  1001. size_correct();
  1002. return false;
  1003. } else {
  1004. size_correct();
  1005. return true;
  1006. }
  1007. };
  1008. height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY));
  1009. switch (this.data.CUT_START.corner) {
  1010. case 1:
  1011. width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
  1012. if (this.data.disable_ratio) {
  1013. height = width / (this.data.width / this.data.height)
  1014. }
  1015. if (!size_inspect()) return;
  1016. cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width);
  1017. break
  1018. case 2:
  1019. width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
  1020. if (this.data.disable_ratio) {
  1021. height = width / (this.data.width / this.data.height)
  1022. }
  1023. if (!size_inspect()) return;
  1024. cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height)
  1025. cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width)
  1026. break
  1027. case 3:
  1028. width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
  1029. if (this.data.disable_ratio) {
  1030. height = width / (this.data.width / this.data.height)
  1031. }
  1032. if (!size_inspect()) return;
  1033. cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height);
  1034. break
  1035. case 4:
  1036. width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
  1037. if (this.data.disable_ratio) {
  1038. height = width / (this.data.width / this.data.height)
  1039. }
  1040. if (!size_inspect()) return;
  1041. break
  1042. }
  1043. if (!this.data.disable_width && !this.data.disable_height) {
  1044. this.setData({
  1045. width: width,
  1046. cut_left: cut_left,
  1047. height: height,
  1048. cut_top: cut_top,
  1049. })
  1050. } else if (!this.data.disable_width) {
  1051. this.setData({
  1052. width: width,
  1053. cut_left: cut_left
  1054. })
  1055. } else if (!this.data.disable_height) {
  1056. this.setData({
  1057. height: height,
  1058. cut_top: cut_top
  1059. })
  1060. }
  1061. this._imgMarginDetectionScale();
  1062. }
  1063. },
  1064. _cutTouchStart(e) {
  1065. let currentX = e.touches[0].clientX;
  1066. let currentY = e.touches[0].clientY;
  1067. let cutbox_top4 = this.data.cut_top + this.data.height - 30;
  1068. let cutbox_bottom4 = this.data.cut_top + this.data.height + 20;
  1069. let cutbox_left4 = this.data.cut_left + this.data.width - 30;
  1070. let cutbox_right4 = this.data.cut_left + this.data.width + 30;
  1071. let cutbox_top3 = this.data.cut_top - 30;
  1072. let cutbox_bottom3 = this.data.cut_top + 30;
  1073. let cutbox_left3 = this.data.cut_left + this.data.width - 30;
  1074. let cutbox_right3 = this.data.cut_left + this.data.width + 30;
  1075. let cutbox_top2 = this.data.cut_top - 30;
  1076. let cutbox_bottom2 = this.data.cut_top + 30;
  1077. let cutbox_left2 = this.data.cut_left - 30;
  1078. let cutbox_right2 = this.data.cut_left + 30;
  1079. let cutbox_top1 = this.data.cut_top + this.data.height - 30;
  1080. let cutbox_bottom1 = this.data.cut_top + this.data.height + 30;
  1081. let cutbox_left1 = this.data.cut_left - 30;
  1082. let cutbox_right1 = this.data.cut_left + 30;
  1083. if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) {
  1084. this._moveDuring();
  1085. this.data._flag_cut_touch = true;
  1086. this.data._flag_img_endtouch = true;
  1087. this.data.CUT_START = {
  1088. width: this.data.width,
  1089. height: this.data.height,
  1090. x: currentX,
  1091. y: currentY,
  1092. corner: 4
  1093. }
  1094. } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) {
  1095. this._moveDuring();
  1096. this.data._flag_cut_touch = true;
  1097. this.data._flag_img_endtouch = true;
  1098. this.data.CUT_START = {
  1099. width: this.data.width,
  1100. height: this.data.height,
  1101. x: currentX,
  1102. y: currentY,
  1103. cut_top: this.data.cut_top,
  1104. cut_left: this.data.cut_left,
  1105. corner: 3
  1106. }
  1107. } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) {
  1108. this._moveDuring();
  1109. this.data._flag_cut_touch = true;
  1110. this.data._flag_img_endtouch = true;
  1111. this.data.CUT_START = {
  1112. width: this.data.width,
  1113. height: this.data.height,
  1114. cut_top: this.data.cut_top,
  1115. cut_left: this.data.cut_left,
  1116. x: currentX,
  1117. y: currentY,
  1118. corner: 2
  1119. }
  1120. } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) {
  1121. this._moveDuring();
  1122. this.data._flag_cut_touch = true;
  1123. this.data._flag_img_endtouch = true;
  1124. this.data.CUT_START = {
  1125. width: this.data.width,
  1126. height: this.data.height,
  1127. cut_top: this.data.cut_top,
  1128. cut_left: this.data.cut_left,
  1129. x: currentX,
  1130. y: currentY,
  1131. corner: 1
  1132. }
  1133. }
  1134. },
  1135. _cutTouchEnd(e) {
  1136. this._moveStop();
  1137. this.data._flag_cut_touch = false;
  1138. },
  1139. //停止移动时需要做的操作
  1140. _moveStop() {
  1141. //清空之前的自动居中延迟函数并添加最新的
  1142. clearTimeout(this.data.TIME_CUT_CENTER);
  1143. this.data.TIME_CUT_CENTER = setTimeout(() => {
  1144. //动画启动
  1145. if (!this.data._cut_animation) {
  1146. this.setData({
  1147. _cut_animation: true
  1148. });
  1149. }
  1150. this.setCutCenter();
  1151. }, 1000)
  1152. //清空之前的背景变化延迟函数并添加最新的
  1153. clearTimeout(this.data.TIME_BG);
  1154. this.data.TIME_BG = setTimeout(() => {
  1155. if (this.data._flag_bright) {
  1156. this.setData({
  1157. _flag_bright: false
  1158. });
  1159. }
  1160. }, 2000)
  1161. },
  1162. //移动中
  1163. _moveDuring() {
  1164. //清空之前的自动居中延迟函数
  1165. clearTimeout(this.data.TIME_CUT_CENTER);
  1166. //清空之前的背景变化延迟函数
  1167. clearTimeout(this.data.TIME_BG);
  1168. //高亮背景
  1169. if (!this.data._flag_bright) {
  1170. this.setData({
  1171. _flag_bright: true
  1172. });
  1173. }
  1174. },
  1175. //监听器
  1176. _watcher() {
  1177. Object.keys(this.data).forEach(v => {
  1178. this._observe(this.data, v, this.data.watch[v]);
  1179. })
  1180. },
  1181. _observe(obj, key, watchFun) {
  1182. var val = obj[key];
  1183. Object.defineProperty(obj, key, {
  1184. configurable: true,
  1185. enumerable: true,
  1186. set: (value) => {
  1187. val = value;
  1188. watchFun && watchFun(val, this);
  1189. },
  1190. get() {
  1191. if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) {
  1192. let ret = parseFloat(parseFloat(val).toFixed(3));
  1193. if (typeof val == "string" && val.indexOf("%") != -1) {
  1194. ret += '%';
  1195. }
  1196. return ret;
  1197. }
  1198. return val;
  1199. }
  1200. })
  1201. },
  1202. _preventTouchMove() { }
  1203. }
  1204. })