cropper.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. /**
  2. * we-cropper v1.2.0
  3. * (c) 2018 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 () {
  11. 'use strict';
  12. var device = void 0;
  13. var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
  14. function isFunction(obj) {
  15. return typeof obj === 'function'
  16. }
  17. function firstLetterUpper(str) {
  18. return str.charAt(0).toUpperCase() + str.slice(1)
  19. }
  20. function setTouchState(instance) {
  21. var arg = [], len = arguments.length - 1;
  22. while (len-- > 0) arg[len] = arguments[len + 1];
  23. TOUCH_STATE.forEach(function (key, i) {
  24. if (arg[i] !== undefined) {
  25. instance[key] = arg[i];
  26. }
  27. });
  28. }
  29. function validator(instance, o) {
  30. Object.defineProperties(instance, o);
  31. }
  32. function getDevice() {
  33. if (!device) {
  34. device = wx.getSystemInfoSync();
  35. }
  36. return device
  37. }
  38. var tmp = {};
  39. var DEFAULT = {
  40. id: {
  41. default: 'cropper',
  42. get: function get() {
  43. return tmp.id
  44. },
  45. set: function set(value) {
  46. if (typeof (value) !== 'string') {
  47. console.error(("id:" + value + " is invalid"));
  48. }
  49. tmp.id = value;
  50. }
  51. },
  52. width: {
  53. default: 750,
  54. get: function get() {
  55. return tmp.width
  56. },
  57. set: function set(value) {
  58. if (typeof (value) !== 'number') {
  59. console.error(("width:" + value + " is invalid"));
  60. }
  61. tmp.width = value;
  62. }
  63. },
  64. height: {
  65. default: 750,
  66. get: function get() {
  67. return tmp.height
  68. },
  69. set: function set(value) {
  70. if (typeof (value) !== 'number') {
  71. console.error(("height:" + value + " is invalid"));
  72. }
  73. tmp.height = value;
  74. }
  75. },
  76. scale: {
  77. default: 2.5,
  78. get: function get() {
  79. return tmp.scale
  80. },
  81. set: function set(value) {
  82. if (typeof (value) !== 'number') {
  83. console.error(("scale:" + value + " is invalid"));
  84. }
  85. tmp.scale = value;
  86. }
  87. },
  88. zoom: {
  89. default: 5,
  90. get: function get() {
  91. return tmp.zoom
  92. },
  93. set: function set(value) {
  94. if (typeof (value) !== 'number') {
  95. console.error(("zoom:" + value + " is invalid"));
  96. } else if (value < 0 || value > 10) {
  97. console.error("zoom should be ranged in 0 ~ 10");
  98. }
  99. tmp.zoom = value;
  100. }
  101. },
  102. src: {
  103. default: 'cropper',
  104. get: function get() {
  105. return tmp.src
  106. },
  107. set: function set(value) {
  108. if (typeof (value) !== 'string') {
  109. console.error(("id:" + value + " is invalid"));
  110. }
  111. tmp.src = value;
  112. }
  113. },
  114. cut: {
  115. default: {},
  116. get: function get() {
  117. return tmp.cut
  118. },
  119. set: function set(value) {
  120. if (typeof (value) !== 'object') {
  121. console.error(("id:" + value + " is invalid"));
  122. }
  123. tmp.cut = value;
  124. }
  125. },
  126. onReady: {
  127. default: null,
  128. get: function get() {
  129. return tmp.ready
  130. },
  131. set: function set(value) {
  132. tmp.ready = value;
  133. }
  134. },
  135. onBeforeImageLoad: {
  136. default: null,
  137. get: function get() {
  138. return tmp.beforeImageLoad
  139. },
  140. set: function set(value) {
  141. tmp.beforeImageLoad = value;
  142. }
  143. },
  144. onImageLoad: {
  145. default: null,
  146. get: function get() {
  147. return tmp.imageLoad
  148. },
  149. set: function set(value) {
  150. tmp.imageLoad = value;
  151. }
  152. },
  153. onBeforeDraw: {
  154. default: null,
  155. get: function get() {
  156. return tmp.beforeDraw
  157. },
  158. set: function set(value) {
  159. tmp.beforeDraw = value;
  160. }
  161. }
  162. };
  163. function prepare() {
  164. var self = this;
  165. var ref = getDevice();
  166. var windowWidth = ref.windowWidth;
  167. self.attachPage = function () {
  168. var pages = getCurrentPages();
  169. // 获取到当前page上下文
  170. var pageContext = pages[pages.length - 1];
  171. // 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
  172. pageContext.wecropper = self;
  173. };
  174. self.createCtx = function () {
  175. var id = self.id;
  176. if (id) {
  177. self.ctx = wx.createCanvasContext(id);
  178. } else {
  179. console.error("constructor: create canvas context failed, 'id' must be valuable");
  180. }
  181. };
  182. self.deviceRadio = windowWidth / 750;
  183. }
  184. function observer() {
  185. var self = this;
  186. var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
  187. self.on = function (event, fn) {
  188. if (EVENT_TYPE.indexOf(event) > -1) {
  189. if (typeof (fn) === 'function') {
  190. event === 'ready'
  191. ? fn(self)
  192. : self[("on" + (firstLetterUpper(event)))] = fn;
  193. }
  194. } else {
  195. console.error(("event: " + event + " is invalid"));
  196. }
  197. return self
  198. };
  199. }
  200. var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  201. function createCommonjsModule(fn, module) {
  202. return module = { exports: {} }, fn(module, module.exports), module.exports;
  203. }
  204. var base64 = createCommonjsModule(function (module, exports) {
  205. /*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
  206. (function (root) {
  207. // Detect free variables `exports`.
  208. var freeExports = 'object' == 'object' && exports;
  209. // Detect free variable `module`.
  210. var freeModule = 'object' == 'object' && module &&
  211. module.exports == freeExports && module;
  212. // Detect free variable `global`, from Node.js or Browserified code, and use
  213. // it as `root`.
  214. var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
  215. if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
  216. root = freeGlobal;
  217. }
  218. /*--------------------------------------------------------------------------*/
  219. var InvalidCharacterError = function (message) {
  220. this.message = message;
  221. };
  222. InvalidCharacterError.prototype = new Error;
  223. InvalidCharacterError.prototype.name = 'InvalidCharacterError';
  224. var error = function (message) {
  225. // Note: the error messages used throughout this file match those used by
  226. // the native `atob`/`btoa` implementation in Chromium.
  227. throw new InvalidCharacterError(message);
  228. };
  229. var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  230. // http://whatwg.org/html/common-microsyntaxes.html#space-character
  231. var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;
  232. // `decode` is designed to be fully compatible with `atob` as described in the
  233. // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
  234. // The optimized base64-decoding algorithm used is based on @atk’s excellent
  235. // implementation. https://gist.github.com/atk/1020396
  236. var decode = function (input) {
  237. input = String(input)
  238. .replace(REGEX_SPACE_CHARACTERS, '');
  239. var length = input.length;
  240. if (length % 4 == 0) {
  241. input = input.replace(/==?$/, '');
  242. length = input.length;
  243. }
  244. if (
  245. length % 4 == 1 ||
  246. // http://whatwg.org/C#alphanumeric-ascii-characters
  247. /[^+a-zA-Z0-9/]/.test(input)
  248. ) {
  249. error(
  250. 'Invalid character: the string to be decoded is not correctly encoded.'
  251. );
  252. }
  253. var bitCounter = 0;
  254. var bitStorage;
  255. var buffer;
  256. var output = '';
  257. var position = -1;
  258. while (++position < length) {
  259. buffer = TABLE.indexOf(input.charAt(position));
  260. bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
  261. // Unless this is the first of a group of 4 characters…
  262. if (bitCounter++ % 4) {
  263. // …convert the first 8 bits to a single ASCII character.
  264. output += String.fromCharCode(
  265. 0xFF & bitStorage >> (-2 * bitCounter & 6)
  266. );
  267. }
  268. }
  269. return output;
  270. };
  271. // `encode` is designed to be fully compatible with `btoa` as described in the
  272. // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
  273. var encode = function (input) {
  274. input = String(input);
  275. if (/[^\0-\xFF]/.test(input)) {
  276. // Note: no need to special-case astral symbols here, as surrogates are
  277. // matched, and the input is supposed to only contain ASCII anyway.
  278. error(
  279. 'The string to be encoded contains characters outside of the ' +
  280. 'Latin1 range.'
  281. );
  282. }
  283. var padding = input.length % 3;
  284. var output = '';
  285. var position = -1;
  286. var a;
  287. var b;
  288. var c;
  289. var buffer;
  290. // Make sure any padding is handled outside of the loop.
  291. var length = input.length - padding;
  292. while (++position < length) {
  293. // Read three bytes, i.e. 24 bits.
  294. a = input.charCodeAt(position) << 16;
  295. b = input.charCodeAt(++position) << 8;
  296. c = input.charCodeAt(++position);
  297. buffer = a + b + c;
  298. // Turn the 24 bits into four chunks of 6 bits each, and append the
  299. // matching character for each of them to the output.
  300. output += (
  301. TABLE.charAt(buffer >> 18 & 0x3F) +
  302. TABLE.charAt(buffer >> 12 & 0x3F) +
  303. TABLE.charAt(buffer >> 6 & 0x3F) +
  304. TABLE.charAt(buffer & 0x3F)
  305. );
  306. }
  307. if (padding == 2) {
  308. a = input.charCodeAt(position) << 8;
  309. b = input.charCodeAt(++position);
  310. buffer = a + b;
  311. output += (
  312. TABLE.charAt(buffer >> 10) +
  313. TABLE.charAt((buffer >> 4) & 0x3F) +
  314. TABLE.charAt((buffer << 2) & 0x3F) +
  315. '='
  316. );
  317. } else if (padding == 1) {
  318. buffer = input.charCodeAt(position);
  319. output += (
  320. TABLE.charAt(buffer >> 2) +
  321. TABLE.charAt((buffer << 4) & 0x3F) +
  322. '=='
  323. );
  324. }
  325. return output;
  326. };
  327. var base64 = {
  328. 'encode': encode,
  329. 'decode': decode,
  330. 'version': '0.1.0'
  331. };
  332. // Some AMD build optimizers, like r.js, check for specific condition patterns
  333. // like the following:
  334. if (
  335. typeof undefined == 'function' &&
  336. typeof undefined.amd == 'object' &&
  337. undefined.amd
  338. ) {
  339. undefined(function () {
  340. return base64;
  341. });
  342. } else if (freeExports && !freeExports.nodeType) {
  343. if (freeModule) { // in Node.js or RingoJS v0.8.0+
  344. freeModule.exports = base64;
  345. } else { // in Narwhal or RingoJS v0.7.0-
  346. for (var key in base64) {
  347. base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
  348. }
  349. }
  350. } else { // in Rhino or a web browser
  351. root.base64 = base64;
  352. }
  353. }(commonjsGlobal));
  354. });
  355. function makeURI(strData, type) {
  356. return 'data:' + type + ';base64,' + strData
  357. }
  358. function fixType(type) {
  359. type = type.toLowerCase().replace(/jpg/i, 'jpeg');
  360. var r = type.match(/png|jpeg|bmp|gif/)[0];
  361. return 'image/' + r
  362. }
  363. function encodeData(data) {
  364. var str = '';
  365. if (typeof data === 'string') {
  366. str = data;
  367. } else {
  368. for (var i = 0; i < data.length; i++) {
  369. str += String.fromCharCode(data[i]);
  370. }
  371. }
  372. return base64.encode(str)
  373. }
  374. /**
  375. * 获取图像区域隐含的像素数据
  376. * @param canvasId canvas标识
  377. * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
  378. * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
  379. * @param width 将要被提取的图像数据矩形区域的宽度
  380. * @param height 将要被提取的图像数据矩形区域的高度
  381. * @param done 完成回调
  382. */
  383. function getImageData(canvasId, x, y, width, height, done) {
  384. wx.canvasGetImageData({
  385. canvasId: canvasId,
  386. x: x,
  387. y: y,
  388. width: width,
  389. height: height,
  390. success: function success(res) {
  391. done(res);
  392. },
  393. fail: function fail(res) {
  394. done(null);
  395. console.error('canvasGetImageData error: ' + res);
  396. }
  397. });
  398. }
  399. /**
  400. * 生成bmp格式图片
  401. * 按照规则生成图片响应头和响应体
  402. * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
  403. * @returns {*} base64字符串
  404. */
  405. function genBitmapImage(oData) {
  406. //
  407. // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
  408. // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
  409. //
  410. var biWidth = oData.width;
  411. var biHeight = oData.height;
  412. var biSizeImage = biWidth * biHeight * 3;
  413. var bfSize = biSizeImage + 54; // total header size = 54 bytes
  414. //
  415. // typedef struct tagBITMAPFILEHEADER {
  416. // WORD bfType;
  417. // DWORD bfSize;
  418. // WORD bfReserved1;
  419. // WORD bfReserved2;
  420. // DWORD bfOffBits;
  421. // } BITMAPFILEHEADER;
  422. //
  423. var BITMAPFILEHEADER = [
  424. // WORD bfType -- The file type signature; must be "BM"
  425. 0x42, 0x4D,
  426. // DWORD bfSize -- The size, in bytes, of the bitmap file
  427. bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
  428. // WORD bfReserved1 -- Reserved; must be zero
  429. 0, 0,
  430. // WORD bfReserved2 -- Reserved; must be zero
  431. 0, 0,
  432. // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
  433. 54, 0, 0, 0
  434. ];
  435. //
  436. // typedef struct tagBITMAPINFOHEADER {
  437. // DWORD biSize;
  438. // LONG biWidth;
  439. // LONG biHeight;
  440. // WORD biPlanes;
  441. // WORD biBitCount;
  442. // DWORD biCompression;
  443. // DWORD biSizeImage;
  444. // LONG biXPelsPerMeter;
  445. // LONG biYPelsPerMeter;
  446. // DWORD biClrUsed;
  447. // DWORD biClrImportant;
  448. // } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
  449. //
  450. var BITMAPINFOHEADER = [
  451. // DWORD biSize -- The number of bytes required by the structure
  452. 40, 0, 0, 0,
  453. // LONG biWidth -- The width of the bitmap, in pixels
  454. biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
  455. // LONG biHeight -- The height of the bitmap, in pixels
  456. biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
  457. // WORD biPlanes -- The number of planes for the target device. This value must be set to 1
  458. 1, 0,
  459. // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
  460. // has a maximum of 2^24 colors (16777216, Truecolor)
  461. 24, 0,
  462. // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
  463. 0, 0, 0, 0,
  464. // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
  465. biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
  466. // LONG biXPelsPerMeter, unused
  467. 0, 0, 0, 0,
  468. // LONG biYPelsPerMeter, unused
  469. 0, 0, 0, 0,
  470. // DWORD biClrUsed, the number of color indexes of palette, unused
  471. 0, 0, 0, 0,
  472. // DWORD biClrImportant, unused
  473. 0, 0, 0, 0
  474. ];
  475. var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
  476. var aImgData = oData.data;
  477. var strPixelData = '';
  478. var biWidth4 = biWidth << 2;
  479. var y = biHeight;
  480. var fromCharCode = String.fromCharCode;
  481. do {
  482. var iOffsetY = biWidth4 * (y - 1);
  483. var strPixelRow = '';
  484. for (var x = 0; x < biWidth; x++) {
  485. var iOffsetX = x << 2;
  486. strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
  487. fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
  488. fromCharCode(aImgData[iOffsetY + iOffsetX]);
  489. }
  490. for (var c = 0; c < iPadding; c++) {
  491. strPixelRow += String.fromCharCode(0);
  492. }
  493. strPixelData += strPixelRow;
  494. } while (--y)
  495. var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
  496. return strEncoded
  497. }
  498. /**
  499. * 转换为图片base64
  500. * @param canvasId canvas标识
  501. * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
  502. * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
  503. * @param width 将要被提取的图像数据矩形区域的宽度
  504. * @param height 将要被提取的图像数据矩形区域的高度
  505. * @param type 转换图片类型
  506. * @param done 完成回调
  507. */
  508. function convertToImage(canvasId, x, y, width, height, type, done) {
  509. if (done === void 0) done = function () { };
  510. if (type === undefined) { type = 'png'; }
  511. type = fixType(type);
  512. if (/bmp/.test(type)) {
  513. getImageData(canvasId, x, y, width, height, function (data) {
  514. var strData = genBitmapImage(data);
  515. isFunction(done) && done(makeURI(strData, 'image/' + type));
  516. });
  517. } else {
  518. console.error('暂不支持生成\'' + type + '\'类型的base64图片');
  519. }
  520. }
  521. var CanvasToBase64 = {
  522. convertToImage: convertToImage,
  523. // convertToPNG: function (width, height, done) {
  524. // return convertToImage(width, height, 'png', done)
  525. // },
  526. // convertToJPEG: function (width, height, done) {
  527. // return convertToImage(width, height, 'jpeg', done)
  528. // },
  529. // convertToGIF: function (width, height, done) {
  530. // return convertToImage(width, height, 'gif', done)
  531. // },
  532. convertToBMP: function (ref, done) {
  533. if (ref === void 0) ref = {};
  534. var canvasId = ref.canvasId;
  535. var x = ref.x;
  536. var y = ref.y;
  537. var width = ref.width;
  538. var height = ref.height;
  539. if (done === void 0) done = function () { };
  540. return convertToImage(canvasId, x, y, width, height, 'bmp', done)
  541. }
  542. };
  543. function methods() {
  544. var self = this;
  545. var id = self.id;
  546. var deviceRadio = self.deviceRadio;
  547. var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
  548. var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
  549. var ref = self.cut;
  550. var x = ref.x; if (x === void 0) x = 0;
  551. var y = ref.y; if (y === void 0) y = 0;
  552. var width = ref.width; if (width === void 0) width = boundWidth;
  553. var height = ref.height; if (height === void 0) height = boundHeight;
  554. self.updateCanvas = function () {
  555. if (self.croperTarget) {
  556. // 画布绘制图片
  557. self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight);
  558. }
  559. isFunction(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
  560. self.setBoundStyle(); // 设置边界样式
  561. self.ctx.draw();
  562. return self
  563. };
  564. self.pushOrign = function (src) {
  565. self.src = src;
  566. isFunction(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
  567. wx.getImageInfo({
  568. src: src,
  569. success: function success(res) {
  570. var innerAspectRadio = res.width / res.height;
  571. self.croperTarget = res.path;
  572. if (innerAspectRadio < width / height) {
  573. self.rectX = x;
  574. self.baseWidth = width;
  575. self.baseHeight = width / innerAspectRadio;
  576. self.rectY = y - Math.abs((height - self.baseHeight) / 2);
  577. } else {
  578. self.rectY = y;
  579. self.baseWidth = height * innerAspectRadio;
  580. self.baseHeight = height;
  581. self.rectX = x - Math.abs((width - self.baseWidth) / 2);
  582. }
  583. self.imgLeft = self.rectX;
  584. self.imgTop = self.rectY;
  585. self.scaleWidth = self.baseWidth;
  586. self.scaleHeight = self.baseHeight;
  587. self.updateCanvas();
  588. isFunction(self.onImageLoad) && self.onImageLoad(self.ctx, self);
  589. }
  590. });
  591. self.update();
  592. return self
  593. };
  594. self.getCropperBase64 = function (done) {
  595. if (done === void 0) done = function () { };
  596. CanvasToBase64.convertToBMP({
  597. canvasId: id,
  598. x: x,
  599. y: y,
  600. width: width,
  601. height: height
  602. }, done);
  603. };
  604. self.getCropperImage = function () {
  605. var args = [], len = arguments.length;
  606. while (len--) args[len] = arguments[len];
  607. var ARG_TYPE = toString.call(args[0]);
  608. var fn = args[args.length - 1];
  609. switch (ARG_TYPE) {
  610. case '[object Object]':
  611. var ref = args[0];
  612. var quality = ref.quality; if (quality === void 0) quality = 10;
  613. if (typeof (quality) !== 'number') {
  614. console.error(("quality:" + quality + " is invalid"));
  615. } else if (quality < 0 || quality > 10) {
  616. console.error("quality should be ranged in 0 ~ 10");
  617. }
  618. wx.canvasToTempFilePath({
  619. canvasId: id,
  620. x: x,
  621. y: y,
  622. width: width,
  623. height: height,
  624. destWidth: width * quality / (deviceRadio * 10),
  625. destHeight: height * quality / (deviceRadio * 10),
  626. success: function success(res) {
  627. isFunction(fn) && fn.call(self, res.tempFilePath);
  628. },
  629. fail: function fail(res) {
  630. isFunction(fn) && fn.call(self, null);
  631. }
  632. }); break
  633. case '[object Function]':
  634. wx.canvasToTempFilePath({
  635. canvasId: id,
  636. x: x,
  637. y: y,
  638. width: width,
  639. height: height,
  640. destWidth: width / deviceRadio,
  641. destHeight: height / deviceRadio,
  642. success: function success(res) {
  643. isFunction(fn) && fn.call(self, res.tempFilePath);
  644. },
  645. fail: function fail(res) {
  646. isFunction(fn) && fn.call(self, null);
  647. }
  648. }); break
  649. }
  650. return self
  651. };
  652. }
  653. /**
  654. * 获取最新缩放值
  655. * @param oldScale 上一次触摸结束后的缩放值
  656. * @param oldDistance 上一次触摸结束后的双指距离
  657. * @param zoom 缩放系数
  658. * @param touch0 第一指touch对象
  659. * @param touch1 第二指touch对象
  660. * @returns {*}
  661. */
  662. var getNewScale = function (oldScale, oldDistance, zoom, touch0, touch1) {
  663. var xMove, yMove, newDistance;
  664. // 计算二指最新距离
  665. xMove = Math.round(touch1.x - touch0.x);
  666. yMove = Math.round(touch1.y - touch0.y);
  667. newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
  668. return oldScale + 0.001 * zoom * (newDistance - oldDistance)
  669. };
  670. function update() {
  671. var self = this;
  672. if (!self.src) { return }
  673. self.__oneTouchStart = function (touch) {
  674. self.touchX0 = Math.round(touch.x);
  675. self.touchY0 = Math.round(touch.y);
  676. };
  677. self.__oneTouchMove = function (touch) {
  678. var xMove, yMove;
  679. // 计算单指移动的距离
  680. if (self.touchended) {
  681. return self.updateCanvas()
  682. }
  683. xMove = Math.round(touch.x - self.touchX0);
  684. yMove = Math.round(touch.y - self.touchY0);
  685. var imgLeft = Math.round(self.rectX + xMove);
  686. var imgTop = Math.round(self.rectY + yMove);
  687. self.outsideBound(imgLeft, imgTop);
  688. self.updateCanvas();
  689. };
  690. self.__twoTouchStart = function (touch0, touch1) {
  691. var xMove, yMove, oldDistance;
  692. self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
  693. self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
  694. // 计算两指距离
  695. xMove = Math.round(touch1.x - touch0.x);
  696. yMove = Math.round(touch1.y - touch0.y);
  697. oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
  698. self.oldDistance = oldDistance;
  699. };
  700. self.__twoTouchMove = function (touch0, touch1) {
  701. var oldScale = self.oldScale;
  702. var oldDistance = self.oldDistance;
  703. var scale = self.scale;
  704. var zoom = self.zoom;
  705. self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
  706. // 设定缩放范围
  707. self.newScale <= 1 && (self.newScale = 1);
  708. self.newScale >= scale && (self.newScale = scale);
  709. self.scaleWidth = Math.round(self.newScale * self.baseWidth);
  710. self.scaleHeight = Math.round(self.newScale * self.baseHeight);
  711. var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
  712. var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
  713. self.outsideBound(imgLeft, imgTop);
  714. self.updateCanvas();
  715. };
  716. self.__xtouchEnd = function () {
  717. self.oldScale = self.newScale;
  718. self.rectX = self.imgLeft;
  719. self.rectY = self.imgTop;
  720. };
  721. }
  722. var handle = {
  723. // 图片手势初始监测
  724. touchStart: function touchStart(e) {
  725. var self = this;
  726. var ref = e.touches;
  727. var touch0 = ref[0];
  728. var touch1 = ref[1];
  729. setTouchState(self, true, null, null);
  730. // 计算第一个触摸点的位置,并参照改点进行缩放
  731. self.__oneTouchStart(touch0);
  732. // 两指手势触发
  733. if (e.touches.length >= 2) {
  734. self.__twoTouchStart(touch0, touch1);
  735. }
  736. },
  737. // 图片手势动态缩放
  738. touchMove: function touchMove(e) {
  739. var self = this;
  740. var ref = e.touches;
  741. var touch0 = ref[0];
  742. var touch1 = ref[1];
  743. setTouchState(self, null, true);
  744. // 单指手势时触发
  745. if (e.touches.length === 1) {
  746. self.__oneTouchMove(touch0);
  747. }
  748. // 两指手势触发
  749. if (e.touches.length >= 2) {
  750. self.__twoTouchMove(touch0, touch1);
  751. }
  752. },
  753. touchEnd: function touchEnd(e) {
  754. var self = this;
  755. setTouchState(self, false, false, true);
  756. self.__xtouchEnd();
  757. }
  758. };
  759. function cut() {
  760. var self = this;
  761. var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
  762. var boundHeight = self.height;
  763. // 裁剪框默认高度,即整个画布高度
  764. var ref = self.cut;
  765. var x = ref.x; if (x === void 0) x = 0;
  766. var y = ref.y; if (y === void 0) y = 0;
  767. var width = ref.width; if (width === void 0) width = boundWidth;
  768. var height = ref.height; if (height === void 0) height = boundHeight;
  769. /**
  770. * 设置边界
  771. * @param imgLeft 图片左上角横坐标值
  772. * @param imgTop 图片左上角纵坐标值
  773. */
  774. self.outsideBound = function (imgLeft, imgTop) {
  775. self.imgLeft = imgLeft >= x
  776. ? x
  777. : self.scaleWidth + imgLeft - x <= width
  778. ? x + width - self.scaleWidth
  779. : imgLeft;
  780. self.imgTop = imgTop >= y
  781. ? y
  782. : self.scaleHeight + imgTop - y <= height
  783. ? y + height - self.scaleHeight
  784. : imgTop;
  785. };
  786. /**
  787. * 设置边界样式
  788. * @param color 边界颜色
  789. */
  790. self.setBoundStyle = function (ref) {
  791. if (ref === void 0) ref = {};
  792. var color = ref.color; if (color === void 0) color = '#04b00f';
  793. var mask = ref.mask; if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)';
  794. var lineWidth = ref.lineWidth; if (lineWidth === void 0) lineWidth = 1;
  795. var boundOption = [
  796. {
  797. start: { x: x - lineWidth, y: y + 10 - lineWidth },
  798. step1: { x: x - lineWidth, y: y - lineWidth },
  799. step2: { x: x + 10 - lineWidth, y: y - lineWidth }
  800. },
  801. {
  802. start: { x: x - lineWidth, y: y + height - 10 + lineWidth },
  803. step1: { x: x - lineWidth, y: y + height + lineWidth },
  804. step2: { x: x + 10 - lineWidth, y: y + height + lineWidth }
  805. },
  806. {
  807. start: { x: x + width - 10 + lineWidth, y: y - lineWidth },
  808. step1: { x: x + width + lineWidth, y: y - lineWidth },
  809. step2: { x: x + width + lineWidth, y: y + 10 - lineWidth }
  810. },
  811. {
  812. start: { x: x + width + lineWidth, y: y + height - 10 + lineWidth },
  813. step1: { x: x + width + lineWidth, y: y + height + lineWidth },
  814. step2: { x: x + width - 10 + lineWidth, y: y + height + lineWidth }
  815. }
  816. ];
  817. // 绘制半透明层
  818. self.ctx.beginPath();
  819. self.ctx.setFillStyle(mask);
  820. self.ctx.fillRect(0, 0, x, boundHeight);
  821. self.ctx.fillRect(x, 0, width, y);
  822. self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
  823. self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
  824. self.ctx.fill();
  825. boundOption.forEach(function (op) {
  826. self.ctx.beginPath();
  827. self.ctx.setStrokeStyle(color);
  828. self.ctx.setLineWidth(lineWidth);
  829. self.ctx.moveTo(op.start.x, op.start.y);
  830. self.ctx.lineTo(op.step1.x, op.step1.y);
  831. self.ctx.lineTo(op.step2.x, op.step2.y);
  832. self.ctx.stroke();
  833. });
  834. };
  835. }
  836. var version = "1.2.0";
  837. var weCropper = function weCropper(params) {
  838. var self = this;
  839. var _default = {};
  840. validator(self, DEFAULT);
  841. Object.keys(DEFAULT).forEach(function (key) {
  842. _default[key] = DEFAULT[key].default;
  843. });
  844. Object.assign(self, _default, params);
  845. self.prepare();
  846. self.attachPage();
  847. self.createCtx();
  848. self.observer();
  849. self.cutt();
  850. self.methods();
  851. self.init();
  852. self.update();
  853. return self
  854. };
  855. weCropper.prototype.init = function init() {
  856. var self = this;
  857. var src = self.src;
  858. self.version = version;
  859. typeof self.onReady === 'function' && self.onReady(self.ctx, self);
  860. if (src) {
  861. self.pushOrign(src);
  862. }
  863. setTouchState(self, false, false, false);
  864. self.oldScale = 1;
  865. self.newScale = 1;
  866. return self
  867. };
  868. Object.assign(weCropper.prototype, handle);
  869. weCropper.prototype.prepare = prepare;
  870. weCropper.prototype.observer = observer;
  871. weCropper.prototype.methods = methods;
  872. weCropper.prototype.cutt = cut;
  873. weCropper.prototype.update = update;
  874. return weCropper;
  875. })));