alloy-crop.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /* AlloyCrop v1.0.1
  2. * By dntzhang
  3. * Github: https://github.com/AlloyTeam/AlloyCrop
  4. */
  5. ;(function(){
  6. var AlloyFinger = typeof require === 'function'
  7. ? require('alloyfinger')
  8. : window.AlloyFinger
  9. var Transform = typeof require === 'function'
  10. ? require('css3transform')
  11. : window.Transform
  12. var AlloyCrop = function (option) {
  13. this.renderTo = document.body;
  14. this.canvas = document.createElement("canvas");
  15. this.output = option.output;
  16. this.width = option.width;
  17. this.height = option.height;
  18. this.canvas.width = option.width * this.output;
  19. this.canvas.height = option.height * this.output;
  20. this.circle = option.circle;
  21. if (option.width !== option.height && option.circle) {
  22. throw "can't set circle to true when width is not equal to height"
  23. }
  24. this.ctx = this.canvas.getContext("2d");
  25. this.croppingBox = document.createElement("div");
  26. this.croppingBox.style.visibility = "hidden";
  27. this.cover = document.createElement("canvas");
  28. this.type = option.type || "png";
  29. this.cover.width = window.innerWidth;
  30. this.cover.height = window.innerHeight;
  31. this.cover_ctx = this.cover.getContext("2d");
  32. this.img = document.createElement("img");
  33. if(option.image_src.substring(0,4).toLowerCase()==='http') {
  34. this.img.crossOrigin = 'anonymous';//resolve base64 uri bug in safari:"cross-origin image load denied by cross-origin resource sharing policy."
  35. }
  36. this.cancel = option.cancel;
  37. this.ok = option.ok;
  38. this.ok_text = option.ok_text || "ok";
  39. this.cancel_text = option.cancel_text || "cancel";
  40. this.croppingBox.appendChild(this.img);
  41. this.croppingBox.appendChild(this.cover);
  42. this.renderTo.appendChild(this.croppingBox);
  43. this.img.onload = this.init.bind(this);
  44. this.img.src = option.image_src;
  45. this.cancel_btn = document.createElement('a');
  46. this.cancel_btn.innerHTML = this.cancel_text;
  47. this.ok_btn = document.createElement('a');
  48. this.ok_btn.innerHTML = this.ok_text;
  49. this.croppingBox.appendChild(this.ok_btn);
  50. this.croppingBox.appendChild(this.cancel_btn);
  51. };
  52. AlloyCrop.prototype = {
  53. init: function () {
  54. this.img_width = this.img.width;
  55. this.img_height = this.img.height;
  56. Transform(this.img,true);
  57. var scaling_x = window.innerWidth / this.img_width,
  58. scaling_y = window.innerHeight / this.img_height;
  59. var scaling = scaling_x > scaling_y ? scaling_y : scaling_x;
  60. this.initScale = scaling;
  61. this.img.scaleX = this.img.scaleY = scaling;
  62. var self = this;
  63. new AlloyFinger(this.croppingBox, {
  64. multipointStart: function (evt) {
  65. //reset origin x and y
  66. var centerX = (evt.touches[0].pageX + evt.touches[1].pageX) / 2;
  67. var centerY = (evt.touches[0].pageY + evt.touches[1].pageY) / 2;
  68. var cr = self.img.getBoundingClientRect();
  69. var img_centerX = cr.left + cr.width / 2;
  70. var img_centerY = cr.top + cr.height / 2;
  71. var offX = centerX - img_centerX;
  72. var offY = centerY - img_centerY;
  73. var preOriginX = self.img.originX
  74. var preOriginY = self.img.originY
  75. self.img.originX = offX / self.img.scaleX;
  76. self.img.originY = offY / self.img.scaleY;
  77. //reset translateX and translateY
  78. self.img.translateX += offX - preOriginX * self.img.scaleX;
  79. self.img.translateY += offY - preOriginY * self.img.scaleX;
  80. self.initScale = self.img.scaleX;
  81. },
  82. pinch: function (evt) {
  83. self.img.scaleX = self.img.scaleY = self.initScale * evt.zoom;
  84. },
  85. pressMove: function (evt) {
  86. self.img.translateX += evt.deltaX;
  87. self.img.translateY += evt.deltaY;
  88. evt.preventDefault();
  89. }
  90. });
  91. new AlloyFinger(this.cancel_btn, {
  92. touchStart:function(){
  93. self.cancel_btn.style.backgroundColor = '#6854e4'
  94. },
  95. tap: this._cancel.bind(this)
  96. });
  97. new AlloyFinger(this.ok_btn, {
  98. touchStart:function(){
  99. self.ok_btn.style.backgroundColor = '#6854e4'
  100. },
  101. tap: this._ok.bind(this)
  102. });
  103. document.addEventListener('touchend',function(){
  104. self.cancel_btn.style.backgroundColor = '#836FFF'
  105. self.ok_btn.style.backgroundColor = '#836FFF'
  106. })
  107. this.renderCover();
  108. this.setStyle();
  109. },
  110. _cancel: function () {
  111. this._css(this.croppingBox, {
  112. display: "none"
  113. });
  114. this.cancel();
  115. },
  116. _ok: function () {
  117. this.crop();
  118. this._css(this.croppingBox, {
  119. display: "none"
  120. });
  121. this.ok(this.canvas.toDataURL("image/" + this.type), this.canvas);
  122. },
  123. renderCover: function () {
  124. var ctx = this.cover_ctx,
  125. w = this.cover.width,
  126. h = this.cover.height,
  127. cw = this.width,
  128. ch = this.height;
  129. ctx.save();
  130. ctx.fillStyle = "black";
  131. ctx.globalAlpha = 0.7;
  132. ctx.fillRect(0, 0, this.cover.width, this.cover.height);
  133. ctx.restore();
  134. ctx.save();
  135. ctx.globalCompositeOperation = "destination-out";
  136. ctx.beginPath();
  137. if (this.circle) {
  138. ctx.arc(w / 2, h / 2, cw / 2 - 4, 0, Math.PI * 2, false);
  139. } else {
  140. ctx.rect(w / 2 - cw / 2, h / 2 - ch / 2, cw, ch)
  141. }
  142. ctx.fill();
  143. ctx.restore();
  144. ctx.save();
  145. ctx.beginPath();
  146. ctx.strokeStyle = "white";
  147. if (this.circle) {
  148. ctx.arc(w / 2, h / 2, cw / 2 - 4, 0, Math.PI * 2, false);
  149. } else {
  150. ctx.rect(w / 2 - cw / 2, h / 2 - ch / 2, cw, ch)
  151. }
  152. ctx.stroke();
  153. },
  154. setStyle: function () {
  155. this._css(this.cover, {
  156. position: "absolute",
  157. zIndex: "100",
  158. left: "0px",
  159. top: "0px"
  160. });
  161. this._css(this.croppingBox, {
  162. color: "white",
  163. textAlign: "center",
  164. fontSize: "18px",
  165. textDecoration: "none",
  166. visibility: "visible"
  167. });
  168. this._css(this.img, {
  169. position: "absolute",
  170. zIndex: "99",
  171. left: "50%",
  172. // error position in meizu when set the top 50%
  173. top: window.innerHeight / 2 + "px",
  174. marginLeft: this.img_width / -2 + "px",
  175. marginTop: this.img_height / -2 + "px"
  176. });
  177. this._css(this.ok_btn, {
  178. position: "fixed",
  179. zIndex: "101",
  180. // width: "100px",
  181. width: "2.8125rem",
  182. right: "50px",
  183. // lineHeight: "40px",
  184. lineHeight: "1.25rem",
  185. // height: "40px",
  186. height: "1.25rem",
  187. bottom: "20px",
  188. borderRadius: "2px",
  189. backgroundColor: "#836FFF",
  190. fontSize: "0.375rem"
  191. });
  192. this._css(this.cancel_btn, {
  193. position: "fixed",
  194. zIndex: "101",
  195. // width: "100px",
  196. width: "2.8125rem",
  197. // height: "40px",
  198. height: "1.25rem",
  199. // lineHeight: "40px",
  200. lineHeight: "1.25rem",
  201. left: "50px",
  202. bottom: "20px",
  203. borderRadius: "2px",
  204. backgroundColor: "#836FFF",
  205. fontSize: "0.375rem"
  206. });
  207. },
  208. crop: function () {
  209. this.calculateRect();
  210. this.ctx.drawImage(this.img, this.crop_rect[0], this.crop_rect[1], this.crop_rect[2], this.crop_rect[3], 0, 0, this.canvas.width, this.canvas.height);
  211. },
  212. calculateRect: function () {
  213. var cr = this.img.getBoundingClientRect();
  214. var c_left = window.innerWidth / 2 - this.width / 2;
  215. var c_top = window.innerHeight / 2 - this.height / 2;
  216. var cover_rect = [c_left, c_top, this.width + c_left, this.height + c_top];
  217. var img_rect = [cr.left, cr.top, cr.width + cr.left, cr.height + cr.top];
  218. var intersect_rect = this.getOverlap.apply(this, cover_rect.concat(img_rect));
  219. var left = (intersect_rect[0] - img_rect[0]) / this.img.scaleX;
  220. var top = (intersect_rect[1] - img_rect[1]) / this.img.scaleY;
  221. var width = intersect_rect[2] / this.img.scaleX;
  222. var height = intersect_rect[3] / this.img.scaleY;
  223. if (left < 0) left = 0;
  224. if (top < 0) top = 0;
  225. if (left + width > this.img_width) width = this.img_width - left;
  226. if (top + height > this.img_height) height = this.img_height - top;
  227. this.crop_rect = [left, top, width, height];
  228. },
  229. // top left (x1,y1) and bottom right (x2,y2) coordination
  230. getOverlap: function (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
  231. if (ax2 < bx1 || ay2 < by1 || ax1 > bx2 || ay1 > by2) return [0, 0, 0, 0];
  232. var left = Math.max(ax1, bx1);
  233. var top = Math.max(ay1, by1);
  234. var right = Math.min(ax2, bx2);
  235. var bottom = Math.min(ay2, by2);
  236. return [left, top, right - left, bottom - top]
  237. },
  238. _css: function (el, obj) {
  239. for (var key in obj) {
  240. if (obj.hasOwnProperty(key)) {
  241. el.style[key] = obj[key];
  242. }
  243. }
  244. }
  245. };
  246. if (typeof module !== 'undefined' && typeof exports === 'object') {
  247. module.exports = AlloyCrop;
  248. }else {
  249. window.AlloyCrop = AlloyCrop;
  250. }
  251. })();