I want to allow a user to add shapes/resize them, with a shape panel like the one on this page:
https://logomakr.com/
I have read into this and the use HTML canvas seems inevitable. My issue is allowing the user to add more than 1 of each shape and allowing the user to resize each shape.
You can achieve that using a JavaScript canvas library, called FabricJS.
ᴅᴇᴍᴏ
var canvas = new fabric.Canvas('c');
function addRectangle() {
var rectangle = new fabric.Rect({
top: 65,
left: 65,
width: 50,
height: 50,
fill: '#03A9F4'
});
canvas.add(rectangle);
}
function addTriangle() {
var triangle = new fabric.Triangle({
top: 65,
left: 65,
width: 50,
height: 50,
fill: '#4CAF50'
});
canvas.add(triangle);
}
function addCircle() {
var circle = new fabric.Circle({
top: 65,
left: 65,
radius: 25,
fill: '#f44336'
});
canvas.add(circle);
}
body { margin: 10px 0 0 0 }
canvas { border: 1px solid #ccc }
button { margin-top: 9px }
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.14/fabric.min.js"></script>
<canvas id="c" width="180" height="180"></canvas>
<button onclick="addRectangle()">Add Rectangle</button>
<button onclick="addTriangle()">Add Triangle</button>
<button onclick="addCircle()">Add Circle</button>
To learn more about this library, refer to the official documentation.
Related
how would i go about creating a transparent text box inside a canvas?
any elegant solution?
I've read than i can make a transparent input box and position is above the canvas,
but how would said canvas save the words entered in said textbox?
would appreciate any solutions
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye
ctx.stroke();
}
var inp = document.getElementById("input");
var para = document.getElementById("p1");
// Check browser support
if (typeof(Storage) !== "undefined") {
inp.value = localStorage.getItem("x");
}
else {
para.innerHTML = "Sorry, your browser does not support Web Storage...";
}
function abc(){
if (typeof(Storage) !== "undefined") {
// Store
localStorage.setItem("x", inp.value);
// Retrieve
inp.value = localStorage.getItem("x");
}
else {
para.innerHTML = "Sorry, your browser does not support Web Storage...";
}
}
#canvas {
position:absolute;
border: 1px solid black;
height: 100px;
}
input {
position:absolute;
left:8px;
top: 16px;
background-color: transparent;
border: 0px;
width: 202px;
height: 100px;
font-size: 200%;
}
<div>
<canvas id="canvas"></canvas>
<input onkeydown="abc()" type="text" id="input">
<p id="p1"></p>
</div>
you can see a error like this
{
"message": "Uncaught SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.",
"filename": "https://stacksnippets.net/js",
"lineno": 50,
"colno": 21
}
when you directly run the snippet, so you must run code through an IDE (nodepad, nodepad++, vscode, brackets, sublime etc.) to you browser, then it will work
I hope this will work for you.
On the Konvajs chat stream someone recently asked for an example of drag-and-drop from a palette onto an HTML5 canvas fronted by the Konvajs library. There were no ready examples and I was curious about how to achieve it.
I answered the question in a codepen but decided to post here for (my own) future reference. See my answer below.
Here is my solution using jquery UI draggable & droppables. Konvajs requires jquery so the use of jquery UI is only a small step further. The palette is a set of small canvas elements with one shape drawn per draggable item. The palette can be housed on any html element and does not need to be attached to the main stage in any way.
// Set up the canvas to catch the dragged shapes
var s1 = new Konva.Stage({container: 'container1', width: 500, height: 200});
// add a layer to host the 'dropped' shapes.
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);
// set up the palette of draggable shapes - 5 sample shapes.
var palletteEle = $('#pallette');
var d, ps, l, c;
for (var i = 0; i<5; i = i + 1){
// make a div to hold the shape
d = $('<div id="shape' + i + '" class="draggable">Shape</div>')
palletteEle.append(d)
// make a mini stage to hold the shape
ps = new Konva.Stage({container: 'shape' + i, width: 50, height: 50});
// make a layer to hold the shape
l = new Konva.Layer();
// add layer to palette
ps.add(l);
// make a shape - red circles for example
c = new Konva.Circle({x: 24, y: 24, radius: 22, fill: 'red', stroke: 'black'})
l.add(c);
ps.draw();
}
// make a crosshair to give some idea of the drop location
var cross = new Konva.Line({points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'gold',
strokeWidth: 1,
lineCap: 'round',
lineJoin: 'round'})
layer1.add(cross);
//s1.draw();
// make the main stage a drop target
$('#container1').addClass('droppable');
// function to move the cross hairs
function moveCross(x, y){
cross.x(x);
y = y - $('#container1').offset().top;
cross.y(y < 0 ? 0 : y);
s1.draw();
}
// draggable setup. Movecross used to move the crosshairs. More work needed but shows the way.
$( ".draggable" ).draggable({
zIndex: 100,
helper: "clone",
opacity: 0.35,
drag: function( event, ui ) {moveCross(ui.offset.left , ui.offset.top + $(this).offset().top)}
});
// set up the droppable
$( ".droppable" ).droppable({
drop: function( event, ui ) {
dropShape(ui.position.left, ui.position.top)
}
});
// Function to create a new shape when we drop something dragged from the palette
function dropShape() {
var c1 = new Konva.Circle({x: cross.x(), y: cross.y(), radius: 22, fill: 'red', stroke: 'black'});
layer1.add(c1);
cross.x(0); cross.y(0);
cross.moveToTop(); // move the cross to the top to stop going bahind previously dropped shapes.
s1.draw();
}
p
{
padding: 4px;
}
#container1
{
display: inline-block;
width: 500px;
height: 200px;
background-color: silver;
overflow: hidden;
}
#pallette
{
height: 52px; width: 500px;
border: 1px solid #666;
margin-bottom: 10px;
z-index: 10;
}
.draggable
{
width:50px;
height: 50px;
display: inline-block;
border: 1px solid #666;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Drag a red circle from the pallette and drop it on the grey canvas.
</p>
<div id='pallette'></div>
<div id='container1'></div>
I tried Vanquished Wombat's solution, it was a great example. But ultimately I wanted my palette to be separate from Konva. So I modified that original snippet to work with Html5 drag & drop, without any jQuery. See the snippet below. You can drag stars & circles from the palette into the Konva canvas. Currently you have to drop onto another shape, but you can modify it easily to drop anywhere on the canvas. I'm using text for the palette items and a custom image for the drag object just for fun. But you can just use an img instead of using the setDragImage code.
const CUSTOM_DATA_TYPE = 'text/x-node-type';
// Set up the canvas to catch the dragged shapes
var s1 = new Konva.Stage({
container: 'container1',
width: 500,
height: 200
});
// add a layer to host the 'dropped' shapes.
var layer1 = new Konva.Layer({
draggable: false
});
s1.add(layer1);
for (let t = 0; t < 10; t++) {
let rect = document.getElementById('container1').getBoundingClientRect();
let x = Math.floor(Math.random() * rect.width);
let y = Math.floor(Math.random() * rect.height);
let type = Math.floor(Math.random() * 100) % 2 == 0 ? 'circle' : 'star';
dropShape(x, y, type);
}
// Function to create a new shape when we drop something dragged from the palette
function dropShape(x, y, type) {
var shape;
if (type == 'circle') {
shape = new Konva.Circle({
x: x,
y: y,
radius: 22,
fill: 'blue',
stroke: 'black'
});
} else {
shape = new Konva.Star({
x: x,
y: y,
numPoints: 5,
innerRadius: 10,
outerRadius: 20,
fill: 'purple',
stroke: 'black'
});
}
layer1.add(shape);
s1.draw();
}
function cursorToCanvasPos(e) {
let clientRect = document.getElementById('container1').getBoundingClientRect();
let pointerPosition = {
x: e.clientX - clientRect.x,
y: e.clientY - clientRect.y,
};
return pointerPosition;
}
function getHoveredShape(e) {
let pointerPosition = cursorToCanvasPos(e);
return s1.getIntersection(pointerPosition);
}
function onDragStart(e, type) {
// Do this or other things can mess with your drag
e.stopPropagation();
e.dataTransfer.setData(CUSTOM_DATA_TYPE, type);
e.dataTransfer.effectAllowed = "all";
var dragIcon = document.createElement('img');
dragIcon.src = 'https://placehold.it/100x100';
dragIcon.width = 100;
e.dataTransfer.setDragImage(dragIcon, 150, 150);
}
function onDragOver(e) {
// Might break if you don't have this
e.stopPropagation();
// Breaks for sure if you don't have this
e.preventDefault();
let thing = getHoveredShape(e);
if (thing) {
e.dataTransfer.dropEffect = "move";
// Just fire off a custom even if you want to, this does nothing in this example.
thing.fire('htmlDragOver');
} else {
e.dataTransfer.dropEffect = "none";
}
}
function onDrop(e) {
e.stopPropagation();
let type = e.dataTransfer.getData(CUSTOM_DATA_TYPE);
let pos = cursorToCanvasPos(e);
dropShape(pos.x, pos.y, type);
}
p {
padding: 4px;
}
#container1 {
display: inline-block;
width: 500px;
height: 200px;
background-color: silver;
overflow: hidden;
}
#palette {
height: 52px;
width: 500px;
border: 1px solid #666;
margin-bottom: 10px;
z-index: 10;
}
#palette span {
width: 50px;
height: 25px;
display: inline-block;
border: 1px solid #666;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Drag circle/star from the palette onto an existing shape on the canvas below.
</p>
<div id='palette'>
<!-- Pre-load this image so it'll be used for our drag -->
<img src="https://placehold.it/100x100" style="display: none">
<span draggable="true" ondragstart="onDragStart(event, 'circle')">circle</span>
<span draggable="true" ondragstart="onDragStart(event, 'star')">star</span>
</div>
<div id='container1' ondragover="onDragOver(event)" ondrop="onDrop(event)"></div>
Added some labels to my Google Map (v3), ideally I'd like two things:
1) To be able to switch them on and off (as when zoomed out the labels become cluttered)
2) To be able to change the textsize of the label depending up the mapzoom.
I added the labels like so, info being read in from some nested arrays:
for (x = 0; x < areadata.length; x++){//Start Label Loop
labelObjects[x] = new MapLabel({
text: areadata [x][0],
position: new google.maps.LatLng(areadata [x][2], areadata [x][1]),
map: mymap,
fontSize: 16,
align: 'center'
});
labelObjects[x].set('position', new google.maps.LatLng(areadata [x][2], areadata [x][1]));
}
I'm using the maplabel-compiled.js from http://google-maps-utility-library-v3.googlecode.com/svn/trunk/maplabel/examples/maplabel.html - with one change however. mapPane.appendChild has been amended to floatPane.appendChild - this brings all labels in front of any Polygons I have on the map.
This works just fine, the problem comes when I try to control the labels, I've tried interacting with the first label in the array like so with no joy:
labelObjects[0].setVisible(false);
labelObjects[0].set('visible', false);
labelObjects[0].set('fontSize', 48);
Anyone had similar issues? Thanks for reading.
be sure that labelObjects is accessible in the scope where try to toggle the mapLabel
there is no method setVisible for a MapLabel
setting a visible-property of a MapLabel will not have any effect. To show/hide the MapLabel set the map-property of the MapLabel to either a google.maps.Map-instance(mymap) or null
var areadata = [
['label#1', 1, 1],
['label#2', 2, 2]
],
labelObjects = [],
mymap;
function init() {
var myLatlng = new google.maps.LatLng(1.5, 1.5),
myOptions = {
zoom: 7,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
mymap = new google.maps.Map(document.getElementById('map'), myOptions);
for (x = 0; x < areadata.length; x++) { //Start Label Loop
labelObjects[x] = new MapLabel({
text: areadata[x][0],
position: new google.maps.LatLng(areadata[x][2], areadata[x][1]),
map: mymap,
fontSize: 16,
align: 'center'
});
labelObjects[x].set('position', new google.maps.LatLng(areadata[x][2], areadata[x][1]));
}
mymap.controls[google.maps.ControlPosition.TOP_CENTER].push(document.getElementById('toggle'));
}
google.maps.event.addDomListener(window, 'load', init);
body,
html,
#map {
margin: 0;
padding: 0;
height: 100%;
}
#toggle {
padding: 1px 6px;
border: 1px solid rgba(0, 0, 0, 0.15);
box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
border-radius:2px;
background: #fff;
cursor: pointer;
margin:4px;
}
<script type="text/javascript" src="http://maps.google.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/js-map-label#1.0.1/src/maplabel.js"></script>
<div id="map"></div>
<a id="toggle" onclick="labelObjects[0].setMap((labelObjects[0].getMap())?null:mymap)">toggle label#1</a>
If I have a canvas in which an image is drawn. The image has four points in the curve. I need to make this curve area draggable.
but without use of kinetic Js
Image attached.
HTML
<div id='curveAreaDrag' class="ui-widget-content" >
<div id="point1" class="ui-widget-content"></div>
<div id="point2" class="ui-widget-content"></div>
<div id="point3" class="ui-widget-content"></div>
<div id="point4" class="ui-widget-content"></div>
</div>
<canvas id="myCanvas" width="400px" height="400px"
style="border:1px solid #d3d3d3;top:0;left:0"></canvas>
jQuery
$("#curveAreaDrag").draggable({drag:function(){save();}});
Updated:
I have four points of bezier curve. i am also scaling each point then the light black area also scaling.
i need to drag the light black area. it is not an image. it is selected area by bezier curve.
[ Edited: To fit OP’s additional information ]
This code uses 2 Cubic Bezier Curves and 6 control handles to let you select the eyeball in your image.
The Red handles control the curve on the top part of the eye. The Blue handles control the curve on the bottom part of the eye. The White handles let you anchor on the corners of the eye. The Orange shows the resulting path (=2 curves).
Below is a picture of the app and here is a Fiddle of it in action: http://jsfiddle.net/m1erickson/98G9F/
Here is the code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.1/jquery-ui.js"></script>
<style>
body { font-family: arial; }
.anchor { background: #fff; position: absolute; display: block; height: 10px; width: 10px; border: 1px solid #333; border-radius:5px;}
.control { background: #fff; position: absolute; display: block; height: 20px; width: 20px; border: 1px solid #333; border-radius:10px;}
.p1, .p2 { background: red; z-index: 50; }
.p3, .p4 { background: blue; }
.pStart { background:white;}
.pEnd {background:white;}
.pStart { left: 54px; top: 166px; }
.pEnd { left: 346px; top: 189px; }
.p1 { left: 150px; top: 39px; }
.p2 { left: 276px; top: 97px; }
.p3 { left: 219px; top: 227px; }
.p4 { left: 147px; top: 224px; }
canvas { border: 1px solid #333;}
#start,#end2{color:green;}
#end1{color:purple;}
#c1,#c3{color:red;}
#c2,#c4{color:blue;}
</style>
</head>
<body>
<canvas height="400" width="400" id="canvas"></canvas>
<p>context.moveTo(
<span id="start"></span>);
</p>
<p>context.bezierCurveTo(
<span id="c1"></span>,
<span id="c2"></span>,
<span id="end1"></span>);
</p>
<p>context.bezierCurveTo(
<span id="c3"></span>,
<span id="c4"></span>,
<span id="end2"></span>);
</p>
<script>
var $p1,$p2,$codeMove,$codeBez1,$codeBez2;
$(function() {
var canvas=document.getElementById("canvas");
var ctx = canvas.getContext("2d");
$pStart=$(".pStart");
$pEnd=$(".pEnd");
$p1 = $(".p1");
$p2 = $(".p2");
$p3 = $(".p3");
$p4 = $(".p4");
$codeMove=$("code-move");
$codeBez1=$("code-bez1");
$codeBez2=$("code-bez2");
$(".pStart, .pEnd, .p1, .p2, .p3, .p4").draggable({
containment: 'parent',
drag: function(event, ui) { renderWrap(ctx); },
stop: function(){ renderWrap(ctx); }
});
var eyeImage=new Image();
eyeImage.onload=function(){
canvas.width=eyeImage.width*1.5;
canvas.height=eyeImage.height*1.5;
renderWrap(ctx);
}
eyeImage.src="http://i.stack.imgur.com/SbcL4.png";
function renderWrap(ctx) {
var pStart=$pStart.position();
var pEnd=$pEnd.position();
var p1 = $p1.position();
var p2 = $p2.position();
var p3 = $p3.position();
var p4 = $p4.position();
render({x:pStart.left, y:pStart.top}, {x:pEnd.left, y:pEnd.top}, {x:p1.left, y:p1.top}, {x:p2.left, y:p2.top}, {x:p3.left, y:p3.top}, {x:p4.left, y:p4.top} );
};
function render(start, end, p1, p2, p3, p4) {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(eyeImage,0,0,eyeImage.width,eyeImage.height,0,0,eyeImage.width*1.5,eyeImage.height*1.5);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "orange";
// start-->end
ctx.moveTo(start.x,start.y);
ctx.bezierCurveTo(p1.x,p1.y,p2.x,p2.y,end.x,end.y);
ctx.stroke();
// end-->start
ctx.bezierCurveTo(p3.x,p3.y,p4.x,p4.y,start.x,start.y);
ctx.stroke();
ctx.closePath();
// connectors
ctx.beginPath();
ctx.strokeStyle = "#999";
ctx.lineWidth = 1;
connector(start,p1);
connector(end,p2);
connector(end,p3);
connector(start,p4);
ctx.closePath();
// display code
$("#start").html(start.x+","+start.y);
$("#c1").html(p1.x+","+p1.y);
$("#c2").html(p2.x+","+p2.y);
$("#end1").html(end.x+","+end.y);
$("#c3").html(p3.x+","+p3.y);
$("#c4").html(p4.x+","+p4.y);
$("#end2").html(start.x+","+start.y);
}
function connector(pt1,pt2){
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.stroke();
}
});
</script>
</body>
</html>
I am using transitionTo method of kineticJS to show animated rotation of a shape on click event of mouse. It works fine if we click the shape first time but then on subsequent clicks it does not rotate the shape. I want to show transition(rotation) of the shape by some angle every time I click on it. Please let me know the mistake I am making and how can I correct it??
This is the code I am using
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid #9C9898;
}
</style>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v3.9.6.js"></script>
<script>
window.onload = function() {
var stage = new Kinetic.Stage({
container: "container",
width: 578,
height: 200
});
var layer = new Kinetic.Layer({
x:stage.getWidth()/3 ,
y: stage.getHeight()/3
});
var rect = new Kinetic.Rect({
x: 239,
y: 75,
width: 100,
height: 50,
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4,
centerOffset: [50, 100]
});
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);
rect.on("click", function() {
rect.transitionTo({
rotation:2*Math.PI,
duration:1
});
stage.draw();
});
};
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
Try this:
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid #9C9898;
}
</style>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v3.10.0.js"></script>
<script>
window.onload = function() {
var angle = 0;
var stage = new Kinetic.Stage({
container: "container",
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 239,
y: 75,
width: 100,
height: 50,
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4
});
layer.add(rect);
stage.add(layer);
rect.on("click", function() {
angle += 2;
rect.transitionTo({
rotation: Math.PI * angle,
duration:1
});
});
};
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
The click was working just fine, however you were telling it to rotate to the same angel every time (why it only animates on the first click). I added a variable so that the angle increases 360 degrees every time you click on it.