/*! svg.draggable.js - v2.2.0 - 2016-05-21 * https://github.com/wout/svg.draggable.js * Copyright (c) 2016 Wout Fierens; Licensed MIT */ ;(function() { // creates handler, saves it function DragHandler(el){ el.remember('_draggable', this) this.el = el } // Sets new parameter, starts dragging DragHandler.prototype.init = function(constraint, val){ var _this = this this.constraint = constraint this.value = val this.el.on('mousedown.drag', function(e){ _this.start(e) }) this.el.on('touchstart.drag', function(e){ _this.start(e) }) } // transforms one point from screen to user coords DragHandler.prototype.transformPoint = function(event, offset){ event = event || window.event var touches = event.changedTouches && event.changedTouches[0] || event this.p.x = touches.pageX - (offset || 0) this.p.y = touches.pageY return this.p.matrixTransform(this.m) } // gets elements bounding box with special handling of groups, nested and use DragHandler.prototype.getBBox = function(){ var box = this.el.bbox() if(this.el instanceof SVG.Nested) box = this.el.rbox() if (this.el instanceof SVG.G || this.el instanceof SVG.Use || this.el instanceof SVG.Nested) { box.x = this.el.x() box.y = this.el.y() } return box } // start dragging DragHandler.prototype.start = function(e){ // check for left button if(e.type == 'click'|| e.type == 'mousedown' || e.type == 'mousemove'){ if((e.which || e.buttons) != 1){ return } } var _this = this // fire beforedrag event this.el.fire('beforedrag', { event: e, handler: this }) // search for parent on the fly to make sure we can call // draggable() even when element is not in the dom currently this.parent = this.parent || this.el.parent(SVG.Nested) || this.el.parent(SVG.Doc) this.p = this.parent.node.createSVGPoint() // save current transformation matrix this.m = this.el.node.getScreenCTM().inverse() var box = this.getBBox() var anchorOffset; // fix text-anchor in text-element (#37) if(this.el instanceof SVG.Text){ anchorOffset = this.el.node.getComputedTextLength(); switch(this.el.attr('text-anchor')){ case 'middle': anchorOffset /= 2; break case 'start': anchorOffset = 0; break; } } this.startPoints = { // We take absolute coordinates since we are just using a delta here point: this.transformPoint(e, anchorOffset), box: box } // add drag and end events to window SVG.on(window, 'mousemove.drag', function(e){ _this.drag(e) }) SVG.on(window, 'touchmove.drag', function(e){ _this.drag(e) }) SVG.on(window, 'mouseup.drag', function(e){ _this.end(e) }) SVG.on(window, 'touchend.drag', function(e){ _this.end(e) }) // fire dragstart event this.el.fire('dragstart', {event: e, p: this.startPoints.point, m: this.m, handler: this}) // prevent browser drag behavior e.preventDefault() // prevent propagation to a parent that might also have dragging enabled e.stopPropagation(); } // while dragging DragHandler.prototype.drag = function(e){ var box = this.getBBox() , p = this.transformPoint(e) , x = this.startPoints.box.x + p.x - this.startPoints.point.x , y = this.startPoints.box.y + p.y - this.startPoints.point.y , c = this.constraint var event = new CustomEvent('dragmove', { detail: { event: e , p: p , m: this.m , handler: this } , cancelable: true }) this.el.fire(event) if(event.defaultPrevented) return p // move the element to its new position, if possible by constraint if (typeof c == 'function') { var coord = c.call(this.el, x, y, this.m) // bool, just show us if movement is allowed or not if (typeof coord == 'boolean') { coord = { x: coord, y: coord } } // if true, we just move. If !false its a number and we move it there if (coord.x === true) { this.el.x(x) } else if (coord.x !== false) { this.el.x(coord.x) } if (coord.y === true) { this.el.y(y) } else if (coord.y !== false) { this.el.y(coord.y) } } else if (typeof c == 'object') { // keep element within constrained box if (c.minX != null && x < c.minX) x = c.minX else if (c.maxX != null && x > c.maxX - box.width){ x = c.maxX - box.width }if (c.minY != null && y < c.minY) y = c.minY else if (c.maxY != null && y > c.maxY - box.height) y = c.maxY - box.height this.el.move(x, y) } // so we can use it in the end-method, too return p } DragHandler.prototype.end = function(e){ // final drag var p = this.drag(e); // fire dragend event this.el.fire('dragend', { event: e, p: p, m: this.m, handler: this }) // unbind events SVG.off(window, 'mousemove.drag') SVG.off(window, 'touchmove.drag') SVG.off(window, 'mouseup.drag') SVG.off(window, 'touchend.drag') } SVG.extend(SVG.Element, { // Make element draggable // Constraint might be an object (as described in readme.md) or a function in the form "function (x, y)" that gets called before every move. // The function can return a boolean or an object of the form {x, y}, to which the element will be moved. "False" skips moving, true moves to raw x, y. draggable: function(value, constraint) { // Check the parameters and reassign if needed if (typeof value == 'function' || typeof value == 'object') { constraint = value value = true } var dragHandler = this.remember('_draggable') || new DragHandler(this) // When no parameter is given, value is true value = typeof value === 'undefined' ? true : value if(value) dragHandler.init(constraint || {}, value) else { this.off('mousedown.drag') this.off('touchstart.drag') } return this } }) }).call(this);