|
|
/** * Copyright 2013 Matthieu Moquet * Copyright 2016-2017 LasLabs Inc. * Version 2.0.1 * License MIT (https://opensource.org/licenses/MIT)
**/
(function() { 'use strict';
window.Darkroom = Darkroom;
// Core object of DarkroomJS.
// Basically it's a single object, instanciable via an element
// (it could be a CSS selector or a DOM element), some custom options,
// and a list of plugin objects (or none to use default ones).
function Darkroom(element, options, plugins) { return this.constructor(element, options, plugins); }
// Create an empty list of plugin objects, which will be filled by
// other plugin scripts. This is the default plugin list if none is
// specified in Darkroom's constructor.
Darkroom.plugins = [];
Darkroom.prototype = { // Reference to the main container element
containerElement: null,
// Reference to the Fabric canvas object
canvas: null,
// Reference to the Fabric image object
image: null,
// Reference to the Fabric source canvas object
sourceCanvas: null,
// Reference to the Fabric source image object
sourceImage: null,
// Track of the original image element
originalImageElement: null,
// Stack of transformations to apply to the image source
transformations: [],
// Default options
defaults: { // Canvas properties (dimension, ratio, color)
minWidth: null, minHeight: null, maxWidth: null, maxHeight: null, ratio: null, backgroundColor: '#fff',
// Plugins options
plugins: {},
// Post-initialisation callback
initialize: function() { /* noop */ } },
// List of the instancied plugins
plugins: {},
// This options are a merge between `defaults` and the options passed
// through the constructor
options: {},
constructor: function(element, options) { this.options = Darkroom.Utils.extend(options, this.defaults);
if (typeof element === 'string') element = document.querySelector(element); if (null === element) return;
var image = new Image(); var parent = element.parentElement; image.onload = function() { // Initialize the DOM/Fabric elements
this._initializeDOM(element, parent); this._initializeImage();
// Then initialize the plugins
this._initializePlugins(Darkroom.plugins);
// Public method to adjust image according to the canvas
this.refresh(function() { // Execute a custom callback after initialization
this.options.initialize.bind(this).call(); }.bind(this)); }.bind(this);
image.src = element.src; },
selfDestroy: function() { var container = this.containerElement; var image = new Image(); image.onload = function() { container.parentNode.replaceChild(image, container); };
image.src = this.sourceImage.toDataURL(); },
// Add ability to attach event listener on the core object.
// It uses the canvas element to process events.
addEventListener: function(eventName, callback) { var el = this.canvas.getElement(); if (el.addEventListener) { el.addEventListener(eventName, callback); } else if (el.attachEvent) { el.attachEvent('on' + eventName, callback); } },
dispatchEvent: function(eventName) { // Use the old way of creating event to be IE compatible
// See https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
var event = document.createEvent('Event'); event.initEvent(eventName, true, true);
this.canvas.getElement().dispatchEvent(event); },
// Adjust image & canvas dimension according to min/max width/height
// and ratio specified in the options.
// This method should be called after each image transformation.
refresh: function(next) { var clone = new Image(); clone.onload = function() { this._replaceCurrentImage(new fabric.Image(clone)); if (next) next(); }.bind(this); clone.src = this.sourceImage.toDataURL(); },
_replaceCurrentImage: function(newImage) { if (this.image) { this.image.remove(); }
this.image = newImage; this.image.selectable = false;
// Adjust width or height according to specified ratio
var viewport = Darkroom.Utils.computeImageViewPort(this.image); var canvasWidth = viewport.width; var canvasHeight = viewport.height;
if (null !== this.options.ratio) { var canvasRatio = +this.options.ratio; var currentRatio = canvasWidth / canvasHeight;
if (currentRatio > canvasRatio) { canvasHeight = canvasWidth / canvasRatio; } else if (currentRatio < canvasRatio) { canvasWidth = canvasHeight * canvasRatio; } }
// Then scale the image to fit into dimension limits
var scaleMin = 1; var scaleMax = 1; var scaleX = 1; var scaleY = 1;
if (null !== this.options.maxWidth && this.options.maxWidth < canvasWidth) { scaleX = this.options.maxWidth / canvasWidth; } if (null !== this.options.maxHeight && this.options.maxHeight < canvasHeight) { scaleY = this.options.maxHeight / canvasHeight; } scaleMin = Math.min(scaleX, scaleY);
scaleX = 1; scaleY = 1; if (null !== this.options.minWidth && this.options.minWidth > canvasWidth) { scaleX = this.options.minWidth / canvasWidth; } if (null !== this.options.minHeight && this.options.minHeight > canvasHeight) { scaleY = this.options.minHeight / canvasHeight; } scaleMax = Math.max(scaleX, scaleY);
var scale = scaleMax * scaleMin; // one should be equals to 1
canvasWidth *= scale; canvasHeight *= scale;
// Finally place the image in the center of the canvas
this.image.setScaleX(1 * scale); this.image.setScaleY(1 * scale); this.canvas.add(this.image); this.canvas.setWidth(canvasWidth); this.canvas.setHeight(canvasHeight); this.canvas.centerObject(this.image); this.image.setCoords(); },
// Apply the transformation on the current image and save it in the
// transformations stack (in order to reconstitute the previous states
// of the image).
applyTransformation: function(transformation) { this.transformations.push(transformation);
transformation.applyTransformation( this.sourceCanvas, this.sourceImage, this._postTransformation.bind(this) ); },
_postTransformation: function(newImage) { if (newImage) this.sourceImage = newImage;
this.refresh(function() { this.dispatchEvent('core:transformation'); }.bind(this)); },
// Initialize image from original element plus re-apply every
// transformations.
reinitializeImage: function() { this.sourceImage.remove(); this._initializeImage(); this._popTransformation(this.transformations.slice()); },
_popTransformation: function(transformations) { if (0 === transformations.length) { this.dispatchEvent('core:reinitialized'); this.refresh(); return; }
var transformation = transformations.shift();
var next = function(newImage) { if (newImage) this.sourceImage = newImage; this._popTransformation(transformations); };
transformation.applyTransformation( this.sourceCanvas, this.sourceImage, next.bind(this) ); },
// Create the DOM elements and instanciate the Fabric canvas.
// The image element is replaced by a new `div` element.
// However the original image is re-injected in order to keep a trace of it.
_initializeDOM: function(imageElement) { // Container
var mainContainerElement = document.createElement('div'); mainContainerElement.className = 'darkroom-container';
// Toolbar
var toolbarElement = document.createElement('div'); toolbarElement.className = 'darkroom-toolbar'; mainContainerElement.appendChild(toolbarElement);
// Viewport canvas
var canvasContainerElement = document.createElement('div'); canvasContainerElement.className = 'darkroom-image-container'; var canvasElement = document.createElement('canvas'); canvasContainerElement.appendChild(canvasElement); mainContainerElement.appendChild(canvasContainerElement);
// Source canvas
var sourceCanvasContainerElement = document.createElement('div'); sourceCanvasContainerElement.className = 'darkroom-source-container'; sourceCanvasContainerElement.style.display = 'none'; var sourceCanvasElement = document.createElement('canvas'); sourceCanvasContainerElement.appendChild(sourceCanvasElement); mainContainerElement.appendChild(sourceCanvasContainerElement);
// Original image
imageElement.parentNode.replaceChild(mainContainerElement, imageElement); imageElement.style.display = 'none'; mainContainerElement.appendChild(imageElement);
// Instanciate object from elements
this.containerElement = mainContainerElement; this.originalImageElement = imageElement;
this.toolbar = new Darkroom.UI.Toolbar(toolbarElement);
this.canvas = new fabric.Canvas(canvasElement, { selection: false, backgroundColor: this.options.backgroundColor, });
this.sourceCanvas = new fabric.Canvas(sourceCanvasElement, { selection: false, backgroundColor: this.options.backgroundColor, }); },
// Instanciate the Fabric image object.
// The image is created as a static element with no control,
// then it is add in the Fabric canvas object.
_initializeImage: function() { this.sourceImage = new fabric.Image(this.originalImageElement, { // Some options to make the image static
selectable: false, evented: false, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, lockUniScaling: true, hasControls: false, hasBorders: false, });
this.sourceCanvas.add(this.sourceImage);
// Adjust width or height according to specified ratio
var viewport = Darkroom.Utils.computeImageViewPort(this.sourceImage); var canvasWidth = viewport.width; var canvasHeight = viewport.height;
this.sourceCanvas.setWidth(canvasWidth); this.sourceCanvas.setHeight(canvasHeight); this.sourceCanvas.centerObject(this.sourceImage); this.sourceImage.setCoords(); },
// Initialize every plugins.
// Note that plugins are instanciated in the same order than they
// are declared in the parameter object.
_initializePlugins: function(plugins) { for (var name in plugins) { var plugin = plugins[name]; var options = this.options.plugins[name];
// Setting false into the plugin options will disable the plugin
if (options === false) continue;
// Avoid any issues with _proto_
if (!plugins.hasOwnProperty(name)) continue;
this.plugins[name] = new plugin(this, options); } }, }; })();
|