we-cropper.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /**
  2. * we-cropper v1.1.5
  3. * (c) 2017 dlhandsome
  4. * @license MIT
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  8. typeof define === 'function' && define.amd ? define(factory) :
  9. (global.WeCropper = factory());
  10. }(this, (function () { 'use strict';
  11. var device = void 0;
  12. var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
  13. function firstLetterUpper (str) {
  14. return str.charAt(0).toUpperCase() + str.slice(1)
  15. }
  16. function setTouchState (instance) {
  17. var arg = [], len = arguments.length - 1;
  18. while ( len-- > 0 ) arg[ len ] = arguments[ len + 1 ];
  19. TOUCH_STATE.forEach(function (key, i) {
  20. if (arg[i] !== undefined) {
  21. instance[key] = arg[i];
  22. }
  23. });
  24. }
  25. function validator (instance, o) {
  26. Object.defineProperties(instance, o);
  27. }
  28. function getDevice () {
  29. if (!device) {
  30. device = wx.getSystemInfoSync();
  31. }
  32. return device
  33. }
  34. function isFunction (obj) {
  35. return typeof obj === 'function'
  36. }
  37. var tmp = {};
  38. var DEFAULT = {
  39. id: {
  40. default: 'cropper',
  41. get: function get () {
  42. return tmp.id
  43. },
  44. set: function set (value) {
  45. if (typeof (value) !== 'string') {
  46. console.error(("id:" + value + " is invalid"));
  47. }
  48. tmp.id = value;
  49. }
  50. },
  51. width: {
  52. default: 750,
  53. get: function get () {
  54. return tmp.width
  55. },
  56. set: function set (value) {
  57. if (typeof (value) !== 'number') {
  58. console.error(("width:" + value + " is invalid"));
  59. }
  60. tmp.width = value;
  61. }
  62. },
  63. height: {
  64. default: 750,
  65. get: function get () {
  66. return tmp.height
  67. },
  68. set: function set (value) {
  69. if (typeof (value) !== 'number') {
  70. console.error(("height:" + value + " is invalid"));
  71. }
  72. tmp.height = value;
  73. }
  74. },
  75. scale: {
  76. default: 2.5,
  77. get: function get () {
  78. return tmp.scale
  79. },
  80. set: function set (value) {
  81. if (typeof (value) !== 'number') {
  82. console.error(("scale:" + value + " is invalid"));
  83. }
  84. tmp.scale = value;
  85. }
  86. },
  87. zoom: {
  88. default: 5,
  89. get: function get () {
  90. return tmp.zoom
  91. },
  92. set: function set (value) {
  93. if (typeof (value) !== 'number') {
  94. console.error(("zoom:" + value + " is invalid"));
  95. } else if (value < 0 || value > 10) {
  96. console.error("zoom should be ranged in 0 ~ 10");
  97. }
  98. tmp.zoom = value;
  99. }
  100. },
  101. src: {
  102. default: 'cropper',
  103. get: function get () {
  104. return tmp.src
  105. },
  106. set: function set (value) {
  107. if (typeof (value) !== 'string') {
  108. console.error(("id:" + value + " is invalid"));
  109. }
  110. tmp.src = value;
  111. }
  112. },
  113. cut: {
  114. default: {},
  115. get: function get () {
  116. return tmp.cut
  117. },
  118. set: function set (value) {
  119. if (typeof (value) !== 'object') {
  120. console.error(("id:" + value + " is invalid"));
  121. }
  122. tmp.cut = value;
  123. }
  124. },
  125. onReady: {
  126. default: null,
  127. get: function get () {
  128. return tmp.ready
  129. },
  130. set: function set (value) {
  131. tmp.ready = value;
  132. }
  133. },
  134. onBeforeImageLoad: {
  135. default: null,
  136. get: function get () {
  137. return tmp.beforeImageLoad
  138. },
  139. set: function set (value) {
  140. tmp.beforeImageLoad = value;
  141. }
  142. },
  143. onImageLoad: {
  144. default: null,
  145. get: function get () {
  146. return tmp.imageLoad
  147. },
  148. set: function set (value) {
  149. tmp.imageLoad = value;
  150. }
  151. },
  152. onBeforeDraw: {
  153. default: null,
  154. get: function get () {
  155. return tmp.beforeDraw
  156. },
  157. set: function set (value) {
  158. tmp.beforeDraw = value;
  159. }
  160. }
  161. };
  162. function prepare () {
  163. var self = this;
  164. var ref = getDevice();
  165. var windowWidth = ref.windowWidth;
  166. self.attachPage = function () {
  167. var pages = getCurrentPages();
  168. // 获取到当前page上下文
  169. var pageContext = pages[pages.length - 1];
  170. // 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
  171. pageContext.wecropper = self;
  172. };
  173. self.createCtx = function () {
  174. var id = self.id;
  175. if (id) {
  176. self.ctx = wx.createCanvasContext(id);
  177. } else {
  178. console.error("constructor: create canvas context failed, 'id' must be valuable");
  179. }
  180. };
  181. self.deviceRadio = windowWidth / 750;
  182. }
  183. function observer () {
  184. var self = this;
  185. var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
  186. self.on = function (event, fn) {
  187. if (EVENT_TYPE.indexOf(event) > -1) {
  188. if (typeof (fn) === 'function') {
  189. event === 'ready'
  190. ? fn(self)
  191. : self[("on" + (firstLetterUpper(event)))] = fn;
  192. }
  193. } else {
  194. console.error(("event: " + event + " is invalid"));
  195. }
  196. return self
  197. };
  198. }
  199. function methods () {
  200. var self = this;
  201. var deviceRadio = self.deviceRadio;
  202. var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
  203. var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
  204. var ref = self.cut;
  205. var x = ref.x; if ( x === void 0 ) x = 0;
  206. var y = ref.y; if ( y === void 0 ) y = 0;
  207. var width = ref.width; if ( width === void 0 ) width = boundWidth;
  208. var height = ref.height; if ( height === void 0 ) height = boundHeight;
  209. self.updateCanvas = function () {
  210. if (self.croperTarget) {
  211. // 画布绘制图片
  212. self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight);
  213. }
  214. isFunction(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
  215. self.setBoundStyle(); // 设置边界样式
  216. self.ctx.draw();
  217. return self
  218. };
  219. self.pushOrign = function (src) {
  220. self.src = src;
  221. isFunction(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
  222. wx.getImageInfo({
  223. src: src,
  224. success: function success (res) {
  225. var innerAspectRadio = res.width / res.height;
  226. self.croperTarget = res.path;
  227. console.log(x, y);
  228. if (innerAspectRadio < width / height) {
  229. self.rectX = x;
  230. self.baseWidth = width;
  231. self.baseHeight = width / innerAspectRadio;
  232. self.rectY = y - Math.abs((height - self.baseHeight) / 2);
  233. } else {
  234. self.rectY = y;
  235. self.baseWidth = height * innerAspectRadio;
  236. self.baseHeight = height;
  237. self.rectX = x - Math.abs((width - self.baseWidth) / 2);
  238. }
  239. self.imgLeft = self.rectX;
  240. self.imgTop = self.rectY;
  241. self.scaleWidth = self.baseWidth;
  242. self.scaleHeight = self.baseHeight;
  243. self.updateCanvas();
  244. isFunction(self.onImageLoad) && self.onImageLoad(self.ctx, self);
  245. }
  246. });
  247. self.update();
  248. return self
  249. };
  250. self.getCropperImage = function () {
  251. var args = [], len = arguments.length;
  252. while ( len-- ) args[ len ] = arguments[ len ];
  253. var id = self.id;
  254. var ARG_TYPE = toString.call(args[0]);
  255. var fn = args[args.length - 1];
  256. switch (ARG_TYPE) {
  257. case '[object Object]':
  258. var ref = args[0];
  259. var quality = ref.quality; if ( quality === void 0 ) quality = 10;
  260. if (typeof (quality) !== 'number') {
  261. console.error(("quality:" + quality + " is invalid"));
  262. } else if (quality < 0 || quality > 10) {
  263. console.error("quality should be ranged in 0 ~ 10");
  264. }
  265. wx.canvasToTempFilePath({
  266. canvasId: id,
  267. x: x,
  268. y: y,
  269. width: width,
  270. height: height,
  271. destWidth: width * quality / (deviceRadio * 10),
  272. destHeight: height * quality / (deviceRadio * 10),
  273. success: function success (res) {
  274. isFunction(fn) && fn.call(self, res.tempFilePath);
  275. },
  276. fail: function fail (res) {
  277. isFunction(fn) && fn.call(self, null);
  278. }
  279. }); break
  280. case '[object Function]':
  281. wx.canvasToTempFilePath({
  282. canvasId: id,
  283. x: x,
  284. y: y,
  285. width: width,
  286. height: height,
  287. destWidth: width / deviceRadio,
  288. destHeight: height / deviceRadio,
  289. success: function success (res) {
  290. isFunction(fn) && fn.call(self, res.tempFilePath);
  291. },
  292. fail: function fail (res) {
  293. isFunction(fn) && fn.call(self, null);
  294. }
  295. }); break
  296. }
  297. return self
  298. };
  299. }
  300. function update () {
  301. var self = this;
  302. if (!self.src) { return }
  303. self.__oneTouchStart = function (touch) {
  304. self.touchX0 = touch.x;
  305. self.touchY0 = touch.y;
  306. };
  307. self.__oneTouchMove = function (touch) {
  308. var xMove, yMove;
  309. // 计算单指移动的距离
  310. if (self.touchended) {
  311. return self.updateCanvas()
  312. }
  313. xMove = touch.x - self.touchX0;
  314. yMove = touch.y - self.touchY0;
  315. var imgLeft = self.rectX + xMove;
  316. var imgTop = self.rectY + yMove;
  317. self.outsideBound(imgLeft, imgTop);
  318. self.updateCanvas();
  319. };
  320. self.__twoTouchStart = function (touch0, touch1) {
  321. var xMove, yMove, oldDistance;
  322. self.touchX1 = self.rectX + self.scaleWidth / 2;
  323. self.touchY1 = self.rectY + self.scaleHeight / 2;
  324. // 计算两指距离
  325. xMove = touch1.x - touch0.x;
  326. yMove = touch1.y - touch0.y;
  327. oldDistance = Math.sqrt(xMove * xMove + yMove * yMove);
  328. self.oldDistance = oldDistance;
  329. };
  330. self.__twoTouchMove = function (touch0, touch1) {
  331. var xMove, yMove, newDistance;
  332. var scale = self.scale;
  333. var zoom = self.zoom;
  334. // 计算二指最新距离
  335. xMove = touch1.x - touch0.x;
  336. yMove = touch1.y - touch0.y;
  337. newDistance = Math.sqrt(xMove * xMove + yMove * yMove);
  338. // 使用0.005的缩放倍数具有良好的缩放体验
  339. self.newScale = self.oldScale + 0.001 * zoom * (newDistance - self.oldDistance);
  340. // 设定缩放范围
  341. self.newScale <= 1 && (self.newScale = 1);
  342. self.newScale >= scale && (self.newScale = scale);
  343. self.scaleWidth = self.newScale * self.baseWidth;
  344. self.scaleHeight = self.newScale * self.baseHeight;
  345. var imgLeft = self.touchX1 - self.scaleWidth / 2;
  346. var imgTop = self.touchY1 - self.scaleHeight / 2;
  347. self.outsideBound(imgLeft, imgTop);
  348. self.updateCanvas();
  349. };
  350. self.__xtouchEnd = function () {
  351. self.oldScale = self.newScale;
  352. self.rectX = self.imgLeft;
  353. self.rectY = self.imgTop;
  354. };
  355. }
  356. var handle = {
  357. // 图片手势初始监测
  358. touchStart: function touchStart (e) {
  359. var self = this;
  360. var ref = e.touches;
  361. var touch0 = ref[0];
  362. var touch1 = ref[1];
  363. setTouchState(self, true, null, null);
  364. // 计算第一个触摸点的位置,并参照改点进行缩放
  365. self.__oneTouchStart(touch0);
  366. // 两指手势触发
  367. if (e.touches.length >= 2) {
  368. self.__twoTouchStart(touch0, touch1);
  369. }
  370. },
  371. // 图片手势动态缩放
  372. touchMove: function touchMove (e) {
  373. var self = this;
  374. var ref = e.touches;
  375. var touch0 = ref[0];
  376. var touch1 = ref[1];
  377. setTouchState(self, null, true);
  378. // 单指手势时触发
  379. if (e.touches.length === 1) {
  380. self.__oneTouchMove(touch0);
  381. }
  382. // 两指手势触发
  383. if (e.touches.length >= 2) {
  384. self.__twoTouchMove(touch0, touch1);
  385. }
  386. },
  387. touchEnd: function touchEnd (e) {
  388. var self = this;
  389. setTouchState(self, false, false, true);
  390. self.__xtouchEnd();
  391. }
  392. };
  393. function cut () {
  394. var self = this;
  395. var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
  396. var boundHeight = self.height;
  397. // 裁剪框默认高度,即整个画布高度
  398. var ref = self.cut;
  399. var x = ref.x; if ( x === void 0 ) x = 0;
  400. var y = ref.y; if ( y === void 0 ) y = 0;
  401. var width = ref.width; if ( width === void 0 ) width = boundWidth;
  402. var height = ref.height; if ( height === void 0 ) height = boundHeight;
  403. /**
  404. * 设置边界
  405. * @param imgLeft 图片左上角横坐标值
  406. * @param imgTop 图片左上角纵坐标值
  407. */
  408. self.outsideBound = function (imgLeft, imgTop) {
  409. self.imgLeft = imgLeft >= x
  410. ? x
  411. : self.scaleWidth + imgLeft - x <= width
  412. ? x + width - self.scaleWidth
  413. : imgLeft;
  414. self.imgTop = imgTop >= y
  415. ? y
  416. : self.scaleHeight + imgTop - y <= height
  417. ? y + height - self.scaleHeight
  418. : imgTop;
  419. };
  420. /**
  421. * 设置边界样式
  422. * @param color 边界颜色
  423. */
  424. self.setBoundStyle = function (ref) {
  425. if ( ref === void 0 ) ref = {};
  426. var color = ref.color; if ( color === void 0 ) color = '#04b00f';
  427. var mask = ref.mask; if ( mask === void 0 ) mask = 'rgba(0, 0, 0, 0.3)';
  428. var lineWidth = ref.lineWidth; if ( lineWidth === void 0 ) lineWidth = 1;
  429. // 绘制半透明层
  430. self.ctx.beginPath();
  431. self.ctx.setFillStyle(mask);
  432. self.ctx.fillRect(0, 0, x, boundHeight);
  433. self.ctx.fillRect(x, 0, width, y);
  434. self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
  435. self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
  436. self.ctx.fill();
  437. // 设置边界左上角样式
  438. // 为使边界样式处于边界外边缘,此时x、y均要减少lineWidth
  439. self.ctx.beginPath();
  440. self.ctx.setStrokeStyle(color);
  441. self.ctx.setLineWidth(lineWidth);
  442. self.ctx.moveTo(x - lineWidth, y + 10 - lineWidth);
  443. self.ctx.lineTo(x - lineWidth, y - lineWidth);
  444. self.ctx.lineTo(x + 10 - lineWidth, y - lineWidth);
  445. self.ctx.stroke();
  446. // 设置边界左下角样式
  447. // 为使边界样式处于边界外边缘,此时x要减少lineWidth、y要增加lineWidth
  448. self.ctx.beginPath();
  449. self.ctx.setStrokeStyle(color);
  450. self.ctx.setLineWidth(lineWidth);
  451. self.ctx.moveTo(x - lineWidth, y + height - 10 + lineWidth);
  452. self.ctx.lineTo(x - lineWidth, y + height + lineWidth);
  453. self.ctx.lineTo(x + 10 - lineWidth, y + height + lineWidth);
  454. self.ctx.stroke();
  455. // 设置边界右上角样式
  456. // 为使边界样式处于边界外边缘,此时x要增加lineWidth、y要减少lineWidth
  457. self.ctx.beginPath();
  458. self.ctx.setStrokeStyle(color);
  459. self.ctx.setLineWidth(lineWidth);
  460. self.ctx.moveTo(x + width - 10 + lineWidth, y - lineWidth);
  461. self.ctx.lineTo(x + width + lineWidth, y - lineWidth);
  462. self.ctx.lineTo(x + width + lineWidth, y + 10 - lineWidth);
  463. self.ctx.stroke();
  464. // 设置边界右下角样式
  465. // 为使边界样式处于边界外边缘,此时x、y均要增加lineWidth
  466. self.ctx.beginPath();
  467. self.ctx.setStrokeStyle(color);
  468. self.ctx.setLineWidth(lineWidth);
  469. self.ctx.moveTo(x + width + lineWidth, y + height - 10 + lineWidth);
  470. self.ctx.lineTo(x + width + lineWidth, y + height + lineWidth);
  471. self.ctx.lineTo(x + width - 10 + lineWidth, y + height + lineWidth);
  472. self.ctx.stroke();
  473. };
  474. }
  475. var version = "1.1.5";
  476. var weCropper = function weCropper (params) {
  477. var self = this;
  478. var _default = {};
  479. validator(self, DEFAULT);
  480. Object.keys(DEFAULT).forEach(function (key) {
  481. _default[key] = DEFAULT[key].default;
  482. });
  483. Object.assign(self, _default, params);
  484. self.prepare();
  485. self.attachPage();
  486. self.createCtx();
  487. self.observer();
  488. self.cutt();
  489. self.methods();
  490. self.init();
  491. self.update();
  492. return self
  493. };
  494. weCropper.prototype.init = function init () {
  495. var self = this;
  496. var src = self.src;
  497. self.version = version;
  498. typeof self.onReady === 'function' && self.onReady(self.ctx, self);
  499. if (src) {
  500. self.pushOrign(src);
  501. }
  502. setTouchState(self, false, false, false);
  503. self.oldScale = 1;
  504. self.newScale = 1;
  505. return self
  506. };
  507. Object.assign(weCropper.prototype, handle);
  508. weCropper.prototype.prepare = prepare;
  509. weCropper.prototype.observer = observer;
  510. weCropper.prototype.methods = methods;
  511. weCropper.prototype.cutt = cut;
  512. weCropper.prototype.update = update;
  513. return weCropper;
  514. })));