index.js 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628
  1. /* eslint-disable no-undef */
  2. (function (factory) {
  3. if (typeof define === 'function' && define.amd) {
  4. // AMD. Register as an anonymous module.
  5. define(['prefix-umd', 'exif-js', 'transformation-matrix'], factory);
  6. } else if (typeof module === 'object' && module.exports) {
  7. // Node/CommonJS
  8. module.exports = factory(require('prefix-umd'), require('exif-js'), require('transformation-matrix'));
  9. } else {
  10. // Browser globals
  11. window.SimpleCrop = factory(window.Prefix, window.EXIF, window.TransformationMatrix);
  12. }
  13. }(function (Prefix, EXIF, TransformationMatrix) {
  14. //兼容性处理
  15. function whichTransitionEvent() {
  16. var t;
  17. var el = document.createElement('div');
  18. var transitions = {
  19. 'transition': 'transitionend',
  20. 'OTransition': 'oTransitionEnd',
  21. 'MozTransition': 'transitionend',
  22. 'WebkitTransition': 'webkitTransitionEnd',
  23. 'MsTransition': 'msTransitionEnd'
  24. };
  25. for (t in transitions) {
  26. if (el.style[t] !== undefined) {
  27. return transitions[t];
  28. }
  29. }
  30. }
  31. var transitionEndEvent = whichTransitionEvent();
  32. var transformProperty = Prefix.prefix('transform');
  33. var transitionProperty = Prefix.prefix('transition');
  34. //includes方法兼容
  35. if (!Array.prototype.includes) {
  36. Object.defineProperty(Array.prototype, 'includes', {
  37. value: function (valueToFind, fromIndex) {
  38. var o = Object(this);
  39. var len = o.length >>> 0;
  40. if (len === 0) {
  41. return false;
  42. }
  43. var n = fromIndex | 0;
  44. var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
  45. function sameValueZero(x, y) {
  46. return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
  47. }
  48. while (k < len) {
  49. if (sameValueZero(o[k], valueToFind)) {
  50. return true;
  51. }
  52. k++;
  53. }
  54. return false;
  55. }
  56. });
  57. }
  58. /**
  59. * ------------------------------------
  60. * 配置
  61. * @param title 标题
  62. * @param visible 组件是否可见
  63. * @param debug 是否开启调试模式(pc、mobile)
  64. * @param $container 容器(pc、mobile)
  65. * ------------------------------------
  66. * 裁剪图片
  67. * @param src 图片地址
  68. * @param size 裁剪图片目标尺寸
  69. * originImage 初始图片
  70. * _orientation 图片方向
  71. * ------------------------------------
  72. * 裁剪框
  73. * @param positionOffset 裁剪框屏幕偏移
  74. * @param boldCornerLen 裁剪框边角加粗长度
  75. * @param coverColor 遮罩背景颜色
  76. * @param cropSizePercent 裁剪框占裁剪显示区域的比例
  77. * @param borderWidth 裁剪框边框宽度
  78. * @param borderColor 裁剪框边框颜色
  79. * @param coverDraw 裁剪框辅助线绘制函数
  80. * @param borderDraw 裁剪框边框绘制函数
  81. * ------------------------------------
  82. * 功能按钮
  83. * @param funcBtns 功能按钮配置数组
  84. * @param cropCallback 图片裁剪完成回调函数
  85. * @param uploadCallback 重新上传裁剪图片回调函数
  86. * @param closeCallback 关闭裁剪组件回调函数
  87. * ------------------------------------
  88. * 滑动控制条
  89. * @param scaleSlider 是否开启滑动控制条(pc、mobile)
  90. * @param maxScale 最大缩放倍数(pc、mobile)
  91. * ------------------------------------
  92. * 旋转刻度盘
  93. * @param rotateSlider 是否开启旋转刻度盘
  94. * @param startAngle 旋转刻度盘开始角度
  95. * @param endAngle 旋转刻度盘结束角度
  96. * @param gapAngle 旋转刻度盘间隔角度
  97. * @param lineationItemWidth 旋转刻度盘间隔宽度
  98. * lineationWidth 根据上述参数计算出的旋转刻度盘总宽度
  99. * rotateWidth 旋转刻度盘显示宽度
  100. * ------------------------------------
  101. * 尺寸(为了减少计算的复杂性,所有坐标都统一为屏幕坐标及尺寸)
  102. * maskViewSize 容器屏幕尺寸
  103. * cropRect 截图框屏幕尺寸
  104. * cropPoints 裁剪框顶点坐标
  105. * cropCenter 裁剪框中心点坐标
  106. * contentWidth 图片显示宽度
  107. * contentHeight 图片显示高度
  108. * contentPoints 图片顶点坐标
  109. * initContentPoints 图片初始顶点坐标
  110. * _contentCurMoveX 图片 X 轴方向上的总位移
  111. * _contentCurMoveY 图片 Y 轴方向上的总位移
  112. * ------------------------------------
  113. * 双指缩放
  114. * _multiPoint 是否开始多点触控
  115. * fingerLen 双指距离
  116. * fingerScale 双指缩放倍数
  117. * fingerCenter 双指操作中心
  118. * ------------------------------------
  119. * 其它
  120. * times 实际尺寸/显示尺寸
  121. * initScale 初始缩放倍数
  122. * $resultCanvas 裁切结果(pc、mobile)
  123. * resultSrc 裁剪结果(wechat)
  124. * isSupportTouch 是否支持 touch 事件(pc、mobile)
  125. * passiveSupported 事件是否支持 passive(pc、mobile)
  126. */
  127. function SimpleCrop(params) {
  128. //配置
  129. this.id = 'crop-' + new Date().getTime();
  130. this.visible = params.visible != null ? params.visible : true; //默认显示
  131. this.title = params.title != null ? params.title : '';
  132. this.debug = params.debug != null ? params.debug : false;
  133. this.$container = params.$container != null ? params.$container : document.body; //容器
  134. //滑动控制条
  135. this.scaleSlider = params.scaleSlider != null ? params.scaleSlider : false;
  136. this.maxScale = params.maxScale ? params.maxScale : 1; //最大缩放倍数,默认为原始尺寸
  137. //其它
  138. var self = this;
  139. self.passiveSupported = false; //判断是否支持 passive
  140. try {
  141. var options = Object.defineProperty({}, 'passive', {
  142. get: function () {
  143. self.passiveSupported = true;
  144. }
  145. })
  146. window.addEventListener('test', null, options)
  147. } catch (err) {
  148. //nothing
  149. }
  150. this.isSupportTouch = 'ontouchend' in document ? true : false; //判断是否支持 touch 事件
  151. //操作状态
  152. this._multiPoint = false; //是否开始多点触控
  153. this._rotateScale = 1; //旋转缩放倍数
  154. this._baseMoveX = 0; //旋转刻度盘位置初始化偏移量
  155. this._curMoveX = 0; //旋转刻度盘位置当前总偏移量
  156. this._changedX = 0; //旋转刻度盘当前偏移量
  157. this._downPoints = []; //操作点坐标数组
  158. this._isControl = false; //是否正在操作
  159. this._scaleMoveX = 0; //滑动缩放控件偏移量
  160. /**
  161. * 旋转交互分为两种:
  162. * 一种是整角旋转(90度);
  163. * 另一种是基于整角旋转的基础上正负45度旋转。
  164. */
  165. this._baseAngle = 0;
  166. this.scaleTimes = 1; //缩放倍数
  167. this.rotateAngle = 0; //旋转角度
  168. this.construct();
  169. //注意 construct 和 bindEvent 中间函数的先后顺序
  170. this.initFuncBtns(params); //初始化功能按钮
  171. this.initRotateSlider(params); //初始化旋转刻度盘
  172. this.initChilds();
  173. this.initBoxBorder(params, true);
  174. this.updateBox(params);
  175. this.bindEvent();
  176. }
  177. //初始化标题
  178. SimpleCrop.prototype.initTitle = function (params) {
  179. this.title = params.title != null ? params.title : '';
  180. this.$title = document.querySelector('#' + this.id + ' .crop-title');
  181. this.$title.innerText = this.title;
  182. if (!params.title) {
  183. this.$title.style.visibility = '';
  184. } else {
  185. this.$title.style.visibility = 'hidden';
  186. }
  187. };
  188. //初始化裁剪框边框以及辅助线
  189. SimpleCrop.prototype.initBoxBorder = function (params, onlyInit) {
  190. this.borderWidth = params.borderWidth != null ? params.borderWidth : 1;
  191. this.borderColor = params.borderColor != null ? params.borderColor : '#fff';
  192. this.boldCornerLen = params.boldCornerLen != null ? params.boldCornerLen : 24;
  193. this.coverColor = params.coverColor != null ? params.coverColor : 'rgba(0,0,0,.3)';
  194. this.coverDraw = params.coverDraw != null ? params.coverDraw : function () {};
  195. this.borderDraw = params.borderDraw != null ? params.borderDraw : this.defaultBorderDraw;
  196. if (!onlyInit) {
  197. this.borderDraw(this.$cropCover);
  198. this.coverDraw(this.$cropCover);
  199. }
  200. };
  201. //初始化功能按钮
  202. SimpleCrop.prototype.initFuncBtns = function (params) {
  203. /**
  204. * 默认功能按钮为取消、裁剪、90度旋转、重置
  205. * upload 重新上传
  206. * crop 裁剪
  207. * close 取消
  208. * around 90度旋转
  209. * reset 重置
  210. */
  211. this.funcBtns = params.funcBtns != null ? params.funcBtns : ['close', 'crop', 'around', 'reset'];
  212. this.cropCallback = params.cropCallback != null ? params.cropCallback : function () {};
  213. this.closeCallback = params.closeCallback != null ? params.closeCallback : function () {};
  214. this.uploadCallback = params.uploadCallback != null ? params.uploadCallback : function () {};
  215. this.$cropBtns = document.querySelector('#' + this.id + ' .crop-btns');
  216. var html = '';
  217. if (this.funcBtns.includes('upload')) {
  218. html += '<div class="upload-btn-container">';
  219. html += '<button class="upload-btn"></button>';
  220. html += '<input type="file" accept="image/png,image/jpeg">';
  221. html += '</div>';
  222. }
  223. if (this.funcBtns.includes('close')) {
  224. html += '<button class="crop-close"></button>';
  225. }
  226. if (this.funcBtns.includes('around')) {
  227. html += '<button class="crop-around"></button>';
  228. }
  229. if (this.funcBtns.includes('reset')) {
  230. html += '<button class="crop-reset"></button>';
  231. }
  232. if (this.funcBtns.includes('crop')) {
  233. html += '<button class="crop-btn"></button>';
  234. }
  235. this.$cropBtns.innerHTML = html;
  236. //重新上传
  237. if (this.funcBtns.includes('upload')) {
  238. if (this.$uploadBtn) {
  239. this.$uploadBtn.removeEventListener('change', this.__uploadInput);
  240. } else {
  241. this.__uploadInput = this.uploadInput.bind(this);
  242. }
  243. this.$uploadBtn = document.querySelector('#' + this.id + ' .upload-btn-container');
  244. this.$uploadInput = document.querySelector('#' + this.id + ' .upload-btn-container input');
  245. this.$uploadBtn.addEventListener('change', this.__uploadInput);
  246. }
  247. //裁剪
  248. if (this.funcBtns.includes('crop')) {
  249. if (this.$cropBtn) {
  250. this.$cropBtn.removeEventListener('click', this.__crop);
  251. } else {
  252. this.__crop = this.crop.bind(this);
  253. }
  254. this.$cropBtn = document.querySelector('#' + this.id + ' .crop-btn');
  255. this.$cropBtn.addEventListener('click', this.__crop);
  256. }
  257. //整角旋转
  258. if (this.funcBtns.includes('around')) {
  259. if (this.$aroundBtn) {
  260. this.$aroundBtn.removeEventListener('click', this.__around);
  261. } else {
  262. this.__around = this.around.bind(this);
  263. }
  264. this.$aroundBtn = document.querySelector('#' + this.id + ' .crop-around');
  265. this.$aroundBtn.addEventListener('click', this.__around);
  266. }
  267. //还原
  268. if (this.funcBtns.includes('reset')) {
  269. if (this.$resetBtn) {
  270. this.$resetBtn.removeEventListener('click', this.reset);
  271. } else {
  272. this.__reset = this.reset.bind(this);
  273. }
  274. this.$resetBtn = document.querySelector('#' + this.id + ' .crop-reset');
  275. this.$resetBtn.addEventListener('click', this.__reset);
  276. }
  277. //关闭
  278. if (this.funcBtns.includes('close')) {
  279. if (this.$closeBtn) {
  280. this.$closeBtn.removeEventListener('click', this.close);
  281. } else {
  282. this.__close = this.close.bind(this);
  283. }
  284. this.$closeBtn = document.querySelector('#' + this.id + ' .crop-close');
  285. this.$closeBtn.addEventListener('click', this.__close);
  286. }
  287. if (this.funcBtns.length > 0) {
  288. this.$cropBtns.style.visibility = '';
  289. } else {
  290. this.$cropBtns.style.visibility = 'hidden';
  291. }
  292. };
  293. //初始化旋转刻度盘
  294. SimpleCrop.prototype.initRotateSlider = function (params) {
  295. this.rotateSlider = params.rotateSlider != null ? params.rotateSlider : true; //默认开启
  296. this.startAngle = params.startAngle != null ? params.startAngle : -90;
  297. this.endAngle = params.endAngle != null ? params.endAngle : 90;
  298. this.gapAngle = params.gapAngle != null ? params.gapAngle : 10;
  299. this.lineationItemWidth = params.lineationItemWidth != null ? params.lineationItemWidth : 40.5;
  300. this.lineationItemWidth = this.lineationItemWidth >= 40.5 ? this.lineationItemWidth : 40.5; // 最小宽度限制
  301. //开始角度需要小于0,结束角度需要大于0,且开始角度和结束角度之间存在大于0的整数个间隔
  302. this.startAngle = this.startAngle < 0 ? parseInt(this.startAngle) : 0;
  303. this.endAngle = this.endAngle > 0 ? parseInt(this.endAngle) : 0;
  304. this.gapAngle = this.gapAngle > 0 ? parseInt(this.gapAngle) : 3;
  305. if ((this.endAngle - this.startAngle) % this.gapAngle != 0) {
  306. this.endAngle = Math.ceil((this.endAngle - this.startAngle) / this.gapAngle) * this.gapAngle + this.startAngle;
  307. }
  308. this.lineationWidth = this.lineationItemWidth * ((this.endAngle - this.startAngle) / this.gapAngle + 1);
  309. this.$cropRotate = document.querySelector('#' + this.id + ' .crop-rotate');
  310. this.$lineation = document.querySelector('#' + this.id + ' .lineation');
  311. this.$rotateCurrent = document.querySelector('#' + this.id + ' .current');
  312. var rotateStyle = window.getComputedStyle(this.$cropRotate);
  313. this.rotateWidth = parseFloat(rotateStyle.getPropertyValue('width')); // 旋转刻度盘显示宽度
  314. this._baseMoveX = -(this.lineationWidth * (0 - this.startAngle + this.gapAngle / 2) / (this.endAngle - this.startAngle + this.gapAngle) - this.rotateWidth / 2); //开始角度大于 0 且结束角度小于 0,以 0 度为起点
  315. var angle = this.rotateAngle - this._baseAngle;
  316. this._curMoveX = angle * this.lineationWidth / (this.endAngle - this.startAngle + this.gapAngle) + this._baseMoveX;
  317. //超出滚动边界,旋转刻度盘重置
  318. if (this._curMoveX > 0 || this._curMoveX < this.rotateWidth - this.lineationWidth) {
  319. this.startControl();
  320. this._curMoveX = this._baseMoveX;
  321. this._baseAngle = 0;
  322. this.rotateAngle = 0;
  323. this._changedX = 0;
  324. this._rotateScale = 1;
  325. this.transform(true);
  326. this.endControl();
  327. }
  328. //setData
  329. var html = '';
  330. for (var i = this.startAngle; i <= this.endAngle; i += this.gapAngle) {
  331. html += '<li><div class="number">' + i + '</div><div class="bg"></div></li>';
  332. }
  333. this.$lineation.innerHTML = html;
  334. this.$lineation.style.width = this.lineationWidth + 'px';
  335. this.$lineation.style[transformProperty] = 'translateX(' + this._curMoveX + 'px)';
  336. if (this.rotateSlider) {
  337. this.$cropRotate.style.visibility = '';
  338. } else {
  339. this.$cropRotate.style.visibility = 'hidden';
  340. }
  341. };
  342. //初始化相关子元素
  343. SimpleCrop.prototype.initChilds = function () {
  344. this.$cropMask = document.querySelector('#' + this.id + ' .crop-mask');
  345. var maskStyle = window.getComputedStyle(this.$cropMask);
  346. this.maskViewSize = {
  347. width: parseInt(maskStyle.getPropertyValue('width')),
  348. height: parseInt(maskStyle.getPropertyValue('height'))
  349. }
  350. this.$cropCover = document.querySelector('#' + this.id + ' .crop-cover');
  351. this.cropCoverContext = this.$cropCover.getContext('2d');
  352. this.$cropCover.width = this.maskViewSize.width * window.devicePixelRatio;
  353. this.$cropCover.height = this.maskViewSize.height * window.devicePixelRatio;
  354. //滑动控制条
  355. this.$cropScale = document.querySelector('#' + this.id + ' .crop-scale');
  356. this.$scaleBtn = document.querySelector('#' + this.id + ' .scale-btn');
  357. this.$scaleNum = document.querySelector('#' + this.id + ' .scale-num');
  358. this.$scaleOneTimes = document.querySelector('#' + this.id + ' .one-times-icon');
  359. this.$scaleTwoTimes = document.querySelector('#' + this.id + ' .two-times-icon');
  360. this.$scaleContainer = document.querySelector('#' + this.id + ' .scale-container');
  361. this.$scaleValue = document.querySelector('#' + this.id + ' .scale-value');
  362. this.$maxScale = document.querySelector('#' + this.id + ' .max-scale');
  363. this.scaleDownX = 0;
  364. this.scaleInitLeft = this.$scaleBtn.getBoundingClientRect().left;
  365. this.scaleCurLeft = this.scaleInitLeft;
  366. this.scaleWidth = this.$scaleNum.getBoundingClientRect().width;
  367. };
  368. //根据裁剪图片目标尺寸、裁剪框显示比例、裁剪框偏移更新等参数更新并重现绘制裁剪框
  369. SimpleCrop.prototype.updateBox = function (params) {
  370. this.size = params.size;
  371. this.positionOffset = params.positionOffset != null ? params.positionOffset : {
  372. top: 0,
  373. left: 0
  374. };
  375. this.cropSizePercent = params.cropSizePercent != null ? params.cropSizePercent : 0.5; //默认0.5则表示高度或者宽度最多占50%
  376. this.times = (this.size.width / this.maskViewSize.width > this.size.height / this.maskViewSize.height) ? this.size.width / this.maskViewSize.width / this.cropSizePercent : this.size.height / this.maskViewSize.height / this.cropSizePercent;
  377. //裁剪框位置相关
  378. this.cropRect = {
  379. width: this.size.width / this.times,
  380. height: this.size.height / this.times,
  381. };
  382. this.cropRect.left = (this.maskViewSize.width - this.cropRect.width) / 2 - this.positionOffset.left;
  383. this.cropRect.top = (this.maskViewSize.height - this.cropRect.height) / 2 - this.positionOffset.top;
  384. this.cropPoints = this.rectToPoints(this.cropRect);
  385. this.cropCenter = this.getPointsCenter(this.cropPoints);
  386. this.borderDraw(this.$cropCover);
  387. this.coverDraw(this.$cropCover);
  388. var src = params.src != null ? params.src : this.src;
  389. this.setImage(src)
  390. };
  391. //获取操作点
  392. SimpleCrop.prototype.getControlPoints = function (e) {
  393. if (e.touches) {
  394. return e.touches;
  395. } else {
  396. return [{
  397. clientX: e.clientX,
  398. clientY: e.clientY
  399. }]
  400. }
  401. };
  402. //获取操作事件名称
  403. SimpleCrop.prototype.getControlEvents = function () {
  404. if (this.isSupportTouch) {
  405. return {
  406. start: 'touchstart',
  407. move: 'touchmove',
  408. end: 'touchend',
  409. cancel: 'touchcancel'
  410. }
  411. } else {
  412. return {
  413. start: 'mousedown',
  414. move: 'mousemove',
  415. end: 'mouseup',
  416. cancel: 'mouseleave'
  417. }
  418. }
  419. };
  420. //初始化滑动控制条
  421. SimpleCrop.prototype.initScaleSlider = function (params) {
  422. if (params) {
  423. this.scaleSlider = params.scaleSlider != null ? params.scaleSlider : false;
  424. this.maxScale = params.maxScale ? params.maxScale : 1; //最大缩放倍数,默认为原始尺寸
  425. }
  426. this.scaleTimes = this.initScale;
  427. this.maxScale = this.initScale < this.maxScale ? this.maxScale : Math.ceil(this.initScale);
  428. this._scaleMoveX = 0;
  429. this.scaleCurLeft = this.scaleInitLeft;
  430. this.$maxScale.innerText = '(x' + this.maxScale + ')';
  431. this.$scaleBtn.style[transformProperty] = 'translateX(0px)';
  432. this.$scaleValue.style.width = '0px';
  433. if (this.scaleSlider) {
  434. this.$cropScale.style.visibility = '';
  435. } else {
  436. this.$cropScale.style.visibility = 'hidden';
  437. }
  438. this.transform(false, true);
  439. };
  440. //html结构
  441. SimpleCrop.prototype.construct = function () {
  442. var html = '';
  443. html += '<div class="crop-component">';
  444. html += '<p class="crop-title">' + this.title + '</p>';
  445. html += '<div class="crop-mask">'
  446. html += '<canvas class="crop-cover"></canvas>';
  447. html += '</div>';
  448. //滑动控制条
  449. if (this.scaleSlider) {
  450. html += '<div class="crop-scale">';
  451. } else {
  452. html += '<div class="crop-scale" style="visibility:hidden;">';
  453. }
  454. html += '<div class="one-times-icon"></div>';
  455. html += '<div class="scale-container">';
  456. html += '<div class="scale-num"><span class="scale-value" style="width:0px;"></span><span class="scale-btn" style="left:-8px;"></span></div>';
  457. html += '</div>';
  458. html += '<div class="two-times-icon"></div>';
  459. html += '<div class="max-scale"></div>'
  460. html += '</div>';
  461. //旋转刻度盘
  462. if (this.rotateSlider) {
  463. html += '<div class="crop-rotate">'
  464. } else {
  465. html += '<div class="crop-rotate" style="visibility:hidden;">'
  466. }
  467. html += '<ul class="lineation">';
  468. html += '</ul>';
  469. html += '<div class="current"></div>';
  470. html += '</div>';
  471. //功能按钮
  472. html += '<div class="crop-btns" style="visibility:hidden;"></div>';
  473. html += '</div>';
  474. this.$target = document.createElement('div');
  475. this.$target.id = this.id;
  476. this.$target.classList.add('crop-whole-cover');
  477. if (this.visible) {
  478. this.$target.style.visibility = '';
  479. } else {
  480. this.$target.style.visibility = 'hidden';
  481. }
  482. this.$target.innerHTML = html;
  483. this.$container.appendChild(this.$target);
  484. };
  485. //默认绘制裁剪框
  486. SimpleCrop.prototype.defaultBorderDraw = function ($cropCover) {
  487. var borderWidth = this.borderWidth;
  488. var boldCornerLen = this.boldCornerLen;
  489. boldCornerLen = boldCornerLen >= borderWidth * 2 ? boldCornerLen : borderWidth * 2;
  490. var coverWidth = $cropCover.width;
  491. var coverHeight = $cropCover.height;
  492. this.cropCoverContext.clearRect(0, 0, coverWidth, coverHeight);
  493. this.cropCoverContext.fillStyle = this.coverColor;
  494. this.cropCoverContext.fillRect(0, 0, coverWidth, coverHeight);
  495. //绘制边框(边框内嵌)
  496. var borderRect = {
  497. left: this.cropRect.left * window.devicePixelRatio,
  498. top: this.cropRect.top * window.devicePixelRatio,
  499. width: this.cropRect.width * window.devicePixelRatio,
  500. height: this.cropRect.height * window.devicePixelRatio
  501. }
  502. this.cropCoverContext.fillStyle = this.borderColor;
  503. this.cropCoverContext.fillRect(borderRect.left, borderRect.top, borderRect.width, borderRect.height);
  504. if (this.boldCornerLen > 0) {
  505. //边框四个角加粗
  506. this.cropCoverContext.fillRect(borderRect.left - borderWidth, borderRect.top - borderWidth, boldCornerLen, boldCornerLen); //左上角
  507. this.cropCoverContext.fillRect(borderRect.left + borderRect.width - boldCornerLen + borderWidth, borderRect.top - borderWidth, boldCornerLen, boldCornerLen); //右上角
  508. this.cropCoverContext.fillRect(borderRect.left - borderWidth, borderRect.top + borderRect.height - boldCornerLen + borderWidth, boldCornerLen, boldCornerLen); //左下角
  509. this.cropCoverContext.fillRect(borderRect.left + borderRect.width - boldCornerLen + borderWidth, borderRect.top + borderRect.height - boldCornerLen + borderWidth, boldCornerLen, boldCornerLen); //右下角
  510. }
  511. //清空内容区域
  512. this.cropCoverContext.clearRect(borderRect.left + borderWidth, borderRect.top + borderWidth, borderRect.width - 2 * borderWidth, borderRect.height - 2 * borderWidth);
  513. };
  514. //初始化
  515. SimpleCrop.prototype.init = function () {
  516. var width = this.contentWidth / 2;
  517. var height = this.contentHeight / 2;
  518. this.initContentPoints = [{
  519. x: -width,
  520. y: height
  521. }, {
  522. x: width,
  523. y: height
  524. }, {
  525. x: width,
  526. y: -height
  527. }, {
  528. x: -width,
  529. y: -height
  530. }]
  531. this.contentPoints = this.initContentPoints.slice();
  532. //计算初始缩放倍数
  533. if (this.size.width / this.size.height > this.contentWidth / this.contentHeight) {
  534. this.initScale = this.size.width / this.contentWidth;
  535. } else {
  536. this.initScale = this.size.height / this.contentHeight;
  537. }
  538. this.reset();
  539. };
  540. //重置
  541. SimpleCrop.prototype.reset = function () {
  542. this.startControl();
  543. this._contentCurMoveX = -this.positionOffset.left;
  544. this._contentCurMoveY = -this.positionOffset.top;
  545. this._rotateScale = 1;
  546. this._baseAngle = 0;
  547. this.rotateAngle = 0;
  548. this._curMoveX = this._baseMoveX;
  549. this._changedX = 0;
  550. this.$lineation.style[transformProperty] = 'translateX(' + this._baseMoveX + 'px)';
  551. this.initScaleSlider();
  552. this.endControl();
  553. };
  554. //加载图片
  555. SimpleCrop.prototype.load = function () {
  556. var self = this;
  557. self.originImage.onload = function () {
  558. EXIF.getData(self.originImage, function () {
  559. self._orientation = EXIF.getTag(this, 'Orientation');
  560. self.getRealCotentSize();
  561. self.transformCoordinates();
  562. self.init();
  563. });
  564. }
  565. };
  566. //设置裁剪图片
  567. SimpleCrop.prototype.setImage = function (image) {
  568. if (image != null && image != '') {
  569. var self = this;
  570. var type = Object.prototype.toString.call(image);
  571. if (type === '[object String]') { //字符串
  572. self.src = image;
  573. self.originImage = new Image();
  574. self.originImage.src = self.src;
  575. self.load();
  576. self.uploadCallback(self.src);
  577. } else if (type === '[object File]') { //文件
  578. self.fileToSrc(image, function (src) {
  579. self.src = src;
  580. self.originImage = new Image();
  581. self.originImage.src = self.src;
  582. self.load();
  583. self.uploadCallback(self.src);
  584. });
  585. }
  586. }
  587. };
  588. //显示
  589. SimpleCrop.prototype.show = function (image) {
  590. if (image) {
  591. this.setImage(image);
  592. }
  593. this.visible = true;
  594. this.$target.style.visibility = '';
  595. };
  596. //隐藏
  597. SimpleCrop.prototype.hide = function () {
  598. this.visible = false;
  599. this.$target.style.visibility = 'hidden';
  600. };
  601. //关闭
  602. SimpleCrop.prototype.close = function () {
  603. this.hide();
  604. this.closeCallback();
  605. };
  606. //裁剪
  607. SimpleCrop.prototype.crop = function () {
  608. this.getCropImage();
  609. this.cropCallback(this.$resultCanvas);
  610. this.hide();
  611. };
  612. //上传
  613. SimpleCrop.prototype.uploadInput = function (evt) {
  614. var files = evt.target.files;
  615. if (files.length > 0) {
  616. this.show(files[0]);
  617. }
  618. this.$uploadInput.value = ''; //清空value属性,从而保证用户修改文件内容但是没有修改文件名时依然能上传成功
  619. };
  620. //整角旋转
  621. SimpleCrop.prototype.around = function () {
  622. this.startControl();
  623. this.rotateAngle = (this._baseAngle - 90) % 360;
  624. this._baseAngle = this.rotateAngle;
  625. this._curMoveX = this._baseMoveX;
  626. this._changedX = 0;
  627. this._rotateScale = 1;
  628. this.$lineation.style[transformProperty] = 'translateX(' + this._baseMoveX + 'px)';
  629. this.transform(false, true);
  630. this.endControl();
  631. };
  632. //事件监听
  633. SimpleCrop.prototype.bindEvent = function () {
  634. var self = this;
  635. var controlEvents = this.getControlEvents();
  636. //滑动控制条
  637. // ------------------ //
  638. self.$scaleBtn.addEventListener(controlEvents.start, function (ev) {
  639. self.scaleDownX = self.getControlPoints(ev)[0].clientX;
  640. self.startControl();
  641. });
  642. self.$scaleContainer.addEventListener(controlEvents.move, function (ev) {
  643. self.scaleMove(ev);
  644. ev.stopPropagation();
  645. });
  646. self.$scaleContainer.addEventListener(controlEvents.cancel, self.endControl.bind(self)); //结束
  647. self.$scaleContainer.addEventListener(controlEvents.end, self.endControl.bind(self));
  648. self.$scaleContainer.addEventListener('click', function (ev) {
  649. self.startControl();
  650. var rect = self.$scaleBtn.getBoundingClientRect();
  651. if (self.scaleDownX <= 0) {
  652. self.scaleDownX = rect.left + rect.width * 1.0 / 2;
  653. }
  654. self.scaleMove(ev);
  655. self.endControl();
  656. });
  657. self.$scaleOneTimes.addEventListener('click', function () { //极小
  658. self.startControl();
  659. self.scaleMoveAt(0);
  660. self.endControl();
  661. });
  662. self.$scaleTwoTimes.addEventListener('click', function () { //极大
  663. self.startControl();
  664. self.scaleMoveAt(self.scaleWidth);
  665. self.endControl();
  666. });
  667. // ------------------ //
  668. //旋转刻度盘
  669. // ------------------ //
  670. self.$cropRotate.addEventListener(controlEvents.start, function (e) {
  671. var touches = self.getControlPoints(e);
  672. self.startControl(touches);
  673. });
  674. self.$cropRotate.addEventListener(controlEvents.move, function (e) {
  675. var touches = self.getControlPoints(e);
  676. if (self._downPoints && self._downPoints.length > 0 && !self._multiPoint) {
  677. var point = touches[0];
  678. var moveX = point.clientX - self._downPoints[0].clientX;
  679. var lastMoveX = self._curMoveX;
  680. var curMoveX = lastMoveX + moveX;
  681. if (curMoveX <= 0 && curMoveX >= (self.rotateWidth - self.lineationWidth)) { //滚动边界
  682. var angle = (curMoveX - self._baseMoveX) / self.lineationWidth * (self.endAngle - self.startAngle + self.gapAngle);
  683. self._curMoveX = curMoveX;
  684. self._changedX = moveX;
  685. self.$lineation.style[transformProperty] = 'translateX(' + curMoveX + 'px)';
  686. self.rotateAngle = self._baseAngle + angle;
  687. self.transform(true);
  688. self._downPoints = touches;
  689. }
  690. }
  691. e.stopPropagation(); //阻止事件冒泡
  692. e.preventDefault();
  693. });
  694. self.$cropRotate.addEventListener(controlEvents.end, self.endControl.bind(self)); //结束
  695. self.$cropRotate.addEventListener(controlEvents.cancel, self.endControl.bind(self));
  696. // ------------------ //
  697. //裁剪图片移动、双指缩放操作
  698. // ------------------ //
  699. var $imageListenerEle = self.isSupportTouch ? self.$container : self.$cropMask;
  700. $imageListenerEle.addEventListener(controlEvents.start, function (ev) {
  701. var touches = self.getControlPoints(ev);
  702. self.startControl(touches);
  703. self._multiPoint = false;
  704. if (self._downPoints && self._downPoints.length >= 2) {
  705. self._multiPoint = true;
  706. var center = {
  707. clientX: (self._downPoints[0].clientX + self._downPoints[1].clientX) / 2,
  708. clientY: (self._downPoints[0].clientY + self._downPoints[1].clientY) / 2
  709. };
  710. self.fingerLen = Math.sqrt(Math.pow(self._downPoints[0].clientX - self._downPoints[1].clientX, 2) + Math.pow(self._downPoints[0].clientY - self._downPoints[1].clientY, 2));
  711. self.fingerScale = 1;
  712. self.fingerCenter = { //双指操作中心
  713. x: center.clientX - self.maskViewSize.width / 2,
  714. y: self.maskViewSize.height / 2 - center.clientY
  715. }
  716. }
  717. });
  718. var options = self.passiveSupported ? { // 如果浏览器支持 passive event listener 为了保证截图操作时页面不滚动需要设置为 false
  719. passive: false,
  720. capture: false
  721. } : false;
  722. $imageListenerEle.addEventListener(controlEvents.move, function (ev) {
  723. var touches = self.getControlPoints(ev);
  724. if (self._downPoints && self._downPoints.length > 0) {
  725. if (!self._multiPoint) { // 单指移动
  726. self.contentMove(touches);
  727. } else { // 双指缩放
  728. var newFingerLen = Math.sqrt(Math.pow(touches[0].clientX - touches[1].clientX, 2) + Math.pow(touches[0].clientY - touches[1].clientY, 2));
  729. var newScale = newFingerLen / self.fingerLen;
  730. self.scaleTimes = self.scaleTimes / self.fingerScale * newScale;
  731. var translate = self.getFingerScaleTranslate(newScale / self.fingerScale);
  732. self._contentCurMoveX -= translate.translateX;
  733. self._contentCurMoveY += translate.translateY;
  734. self.fingerScale = newScale;
  735. self.transform(false, true);
  736. }
  737. }
  738. if (self.visible) { //组件显示状态才屏蔽掉一些事件的默认行为,防止组件关闭后继续影响页面
  739. ev.preventDefault();
  740. }
  741. }, options);
  742. $imageListenerEle.addEventListener(controlEvents.end, self.endControl.bind(self)); //结束
  743. $imageListenerEle.addEventListener(controlEvents.cancel, self.endControl.bind(self));
  744. // ------------------ //
  745. };
  746. //双指缩放优化为以双指中心为基础点,实际变换以中心点为基准点,因此需要计算两者的偏移
  747. SimpleCrop.prototype.getFingerScaleTranslate = function (scale) {
  748. var fingerPoints = []; //以双指中心缩放的新坐标
  749. var center = this.getPointsCenter(this.contentPoints); //中心点不变
  750. for (var i = 0; i < this.contentPoints.length; i++) {
  751. var point = this.contentPoints[i];
  752. fingerPoints.push({
  753. x: point.x * scale - this.fingerCenter.x * (scale - 1),
  754. y: point.y * scale - this.fingerCenter.y * (scale - 1)
  755. })
  756. }
  757. var newCenter = this.getPointsCenter(fingerPoints);
  758. return {
  759. translateX: center.x - newCenter.x,
  760. translateY: center.y - newCenter.y
  761. }
  762. }
  763. //根据图片方向计算源图片实际宽高
  764. SimpleCrop.prototype.getRealCotentSize = function () {
  765. this.contentWidth = this.originImage.width;
  766. this.contentHeight = this.originImage.height;
  767. //图片方向大于 4 时宽高互换
  768. if (this._orientation > 4) {
  769. this.contentWidth = this.originImage.height;
  770. this.contentHeight = this.originImage.width;
  771. }
  772. }
  773. //处理图片方向
  774. SimpleCrop.prototype.transformCoordinates = function () {
  775. if (this.$cropContent) {
  776. this.$cropMask.removeChild(this.$cropContent);
  777. }
  778. this.$cropContent = document.createElement('canvas');
  779. this.$cropContent.width = this.contentWidth;
  780. this.$cropContent.height = this.contentHeight;
  781. var imageCtx = this.$cropContent.getContext('2d');
  782. var width = this.originImage.width;
  783. var height = this.originImage.height;
  784. switch (this._orientation) {
  785. case 2:
  786. // horizontal flip
  787. imageCtx.translate(width, 0);
  788. imageCtx.scale(-1, 1);
  789. break;
  790. case 3:
  791. // 180° rotate left
  792. imageCtx.translate(width, height);
  793. imageCtx.rotate(Math.PI);
  794. break;
  795. case 4:
  796. // vertical flip
  797. imageCtx.translate(0, height);
  798. imageCtx.scale(1, -1);
  799. break;
  800. case 5:
  801. // vertical flip + 90 rotate right
  802. imageCtx.rotate(0.5 * Math.PI);
  803. imageCtx.scale(1, -1);
  804. break;
  805. case 6:
  806. // 90° rotate right
  807. imageCtx.rotate(0.5 * Math.PI);
  808. imageCtx.translate(0, -height);
  809. break;
  810. case 7:
  811. // horizontal flip + 90 rotate right
  812. imageCtx.rotate(0.5 * Math.PI);
  813. imageCtx.translate(width, -height);
  814. imageCtx.scale(-1, 1);
  815. break;
  816. case 8:
  817. // 90° rotate left
  818. imageCtx.rotate(-0.5 * Math.PI);
  819. imageCtx.translate(-width, 0);
  820. break;
  821. }
  822. imageCtx.drawImage(this.originImage, 0, 0, width, height);
  823. //初始位置垂直水平居中
  824. this._initTransform = 'translate3d(-50%,-50%,0)';
  825. this.$cropContent.classList.add('crop-content');
  826. this.$cropContent.style.position = 'absolute';
  827. this.$cropContent.style.left = '50%';
  828. this.$cropContent.style.top = '50%';
  829. this.$cropContent.style[transformProperty] = this._initTransform;
  830. this.$cropMask.insertBefore(this.$cropContent, this.$cropCover);
  831. }
  832. //获取裁剪图片
  833. SimpleCrop.prototype.getCropImage = function () {
  834. var scaleNum = this.scaleTimes / this.times * this._rotateScale;
  835. var contentWidth = this.contentWidth;
  836. var contentHeight = this.contentHeight;
  837. var cropWidth = this.size.width;
  838. var cropHeight = this.size.height;
  839. //需要考虑裁剪图片实际尺寸比裁剪尺寸小的情况
  840. var ratio = 1;
  841. if (contentWidth < cropWidth || contentHeight < cropHeight) { // 裁剪中间画布尺寸必须大于实际裁剪尺寸
  842. if (cropWidth / contentWidth > cropHeight / contentHeight) {
  843. ratio = cropWidth / contentWidth;
  844. contentHeight = contentHeight * ratio;
  845. contentWidth = cropWidth;
  846. } else {
  847. ratio = cropHeight / contentHeight;
  848. contentWidth = contentWidth * ratio
  849. contentHeight = cropHeight;
  850. }
  851. }
  852. var center = {
  853. x: contentWidth / 2,
  854. y: contentHeight / 2
  855. };
  856. var $contentCanvas = document.createElement('canvas');
  857. $contentCanvas.width = contentWidth;
  858. $contentCanvas.height = contentHeight;
  859. var contentCtx = $contentCanvas.getContext('2d');
  860. contentCtx.translate(center.x, center.y);
  861. contentCtx.scale(scaleNum * this.times / ratio, scaleNum * this.times / ratio) // 缩放 this.times
  862. contentCtx.translate((this._contentCurMoveX + this.positionOffset.left) / scaleNum * ratio, (this._contentCurMoveY + this.positionOffset.top) / scaleNum * ratio);
  863. contentCtx.rotate(this.rotateAngle / 180 * Math.PI);
  864. contentCtx.translate(-center.x, -center.y);
  865. contentCtx.drawImage(this.$cropContent, 0, 0, contentWidth, contentHeight);
  866. var $cropCanvas = document.createElement('canvas');
  867. $cropCanvas.width = cropWidth;
  868. $cropCanvas.height = cropHeight;
  869. var cropCtx = $cropCanvas.getContext('2d');
  870. cropCtx.drawImage($contentCanvas, (contentWidth - cropWidth) / 2, (contentHeight - cropHeight) / 2, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
  871. this.$resultCanvas = $cropCanvas;
  872. };
  873. //操作结束
  874. SimpleCrop.prototype.endControl = function () {
  875. if (this._isControl) {
  876. var self = this;
  877. this._downPoints = [];
  878. this.scaleDownX = 0;
  879. if (this.$cropContent && !this.isWholeCover(this.contentPoints, this.cropPoints)) { //如果没有完全包含则需要进行适配变换
  880. var scaleNum = this.scaleTimes / this.times * this._rotateScale;
  881. var transform = '';
  882. transform += ' scale(' + scaleNum + ')'; //缩放
  883. transform += ' translateX(' + this._contentCurMoveX / scaleNum + 'px) translateY(' + this._contentCurMoveY / scaleNum + 'px)'; //移动
  884. transform += ' rotate(' + this.rotateAngle + 'deg) ';
  885. //适配变换
  886. var coverTr = this.getCoverTransform(transform);
  887. var coverMat = this.getTransformMatrix(coverTr);
  888. this._contentCurMoveX = coverMat.e;
  889. this._contentCurMoveY = coverMat.f;
  890. this.contentPoints = this.getTransformPoints('scaleY(-1)' + coverTr, this.initContentPoints);
  891. if (!this.debug) {
  892. this.$cropContent.style[transformProperty] = this._initTransform + coverTr;
  893. } else {
  894. this.$cropContent.style[transitionProperty] = 'transform .5s linear';
  895. var start = coverTr.indexOf(transform) + transform.length;
  896. var coverTrAr = coverTr.substring(start, coverTr.length).trim().split(' ');
  897. var no = 0;
  898. var tr = this._initTransform + transform + coverTrAr[no];
  899. this.$cropContent.style[transformProperty] = tr;
  900. this.$cropContent.addEventListener(transitionEndEvent, function () {
  901. no++;
  902. if (no < coverTr.length) {
  903. tr += coverTrAr[no];
  904. self.$cropContent.style[transformProperty] = tr;
  905. }
  906. });
  907. }
  908. }
  909. this._isControl = false;
  910. }
  911. };
  912. //操作开始
  913. SimpleCrop.prototype.startControl = function (touches) {
  914. touches = touches ? touches : [];
  915. if (!this._isControl || this.isTwoFingerEvent(touches)) {
  916. this._isControl = true;
  917. if (this.$cropContent) {
  918. this.$cropContent.style[transitionProperty] = 'none';
  919. }
  920. this._downPoints = touches;
  921. }
  922. };
  923. //双指操作事件
  924. SimpleCrop.prototype.isTwoFingerEvent = function (touches) {
  925. /**
  926. * 微信小程序双指操作时,会触发两次 touchstart 事件且前后两次事件触摸点坐标有一个坐标相同
  927. */
  928. if (this._isControl && this._downPoints && this._downPoints.length == 1 && touches.length >= 2 &&
  929. ((touches[0].clientX == this._downPoints[0].clientX && touches[0].clientY == this._downPoints[0].clientY) || (touches[1].clientX == this._downPoints[0].clientX && touches[1].clientY == this._downPoints[0].clientY))) {
  930. return true;
  931. }
  932. return false;
  933. };
  934. //滑动控制条按钮移动
  935. SimpleCrop.prototype.scaleMove = function (ev) {
  936. if (this.scaleDownX > 0) {
  937. var pointX = this.getControlPoints(ev)[0].clientX;
  938. var moveX = pointX - this.scaleDownX;
  939. var newCurLeft = this.scaleCurLeft + moveX;
  940. if (newCurLeft >= this.scaleInitLeft && newCurLeft <= (this.scaleWidth + this.scaleInitLeft)) {
  941. var lastMoveX = this._scaleMoveX;
  942. if (!lastMoveX) {
  943. lastMoveX = 0;
  944. }
  945. var curMoveX = lastMoveX + moveX;
  946. this.scaleDownX = pointX;
  947. this.scaleMoveAt(curMoveX);
  948. }
  949. }
  950. }
  951. //移动滑动控制条按钮到某个位置
  952. SimpleCrop.prototype.scaleMoveAt = function (curMoveX) {
  953. this.$scaleBtn.style[transformProperty] = 'translateX(' + curMoveX + 'px)';
  954. this.$scaleValue.style.width = curMoveX + 'px';
  955. this._scaleMoveX = curMoveX;
  956. this.scaleCurLeft = this.scaleInitLeft + curMoveX;
  957. this.scaleTimes = this.initScale + curMoveX * 1.0 / this.scaleWidth * (this.maxScale - this.initScale);
  958. this.transform(false, true);
  959. };
  960. //内容图片移动
  961. SimpleCrop.prototype.contentMove = function (touches) {
  962. var point = touches[0];
  963. var moveX = point.clientX - this._downPoints[0].clientX;
  964. var moveY = point.clientY - this._downPoints[0].clientY;
  965. this._contentCurMoveX += moveX;
  966. this._contentCurMoveY += moveY;
  967. this._downPoints = touches;
  968. this.transform();
  969. };
  970. //旋转、缩放、移动
  971. SimpleCrop.prototype.transform = function (rotateCover, scaleKeepCover) {
  972. if (this.$cropContent) {
  973. var scaleNum = this.scaleTimes / this.times * this._rotateScale;
  974. var transform = '';
  975. transform += ' scale(' + scaleNum + ')'; //缩放
  976. transform += ' translateX(' + this._contentCurMoveX / scaleNum + 'px) translateY(' + this._contentCurMoveY / scaleNum + 'px)'; //移动
  977. transform += ' rotate(' + this.rotateAngle + 'deg)';
  978. if (scaleKeepCover) { //缩放时为了保证裁剪框不出现空白,需要在原有变换的基础上再进行一定的位移变换
  979. transform = this.getCoverTransform(transform, true);
  980. var scMat = this.getTransformMatrix(transform);
  981. this._contentCurMoveX = scMat.e;
  982. this._contentCurMoveY = scMat.f;
  983. }
  984. if (rotateCover) { //旋转时需要保证裁剪框不出现空白,需要在原有变换的基础上再进行一定的适配变换
  985. var rotatePoints = this.getTransformPoints('scaleY(-1)' + transform, this.initContentPoints);
  986. var coverScale = this.getCoverRectScale(rotatePoints, this.cropPoints);
  987. var changedX = this._changedX;
  988. var curMoveX = this._curMoveX;
  989. var totalMoveX = curMoveX - changedX - this._baseMoveX;
  990. var rotateCenter = this.getPointsCenter(rotatePoints);
  991. var centerVec = {
  992. x: rotateCenter.x - this.cropCenter.x,
  993. y: rotateCenter.y - this.cropCenter.y
  994. }
  995. var percent = Math.abs(changedX) / Math.abs(totalMoveX);
  996. if (coverScale > 1) {
  997. this._rotateScale = this._rotateScale * coverScale;
  998. scaleNum = scaleNum * coverScale;
  999. } else if (this.vecLen(centerVec) < 1 && percent > 0) { //中心点接近重合时,旋转支持自适应缩小
  1000. if (coverScale < (1 - percent)) { //不能突变
  1001. coverScale = 1 - percent;
  1002. }
  1003. if (this._rotateScale * coverScale > 1) {
  1004. this._rotateScale = this._rotateScale * coverScale;
  1005. } else { //不能影响 scaleTimes
  1006. this._rotateScale = 1;
  1007. coverScale = 1;
  1008. }
  1009. scaleNum = scaleNum * coverScale;
  1010. }
  1011. }
  1012. //操作变换
  1013. transform = '';
  1014. transform += ' scale(' + scaleNum + ')'; //缩放
  1015. transform += ' translateX(' + this._contentCurMoveX / scaleNum + 'px) translateY(' + this._contentCurMoveY / scaleNum + 'px)'; //移动
  1016. transform += ' rotate(' + this.rotateAngle + 'deg)';
  1017. this.$cropContent.style[transformProperty] = this._initTransform + transform;
  1018. this.contentPoints = this.getTransformPoints('scaleY(-1)' + transform, this.initContentPoints);
  1019. }
  1020. };
  1021. //计算一个矩形刚好包含另一个矩形需要的缩放倍数
  1022. SimpleCrop.prototype.getCoverRectScale = function (outer, inner) {
  1023. var scale = 0;
  1024. for (var i = 0; i < inner.length; i++) {
  1025. var num = this.getCoverPointScale(inner[i], outer);
  1026. if (num > scale) {
  1027. scale = num;
  1028. }
  1029. }
  1030. return scale;
  1031. };
  1032. //判断 矩形A 是否完全包含 矩形B
  1033. SimpleCrop.prototype.isWholeCover = function (rectA, rectB) {
  1034. for (var i = 0; i < rectB.length; i++) {
  1035. if (!this.isPointInRectCheckByLen(rectB[i], rectA)) {
  1036. return false;
  1037. }
  1038. }
  1039. return true;
  1040. };
  1041. //计算一个矩形刚好包含矩形外一点需要的缩放倍数
  1042. SimpleCrop.prototype.getCoverPointScale = function (point, rectPoints) {
  1043. var pcv = this.getPCVectorProjOnUpAndRight(point, rectPoints);
  1044. //计算矩形外一点到矩形中心向量在矩形边框向量上的投影距离
  1045. var uLen = this.vecLen(pcv.uproj);
  1046. var height = this.vecLen(pcv.up) / 2;
  1047. var rLen = this.vecLen(pcv.rproj);
  1048. var width = this.vecLen(pcv.right) / 2;
  1049. //根据投影距离计算缩放倍数
  1050. if (uLen / height > rLen / width) {
  1051. return 1 + (uLen - height) / height;
  1052. } else {
  1053. return 1 + (rLen - width) / width;
  1054. }
  1055. };
  1056. //计算图片内容刚好包含裁剪框的transform变换
  1057. SimpleCrop.prototype.getCoverTransform = function (transform, onlyTranslate) {
  1058. var cRect = this.getCoveRect(this.cropPoints, this.rotateAngle);
  1059. onlyTranslate = onlyTranslate ? onlyTranslate : false;
  1060. //计算放大倍数
  1061. var uScale = 1; //水平缩放倍数和垂直缩放倍数
  1062. var rScale = 1;
  1063. var cup = {
  1064. x: this.contentPoints[1].x - this.contentPoints[2].x,
  1065. y: this.contentPoints[1].y - this.contentPoints[2].y
  1066. }
  1067. var cright = {
  1068. x: this.contentPoints[1].x - this.contentPoints[0].x,
  1069. y: this.contentPoints[1].y - this.contentPoints[0].y
  1070. }
  1071. var tup = {
  1072. x: cRect[1].x - cRect[2].x,
  1073. y: cRect[1].y - cRect[2].y
  1074. }
  1075. var tright = {
  1076. x: cRect[1].x - cRect[0].x,
  1077. y: cRect[1].y - cRect[0].y
  1078. }
  1079. var uAng = this.vecAngle(cup, tup);
  1080. if (Math.abs(180 - uAng) < Math.abs(90 - uAng) || Math.abs(0 - uAng) < Math.abs(90 - uAng)) { //更接近180或者0
  1081. uScale = this.vecLen(tup) / this.vecLen(cup);
  1082. rScale = this.vecLen(tright) / this.vecLen(cright);
  1083. } else {
  1084. uScale = this.vecLen(tup) / this.vecLen(cright);
  1085. rScale = this.vecLen(tright) / this.vecLen(cup);
  1086. }
  1087. uScale = uScale < 1 ? 1 : uScale;
  1088. rScale = rScale < 1 ? 1 : rScale;
  1089. var scale = uScale > rScale ? uScale : rScale;
  1090. if (onlyTranslate && scale > 1) {
  1091. return transform;
  1092. }
  1093. //复制坐标
  1094. var scalePoints = [];
  1095. for (var i = 0; i < this.contentPoints.length; i++) {
  1096. scalePoints.push({
  1097. x: this.contentPoints[i].x,
  1098. y: this.contentPoints[i].y
  1099. });
  1100. }
  1101. //计算放大后的新坐标
  1102. if (scale > 1) {
  1103. transform += ' scale(' + scale + ')';
  1104. this.scaleTimes = this.scaleTimes * scale;
  1105. //this._rotateScale = this._rotateScale * scale; // this._rotateScale 只能受旋转角度影响
  1106. scalePoints = this.getTransformPoints('scaleY(-1)' + transform, this.initContentPoints);
  1107. }
  1108. //位移变换
  1109. var scaleNum = this.scaleTimes / this.times * this._rotateScale;
  1110. var count = 0;
  1111. var self = this;
  1112. var outDetails = [];
  1113. do {
  1114. //找出裁剪框超出的顶点
  1115. outDetails = this.getOutDetails(this.cropPoints, scalePoints);
  1116. if (outDetails.length > 0) {
  1117. count++;
  1118. outDetails.sort(function (a, b) { //找出距离最远的点
  1119. var aLen = self.vecLen(a.iv);
  1120. var bLen = self.vecLen(b.iv);
  1121. if (aLen < bLen) {
  1122. return 1;
  1123. }
  1124. if (aLen > bLen) {
  1125. return -1;
  1126. }
  1127. return 0;
  1128. });
  1129. //开始移动
  1130. var maxFarOut = outDetails[0];
  1131. var maxFarPcv = maxFarOut.pcv;
  1132. //计算X轴位移
  1133. uAng = this.vecAngle(maxFarPcv.up, maxFarPcv.uproj);
  1134. var uLen = this.vecLen(maxFarPcv.uproj);
  1135. var moveY = 0;
  1136. //if(uAng == 0){ //同方向
  1137. if (Math.abs(uAng) < 90) { //浮点数精度问题,接近0时小于90 ,接近180时大于90
  1138. moveY = -uLen * maxFarOut.uOver;
  1139. } else {
  1140. moveY = uLen * maxFarOut.uOver;
  1141. }
  1142. if (moveY != 0) {
  1143. transform += ' translateY(' + moveY / scaleNum + 'px)';
  1144. }
  1145. //计算Y轴位移
  1146. var rAng = this.vecAngle(maxFarPcv.right, maxFarPcv.rproj);
  1147. var rLen = this.vecLen(maxFarPcv.rproj);
  1148. var moveX = 0;
  1149. if (Math.abs(rAng) < 90) { //同方向
  1150. moveX = rLen * maxFarOut.rOver;
  1151. } else {
  1152. moveX = -rLen * maxFarOut.rOver;
  1153. }
  1154. if (moveX != 0) {
  1155. transform += ' translateX(' + moveX / scaleNum + 'px)';
  1156. }
  1157. //计算位移后的新坐标
  1158. if (moveX != 0 || moveY != 0) {
  1159. for (i = 0; i < scalePoints.length; i++) {
  1160. scalePoints[i].x = scalePoints[i].x + maxFarOut.iv.x,
  1161. scalePoints[i].y = scalePoints[i].y + maxFarOut.iv.y;
  1162. }
  1163. }
  1164. }
  1165. } while (count < 2 && outDetails.length > 0)
  1166. return transform;
  1167. }
  1168. //找出一个矩形在另一个矩形外的顶点数据
  1169. SimpleCrop.prototype.getOutDetails = function (inner, outer) {
  1170. var outDetails = [];
  1171. for (var i = 0; i < inner.length; i++) {
  1172. var pt = inner[i];
  1173. if (!this.isPointInRectCheckByLen(pt, outer)) {
  1174. var pcv = this.getPCVectorProjOnUpAndRight(pt, outer);
  1175. var iv = {
  1176. x: 0,
  1177. y: 0
  1178. };
  1179. var uLen = this.vecLen(pcv.uproj);
  1180. var height = this.vecLen(pcv.up) / 2;
  1181. var rLen = this.vecLen(pcv.rproj);
  1182. var width = this.vecLen(pcv.right) / 2;
  1183. var uOver = 0;
  1184. var rOver = 0;
  1185. if (uLen > height) {
  1186. uOver = (uLen - height) / uLen;
  1187. iv.x += pcv.uproj.x * uOver;
  1188. iv.y += pcv.uproj.y * uOver;
  1189. }
  1190. if (rLen > width) {
  1191. rOver = (rLen - width) / rLen;
  1192. iv.x += pcv.rproj.x * rOver;
  1193. iv.y += pcv.rproj.y * rOver;
  1194. }
  1195. outDetails.push({
  1196. x: pt.x,
  1197. y: pt.y,
  1198. iv: iv,
  1199. uOver: uOver,
  1200. rOver: rOver,
  1201. pcv: pcv
  1202. });
  1203. }
  1204. }
  1205. return outDetails;
  1206. }
  1207. //获取刚好包含某个矩形的新矩形
  1208. SimpleCrop.prototype.getCoveRect = function (rect, angle) {
  1209. if (angle < 0) {
  1210. angle = 90 + angle % 90;
  1211. } else {
  1212. angle = angle % 90;
  1213. }
  1214. var rad = angle / 180 * Math.PI;
  1215. var up = {
  1216. x: rect[1].x - rect[2].x,
  1217. y: rect[1].y - rect[2].y
  1218. }
  1219. var right = {
  1220. x: rect[1].x - rect[0].x,
  1221. y: rect[1].y - rect[0].y
  1222. }
  1223. var rLen = this.vecLen(right);
  1224. var uLen = this.vecLen(up);
  1225. var nRect = [];
  1226. nRect[0] = {};
  1227. nRect[0].x = rect[0].x + rLen * Math.sin(rad) * Math.sin(rad);
  1228. nRect[0].y = rect[0].y + rLen * Math.sin(rad) * Math.cos(rad);
  1229. nRect[1] = {};
  1230. nRect[1].x = rect[1].x + uLen * Math.sin(rad) * Math.cos(rad);
  1231. nRect[1].y = rect[1].y - uLen * Math.sin(rad) * Math.sin(rad);
  1232. nRect[2] = {};
  1233. nRect[2].x = rect[2].x - rLen * Math.sin(rad) * Math.sin(rad);
  1234. nRect[2].y = rect[2].y - rLen * Math.sin(rad) * Math.cos(rad);
  1235. nRect[3] = {};
  1236. nRect[3].x = rect[3].x - uLen * Math.sin(rad) * Math.cos(rad);
  1237. nRect[3].y = rect[3].y + uLen * Math.sin(rad) * Math.sin(rad);
  1238. return nRect;
  1239. }
  1240. //计算新的变换坐标
  1241. SimpleCrop.prototype.getTransformPoints = function (transform, points) {
  1242. var matrix = this.getTransformMatrix(transform);
  1243. var nPoints = [];
  1244. for (var i = 0; i < points.length; i++) {
  1245. var item = {
  1246. x: points[i].x,
  1247. y: points[i].y
  1248. };
  1249. item = TransformationMatrix.applyToPoint(matrix, item);
  1250. nPoints.push(item);
  1251. }
  1252. nPoints.reverse(); //顶点顺序发生了变化,需要颠倒
  1253. return nPoints;
  1254. }
  1255. //获得矩形点坐标中心
  1256. SimpleCrop.prototype.getPointsCenter = function (points) {
  1257. var center = {
  1258. x: (points[0].x + points[2].x) / 2,
  1259. y: (points[0].y + points[2].y) / 2,
  1260. }
  1261. return center;
  1262. };
  1263. //矩形位置形式转换为顶点坐标形式
  1264. SimpleCrop.prototype.rectToPoints = function (rect) {
  1265. var points = [];
  1266. points.push({
  1267. x: -(this.maskViewSize.width / 2 - rect.left),
  1268. y: this.maskViewSize.height / 2 - rect.top
  1269. });
  1270. points.push({
  1271. x: points[0].x + rect.width,
  1272. y: points[0].y
  1273. });
  1274. points.push({
  1275. x: points[1].x,
  1276. y: points[1].y - rect.height
  1277. });
  1278. points.push({
  1279. x: points[0].x,
  1280. y: points[2].y
  1281. });
  1282. return points;
  1283. };
  1284. //获取 css transform 属性对应的矩形形式
  1285. SimpleCrop.prototype.getTransformMatrix = function (transform) {
  1286. var transforms = transform.split(' ');
  1287. var params = [];
  1288. for (var i = 0; i < transforms.length; i++) {
  1289. if (transforms[i].trim() != '') { // 不能为空
  1290. var func = this.getTransformFunctionName(transforms[i]);
  1291. var result;
  1292. if (func.name != 'rotate') {
  1293. result = TransformationMatrix[func.name](func.params[0], func.params[1]);
  1294. } else {
  1295. result = TransformationMatrix[func.name](func.params[0]);
  1296. }
  1297. params.push(result);
  1298. }
  1299. }
  1300. return TransformationMatrix.compose(params);
  1301. };
  1302. //根据 css transform 属性获取 transformation-matrix 对应的函数名称以及参数
  1303. SimpleCrop.prototype.getTransformFunctionName = function (transform) {
  1304. var start = transform.indexOf('(');
  1305. var end = transform.indexOf(')');
  1306. var func = {};
  1307. //参数
  1308. var params = transform.substring(start + 1, end).split(',');
  1309. var arr = [];
  1310. for (var i = 0; i < params.length; i++) {
  1311. arr.push(parseFloat(params[i]));
  1312. }
  1313. func.params = arr;
  1314. //名称
  1315. var name = transform.substring(0, start).toLowerCase();
  1316. var defParams = 0; //默认参数
  1317. if (name.indexOf('scale') != -1) {
  1318. func.name = 'scale';
  1319. defParams = 1;
  1320. } else if (name.indexOf('translate') != -1) {
  1321. func.name = 'translate';
  1322. } else if (name.indexOf('skew') != -1) {
  1323. func.name = 'skewDEG';
  1324. } else if (name.indexOf('rotate') != -1) {
  1325. func.name = 'rotateDEG'; // 角度
  1326. }
  1327. //加入默认参数
  1328. if (name.indexOf('x') != -1) {
  1329. func.params.push(defParams);
  1330. } else if (name.indexOf('y') != -1) {
  1331. func.params.unshift(defParams);
  1332. } else if (name.indexOf('rotate') == -1 && func.params.length <= 1) { // 除了 rotate 其它函数支持 x、y 两个参数,如果 css transform 属性参数只有一个则另一个参数也是如此。
  1333. func.params.push(func.params[0]);
  1334. }
  1335. return func;
  1336. };
  1337. //计算向量 a 在向量 b 上的投影向量
  1338. SimpleCrop.prototype.getProjectionVector = function (vecA, vecB) {
  1339. var bLen = this.vecLen(vecB);
  1340. var ab = vecA.x * vecB.x + vecA.y * vecB.y;
  1341. var proj = {
  1342. x: ab / Math.pow(bLen, 2) * vecB.x,
  1343. y: ab / Math.pow(bLen, 2) * vecB.y,
  1344. }
  1345. return proj
  1346. };
  1347. //计算矩形中心到某点的向量在矩形自身坐标系上方向和右方向上的投影向量
  1348. SimpleCrop.prototype.getPCVectorProjOnUpAndRight = function (point, rectPoints) {
  1349. //计算矩形自身坐标系的上方向向量和右方向向量
  1350. var up = {
  1351. x: rectPoints[1].x - rectPoints[2].x,
  1352. y: rectPoints[1].y - rectPoints[2].y
  1353. }
  1354. var right = {
  1355. x: rectPoints[1].x - rectPoints[0].x,
  1356. y: rectPoints[1].y - rectPoints[0].y
  1357. }
  1358. //计算矩形中心点
  1359. var center = this.getPointsCenter(rectPoints);
  1360. var line = {
  1361. x: point.x - center.x,
  1362. y: point.y - center.y
  1363. }
  1364. var uproj = this.getProjectionVector(line, up);
  1365. var rproj = this.getProjectionVector(line, right);
  1366. return {
  1367. up: up,
  1368. uproj: uproj,
  1369. right: right,
  1370. rproj: rproj
  1371. };
  1372. };
  1373. //根据矩形中心到某一点向量在矩形边框向量的投影长度判断该点是否在矩形内
  1374. SimpleCrop.prototype.isPointInRectCheckByLen = function (point, rectPoints) {
  1375. var pcv = this.getPCVectorProjOnUpAndRight(point, rectPoints);
  1376. var precision = 100; //保留两位小数
  1377. var uLen = Math.round(this.vecLen(pcv.uproj) * precision);
  1378. var height = Math.round(this.vecLen(pcv.up) / 2 * precision);
  1379. var rLen = Math.round(this.vecLen(pcv.rproj) * precision);
  1380. var width = Math.round(this.vecLen(pcv.right) / 2 * precision);
  1381. if (uLen <= height && rLen <= width) {
  1382. return true;
  1383. } else {
  1384. return false;
  1385. }
  1386. }
  1387. //根据角度和判断点是否在矩形内
  1388. SimpleCrop.prototype.isPointInRectCheckByAngle = function (point, rectPoints) {
  1389. //先计算四个向量
  1390. var vecs = [];
  1391. for (var i = 0; i < rectPoints.length; i++) {
  1392. var p = rectPoints[i];
  1393. vecs.push({
  1394. x: (p.x - point.x),
  1395. y: (p.y - point.y)
  1396. });
  1397. }
  1398. //计算模最小向量
  1399. var sIndex = 0;
  1400. var sLen = 0;
  1401. for (i = 0; i < vecs.length; i++) {
  1402. var len = this.vecLen(vecs[i]);
  1403. if (len == 0 || len < sLen) {
  1404. sIndex = i;
  1405. sLen = len;
  1406. }
  1407. }
  1408. len = vecs.length;
  1409. var sVec = vecs.splice(sIndex, 1)[0];
  1410. var tVec = sVec;
  1411. var eVec;
  1412. //依次计算四个向量的夹角
  1413. var angles = [];
  1414. for (i = 1; i < len; i++) {
  1415. var data = this.getMinAngle(tVec, vecs);
  1416. tVec = data.vec;
  1417. vecs.splice(data.index, 1);
  1418. angles.push(data.angle);
  1419. if (vecs.length == 1) {
  1420. eVec = vecs[0];
  1421. }
  1422. }
  1423. angles.push(this.getMinAngle(eVec, [sVec]).angle);
  1424. var sum = 0;
  1425. for (i = 0; i < angles.length; i++) {
  1426. sum += angles[i];
  1427. }
  1428. //向量之间的夹角等于360度则表示点在矩形内
  1429. sum = sum.toPrecision(12); //取12位精度能在大部分情况下解决浮点数误差导致的精度问题
  1430. if (sum < 360) {
  1431. return false;
  1432. } else {
  1433. return true;
  1434. }
  1435. };
  1436. //计算向量数组的中向量和目标向量的最小夹角
  1437. SimpleCrop.prototype.getMinAngle = function (tVec, aVec) {
  1438. var minAngle = this.vecAngle(tVec, aVec[0]);
  1439. var minIndex = 0;
  1440. for (var i = 1; i < aVec.length; i++) {
  1441. var angle = this.vecAngle(tVec, aVec[i]);
  1442. if (angle < minAngle) {
  1443. minAngle = angle;
  1444. minIndex = i;
  1445. }
  1446. }
  1447. return {
  1448. angle: minAngle,
  1449. vec: aVec[minIndex],
  1450. index: minIndex
  1451. };
  1452. };
  1453. //计算向量夹角
  1454. SimpleCrop.prototype.vecAngle = function (vec1, vec2) {
  1455. var acos = (vec1.x * vec2.x + vec1.y * vec2.y) / (this.vecLen(vec1) * this.vecLen(vec2));
  1456. if (Math.abs(acos) > 1) { //因为浮点数精度结果有可能超过1,Math.acos(1.0000001) = NaN
  1457. acos = acos > 0 ? 1 : -1;
  1458. }
  1459. var rad = Math.acos(acos);
  1460. var angle = rad * 180 / Math.PI;
  1461. return angle;
  1462. };
  1463. //计算向量的模
  1464. SimpleCrop.prototype.vecLen = function (vec) {
  1465. return Math.sqrt(vec.x * vec.x + vec.y * vec.y);
  1466. };
  1467. //file转image
  1468. SimpleCrop.prototype.fileToSrc = function (file, callback) {
  1469. var reader = new FileReader();
  1470. reader.onload = function (e) {
  1471. callback(e.target.result);
  1472. }
  1473. reader.readAsDataURL(file)
  1474. };
  1475. return SimpleCrop;
  1476. }));