I having be exploring several options for building an open-source HTML5 based application like Sketch, which will basically be vector based app for creating UI screens etc.
I saw several great JS Libraries like http://snapsvg.io, http://paperjs.orgetc...
And creating a static SVG element is as simple as below:
var s = Snap("#svg");
// Lets create big circle in the middle:
var bigCircle = s.circle(150, 150, 100);
What I am not having clue about is, binding the job to mouse move event. So if user click and drag, depending on the distance they dragged, the element should be created. The basic feature all editors have. I would need implement similar functionality for resizing also.
I think you already solved this problem, but here is how to do. I'm using SVG.js and plugin svg.draw.js. It is lightweight and well documented.
It's a lot of code here, but once you run it is exactly what you want.
/*! svg.draw.js - v2.0.3 - 2017-06-19
* https://github.com/svgdotjs/svg.draw.js
* Copyright (c) 2017 Ulrich-Matthias Schäfer; Licensed MIT */
/* Include min code here since there is no CDN for svg.draw.js */
(function(){function a(a,b,c){this.el=a,a.remember("_paintHandler",this);var d=this,e=this.getPlugin();this.parent=a.parent(SVG.Nested)||a.parent(SVG.Doc),this.p=this.parent.node.createSVGPoint(),this.m=null,this.startPoint=null,this.lastUpdateCall=null,this.options={};for(var f in this.el.draw.defaults)this.options[f]=this.el.draw.defaults[f],"undefined"!=typeof c[f]&&(this.options[f]=c[f]);e.point&&(e.pointPlugin=e.point,delete e.point);for(var f in e)this[f]=e[f];b||this.parent.on("click.draw",function(a){d.start(a)})}a.prototype.transformPoint=function(a,b){return this.p.x=a-(this.offset.x-window.pageXOffset),this.p.y=b-(this.offset.y-window.pageYOffset),this.p.matrixTransform(this.m)},a.prototype.start=function(a){var b=this;this.m=this.el.node.getScreenCTM().inverse(),this.offset={x:window.pageXOffset,y:window.pageYOffset},this.options.snapToGrid*=Math.sqrt(this.m.a*this.m.a+this.m.b*this.m.b),this.startPoint=this.snapToGrid(this.transformPoint(a.clientX,a.clientY)),this.init&&this.init(a),this.el.fire("drawstart",{event:a,p:this.p,m:this.m}),SVG.on(window,"mousemove.draw",function(a){b.update(a)}),this.start=this.point},a.prototype.point=function(a){return this.point!=this.start?this.start(a):this.pointPlugin?this.pointPlugin(a):void this.stop(a)},a.prototype.stop=function(a){a&&this.update(a),this.clean&&this.clean(),SVG.off(window,"mousemove.draw"),this.parent.off("click.draw"),this.el.forget("_paintHandler"),this.el.draw=function(){},this.el.fire("drawstop")},a.prototype.update=function(a){!a&&this.lastUpdateCall&&(a=this.lastUpdateCall),this.lastUpdateCall=a,this.calc(a),this.el.fire("drawupdate",{event:a,p:this.p,m:this.m})},a.prototype.done=function(){this.calc(),this.stop(),this.el.fire("drawdone")},a.prototype.cancel=function(){this.stop(),this.el.remove(),this.el.fire("drawcancel")},a.prototype.snapToGrid=function(a){var b=null;if(a.length)return b=[a[0]%this.options.snapToGrid,a[1]%this.options.snapToGrid],a[0]-=b[0]<this.options.snapToGrid/2?b[0]:b[0]-this.options.snapToGrid,a[1]-=b[1]<this.options.snapToGrid/2?b[1]:b[1]-this.options.snapToGrid,a;for(var c in a)b=a[c]%this.options.snapToGrid,a[c]-=(b<this.options.snapToGrid/2?b:b-this.options.snapToGrid)+(0>b?this.options.snapToGrid:0);return a},a.prototype.param=function(a,b){this.options[a]=null===b?this.el.draw.defaults[a]:b,this.update()},a.prototype.getPlugin=function(){return this.el.draw.plugins[this.el.type]},SVG.extend(SVG.Element,{draw:function(b,c,d){b instanceof Event||"string"==typeof b||(c=b,b=null);var e=this.remember("_paintHandler")||new a(this,b,c||{});return b instanceof Event&&e.start(b),e[b]&&e[b](c,d),this}}),SVG.Element.prototype.draw.defaults={snapToGrid:1},SVG.Element.prototype.draw.extend=function(a,b){var c={};"string"==typeof a?c[a]=b:c=a;for(var d in c){var e=d.trim().split(/\s+/);for(var f in e)SVG.Element.prototype.draw.plugins[e[f]]=c[d]}},SVG.Element.prototype.draw.plugins={},SVG.Element.prototype.draw.extend("rect image",{init:function(a){var b=this.startPoint;this.el.attr({x:b.x,y:b.y,height:0,width:0})},calc:function(a){var b={x:this.startPoint.x,y:this.startPoint.y},c=this.transformPoint(a.clientX,a.clientY);b.width=c.x-b.x,b.height=c.y-b.y,this.snapToGrid(b),b.width<0&&(b.x=b.x+b.width,b.width=-b.width),b.height<0&&(b.y=b.y+b.height,b.height=-b.height),this.el.attr(b)}}),SVG.Element.prototype.draw.extend("line polyline polygon",{init:function(a){this.set=new SVG.Set;var b=this.startPoint,c=[[b.x,b.y],[b.x,b.y]];this.el.plot(c),this.drawCircles()},calc:function(a){var b=this.el.array().valueOf();if(b.pop(),a){var c=this.transformPoint(a.clientX,a.clientY);b.push(this.snapToGrid([c.x,c.y]))}this.el.plot(b)},point:function(a){if(this.el.type.indexOf("poly")>-1){var b=this.transformPoint(a.clientX,a.clientY),c=this.el.array().valueOf();return c.push(this.snapToGrid([b.x,b.y])),this.el.plot(c),this.drawCircles(),void this.el.fire("drawpoint",{event:a,p:{x:b.x,y:b.y},m:this.m})}this.stop(a)},clean:function(){this.set.each(function(){this.remove()}),this.set.clear(),delete this.set},drawCircles:function(){var a=this.el.array().valueOf();this.set.each(function(){this.remove()}),this.set.clear();for(var b=0;b<a.length;++b){this.p.x=a[b][0],this.p.y=a[b][1];var c=this.p.matrixTransform(this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM()));this.set.add(this.parent.circle(5).stroke({width:1}).fill("#ccc").center(c.x,c.y))}}}),SVG.Element.prototype.draw.extend("circle",{init:function(a){var b=this.startPoint;this.el.attr({cx:b.x,cy:b.y,r:1})},calc:function(a){var b=this.transformPoint(a.clientX,a.clientY),c={cx:this.startPoint.x,cy:this.startPoint.y,r:Math.sqrt((b.x-this.startPoint.x)*(b.x-this.startPoint.x)+(b.y-this.startPoint.y)*(b.y-this.startPoint.y))};this.snapToGrid(c),this.el.attr(c)}}),SVG.Element.prototype.draw.extend("ellipse",{init:function(a){var b=this.startPoint;this.el.attr({cx:b.x,cy:b.y,rx:1,ry:1})},calc:function(a){var b=this.transformPoint(a.clientX,a.clientY),c={cx:this.startPoint.x,cy:this.startPoint.y,rx:Math.abs(b.x-this.startPoint.x),ry:Math.abs(b.y-this.startPoint.y)};this.snapToGrid(c),this.el.attr(c)}})}).call(this);
const draw = SVG('drawing');
const shapes = [];
let index = 0;
let shape;
const getDrawObject = () => {
shape = document.getElementById('shape').value;
const color = document.getElementById('color').value;
const option = {
stroke: color,
'stroke-width': 2,
'fill-opacity': 0,
};
switch (shape) {
case 'mouse paint':
return draw.polyline().attr(option);
case 'ellipse':
return draw.ellipse().attr(option);
case 'rect':
return draw.rect().attr(option);
}
return null;
}
draw.on('mousedown', event => {
const shape = getDrawObject();
shapes[index] = shape;
shape.draw(event);
});
draw.on('mousemove', event => {
if (shape === 'mouse paint' && shapes[index]) {
shapes[index].draw('point', event);
}
})
draw.on('mouseup', event => {
if (shape === 'mouse paint') {
shapes[index].draw('stop', event);
} else {
shapes[index].draw(event);
}
index++;
})
// This is custom extension of line, polyline, polygon which doesn't draw the circle on the line.
SVG.Element.prototype.draw.extend('line polyline polygon', {
init:function(e){
// When we draw a polygon, we immediately need 2 points.
// One start-point and one point at the mouse-position
this.set = new SVG.Set();
var p = this.startPoint,
arr = [
[p.x, p.y],
[p.x, p.y]
];
this.el.plot(arr);
},
// The calc-function sets the position of the last point to the mouse-position (with offset ofc)
calc:function (e) {
var arr = this.el.array().valueOf();
arr.pop();
if (e) {
var p = this.transformPoint(e.clientX, e.clientY);
arr.push(this.snapToGrid([p.x, p.y]));
}
this.el.plot(arr);
},
point:function(e){
if (this.el.type.indexOf('poly') > -1) {
// Add the new Point to the point-array
var p = this.transformPoint(e.clientX, e.clientY),
arr = this.el.array().valueOf();
arr.push(this.snapToGrid([p.x, p.y]));
this.el.plot(arr);
// Fire the `drawpoint`-event, which holds the coords of the new Point
this.el.fire('drawpoint', {event:e, p:{x:p.x, y:p.y}, m:this.m});
return;
}
// We are done, if the element is no polyline or polygon
this.stop(e);
},
clean:function(){
// Remove all circles
this.set.each(function () {
this.remove();
});
this.set.clear();
delete this.set;
},
});
#drawing {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.3/svg.min.js"></script>
<div id="drawing"></div>
Shape:
<select id="shape">
<option value="mouse paint">Mouse paint</option>
<option value="rect">Recangle</option>
<option value="ellipse">Circle</option>
</select> Color:
<select id="color">
<option value="#ff0099">Pink</option>
<option value="#f3f313">Yellow</option>
<option value="#0dd5fc">Blue</option>
<option value="#83f52c">Green</option>
</select>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With