You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
13 KiB

  1. /**
  2. * Copyright 2013 Matthieu Moquet
  3. * Copyright 2016-2017 LasLabs Inc.
  4. * Version 2.0.1
  5. * License MIT (https://opensource.org/licenses/MIT)
  6. **/
  7. (function() {
  8. 'use strict';
  9. window.Darkroom = Darkroom;
  10. // Core object of DarkroomJS.
  11. // Basically it's a single object, instanciable via an element
  12. // (it could be a CSS selector or a DOM element), some custom options,
  13. // and a list of plugin objects (or none to use default ones).
  14. function Darkroom(element, options, plugins) {
  15. return this.constructor(element, options, plugins);
  16. }
  17. // Create an empty list of plugin objects, which will be filled by
  18. // other plugin scripts. This is the default plugin list if none is
  19. // specified in Darkroom's constructor.
  20. Darkroom.plugins = [];
  21. Darkroom.prototype = {
  22. // Reference to the main container element
  23. containerElement: null,
  24. // Reference to the Fabric canvas object
  25. canvas: null,
  26. // Reference to the Fabric image object
  27. image: null,
  28. // Reference to the Fabric source canvas object
  29. sourceCanvas: null,
  30. // Reference to the Fabric source image object
  31. sourceImage: null,
  32. // Track of the original image element
  33. originalImageElement: null,
  34. // Stack of transformations to apply to the image source
  35. transformations: [],
  36. // Default options
  37. defaults: {
  38. // Canvas properties (dimension, ratio, color)
  39. minWidth: null,
  40. minHeight: null,
  41. maxWidth: null,
  42. maxHeight: null,
  43. ratio: null,
  44. backgroundColor: '#fff',
  45. // Plugins options
  46. plugins: {},
  47. // Post-initialisation callback
  48. initialize: function() { /* noop */ }
  49. },
  50. // List of the instancied plugins
  51. plugins: {},
  52. // This options are a merge between `defaults` and the options passed
  53. // through the constructor
  54. options: {},
  55. constructor: function(element, options) {
  56. this.options = Darkroom.Utils.extend(options, this.defaults);
  57. if (typeof element === 'string')
  58. element = document.querySelector(element);
  59. if (null === element)
  60. return;
  61. var image = new Image();
  62. var parent = element.parentElement;
  63. image.onload = function() {
  64. // Initialize the DOM/Fabric elements
  65. this._initializeDOM(element, parent);
  66. this._initializeImage();
  67. // Then initialize the plugins
  68. this._initializePlugins(Darkroom.plugins);
  69. // Public method to adjust image according to the canvas
  70. this.refresh(function() {
  71. // Execute a custom callback after initialization
  72. this.options.initialize.bind(this).call();
  73. }.bind(this));
  74. }.bind(this);
  75. image.src = element.src;
  76. },
  77. selfDestroy: function() {
  78. var container = this.containerElement;
  79. var image = new Image();
  80. image.onload = function() {
  81. container.parentNode.replaceChild(image, container);
  82. };
  83. image.src = this.sourceImage.toDataURL();
  84. },
  85. // Add ability to attach event listener on the core object.
  86. // It uses the canvas element to process events.
  87. addEventListener: function(eventName, callback) {
  88. var el = this.canvas.getElement();
  89. if (el.addEventListener) {
  90. el.addEventListener(eventName, callback);
  91. } else if (el.attachEvent) {
  92. el.attachEvent('on' + eventName, callback);
  93. }
  94. },
  95. dispatchEvent: function(eventName) {
  96. // Use the old way of creating event to be IE compatible
  97. // See https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
  98. var event = document.createEvent('Event');
  99. event.initEvent(eventName, true, true);
  100. this.canvas.getElement().dispatchEvent(event);
  101. },
  102. // Adjust image & canvas dimension according to min/max width/height
  103. // and ratio specified in the options.
  104. // This method should be called after each image transformation.
  105. refresh: function(next) {
  106. var clone = new Image();
  107. clone.onload = function() {
  108. this._replaceCurrentImage(new fabric.Image(clone));
  109. if (next) next();
  110. }.bind(this);
  111. clone.src = this.sourceImage.toDataURL();
  112. },
  113. _replaceCurrentImage: function(newImage) {
  114. if (this.image) {
  115. this.image.remove();
  116. }
  117. this.image = newImage;
  118. this.image.selectable = false;
  119. // Adjust width or height according to specified ratio
  120. var viewport = Darkroom.Utils.computeImageViewPort(this.image);
  121. var canvasWidth = viewport.width;
  122. var canvasHeight = viewport.height;
  123. if (null !== this.options.ratio) {
  124. var canvasRatio = +this.options.ratio;
  125. var currentRatio = canvasWidth / canvasHeight;
  126. if (currentRatio > canvasRatio) {
  127. canvasHeight = canvasWidth / canvasRatio;
  128. } else if (currentRatio < canvasRatio) {
  129. canvasWidth = canvasHeight * canvasRatio;
  130. }
  131. }
  132. // Then scale the image to fit into dimension limits
  133. var scaleMin = 1;
  134. var scaleMax = 1;
  135. var scaleX = 1;
  136. var scaleY = 1;
  137. if (null !== this.options.maxWidth && this.options.maxWidth < canvasWidth) {
  138. scaleX = this.options.maxWidth / canvasWidth;
  139. }
  140. if (null !== this.options.maxHeight && this.options.maxHeight < canvasHeight) {
  141. scaleY = this.options.maxHeight / canvasHeight;
  142. }
  143. scaleMin = Math.min(scaleX, scaleY);
  144. scaleX = 1;
  145. scaleY = 1;
  146. if (null !== this.options.minWidth && this.options.minWidth > canvasWidth) {
  147. scaleX = this.options.minWidth / canvasWidth;
  148. }
  149. if (null !== this.options.minHeight && this.options.minHeight > canvasHeight) {
  150. scaleY = this.options.minHeight / canvasHeight;
  151. }
  152. scaleMax = Math.max(scaleX, scaleY);
  153. var scale = scaleMax * scaleMin; // one should be equals to 1
  154. canvasWidth *= scale;
  155. canvasHeight *= scale;
  156. // Finally place the image in the center of the canvas
  157. this.image.setScaleX(1 * scale);
  158. this.image.setScaleY(1 * scale);
  159. this.canvas.add(this.image);
  160. this.canvas.setWidth(canvasWidth);
  161. this.canvas.setHeight(canvasHeight);
  162. this.canvas.centerObject(this.image);
  163. this.image.setCoords();
  164. },
  165. // Apply the transformation on the current image and save it in the
  166. // transformations stack (in order to reconstitute the previous states
  167. // of the image).
  168. applyTransformation: function(transformation) {
  169. this.transformations.push(transformation);
  170. transformation.applyTransformation(
  171. this.sourceCanvas,
  172. this.sourceImage,
  173. this._postTransformation.bind(this)
  174. );
  175. },
  176. _postTransformation: function(newImage) {
  177. if (newImage)
  178. this.sourceImage = newImage;
  179. this.refresh(function() {
  180. this.dispatchEvent('core:transformation');
  181. }.bind(this));
  182. },
  183. // Initialize image from original element plus re-apply every
  184. // transformations.
  185. reinitializeImage: function() {
  186. this.sourceImage.remove();
  187. this._initializeImage();
  188. this._popTransformation(this.transformations.slice());
  189. },
  190. _popTransformation: function(transformations) {
  191. if (0 === transformations.length) {
  192. this.dispatchEvent('core:reinitialized');
  193. this.refresh();
  194. return;
  195. }
  196. var transformation = transformations.shift();
  197. var next = function(newImage) {
  198. if (newImage) this.sourceImage = newImage;
  199. this._popTransformation(transformations);
  200. };
  201. transformation.applyTransformation(
  202. this.sourceCanvas,
  203. this.sourceImage,
  204. next.bind(this)
  205. );
  206. },
  207. // Create the DOM elements and instanciate the Fabric canvas.
  208. // The image element is replaced by a new `div` element.
  209. // However the original image is re-injected in order to keep a trace of it.
  210. _initializeDOM: function(imageElement) {
  211. // Container
  212. var mainContainerElement = document.createElement('div');
  213. mainContainerElement.className = 'darkroom-container';
  214. // Toolbar
  215. var toolbarElement = document.createElement('div');
  216. toolbarElement.className = 'darkroom-toolbar';
  217. mainContainerElement.appendChild(toolbarElement);
  218. // Viewport canvas
  219. var canvasContainerElement = document.createElement('div');
  220. canvasContainerElement.className = 'darkroom-image-container';
  221. var canvasElement = document.createElement('canvas');
  222. canvasContainerElement.appendChild(canvasElement);
  223. mainContainerElement.appendChild(canvasContainerElement);
  224. // Source canvas
  225. var sourceCanvasContainerElement = document.createElement('div');
  226. sourceCanvasContainerElement.className = 'darkroom-source-container';
  227. sourceCanvasContainerElement.style.display = 'none';
  228. var sourceCanvasElement = document.createElement('canvas');
  229. sourceCanvasContainerElement.appendChild(sourceCanvasElement);
  230. mainContainerElement.appendChild(sourceCanvasContainerElement);
  231. // Original image
  232. imageElement.parentNode.replaceChild(mainContainerElement, imageElement);
  233. imageElement.style.display = 'none';
  234. mainContainerElement.appendChild(imageElement);
  235. // Instanciate object from elements
  236. this.containerElement = mainContainerElement;
  237. this.originalImageElement = imageElement;
  238. this.toolbar = new Darkroom.UI.Toolbar(toolbarElement);
  239. this.canvas = new fabric.Canvas(canvasElement, {
  240. selection: false,
  241. backgroundColor: this.options.backgroundColor,
  242. });
  243. this.sourceCanvas = new fabric.Canvas(sourceCanvasElement, {
  244. selection: false,
  245. backgroundColor: this.options.backgroundColor,
  246. });
  247. },
  248. // Instanciate the Fabric image object.
  249. // The image is created as a static element with no control,
  250. // then it is add in the Fabric canvas object.
  251. _initializeImage: function() {
  252. this.sourceImage = new fabric.Image(this.originalImageElement, {
  253. // Some options to make the image static
  254. selectable: false,
  255. evented: false,
  256. lockMovementX: true,
  257. lockMovementY: true,
  258. lockRotation: true,
  259. lockScalingX: true,
  260. lockScalingY: true,
  261. lockUniScaling: true,
  262. hasControls: false,
  263. hasBorders: false,
  264. });
  265. this.sourceCanvas.add(this.sourceImage);
  266. // Adjust width or height according to specified ratio
  267. var viewport = Darkroom.Utils.computeImageViewPort(this.sourceImage);
  268. var canvasWidth = viewport.width;
  269. var canvasHeight = viewport.height;
  270. this.sourceCanvas.setWidth(canvasWidth);
  271. this.sourceCanvas.setHeight(canvasHeight);
  272. this.sourceCanvas.centerObject(this.sourceImage);
  273. this.sourceImage.setCoords();
  274. },
  275. // Initialize every plugins.
  276. // Note that plugins are instanciated in the same order than they
  277. // are declared in the parameter object.
  278. _initializePlugins: function(plugins) {
  279. for (var name in plugins) {
  280. var plugin = plugins[name];
  281. var options = this.options.plugins[name];
  282. // Setting false into the plugin options will disable the plugin
  283. if (options === false)
  284. continue;
  285. // Avoid any issues with _proto_
  286. if (!plugins.hasOwnProperty(name))
  287. continue;
  288. this.plugins[name] = new plugin(this, options);
  289. }
  290. },
  291. };
  292. })();