HTML5 Canvas layer to HTML/CSS code - javascript

I've got a canvas on which I can add multiple layers (text and images), I use this to make templates. What I am trying to achieve at the end is that when I add a layer there will be a block of HTML/CSS created that contains the properties of the layer, these properties are:
X
Y
Width
Height
Font
Font-size
Font-Color
Angle
Content
In the end I want to achieve that when I add a layer a block of code will be created and when I change any of the layers properties by for example moving it then the code should be changed aswell and when I change the code then the layer should change accordingly on the canvas.
I have searched alot for something like this but I wasn't able to find anything. If someone knows how I can do this (if it's even possible) it would be great!
A working example of my canvas can be found here:
http://codepen.io/anon/pen/HvLDs (If it doesnt do anything when you've added a layer try refreshing)
Pastebins of the codes:
http://pastebin.com/uRqiKzGd
http://pastebin.com/CXF6DtyC

You are using the wrong technology. HTML5 Canvas doesn't record the elements you add in the DOM, it's more of a 'Fire and Forget' technology. Once you've rendered it, you've lost it essentially.
If you look at SVG on the otherhand, each SVG item has a DOM representation that you can clearly see, manipulate and style using CSS. Layering SVG would be a much more appropriate approach to this problem than trying to use Canvas.
To illustrate, here's some example code to render a Canvas circle:
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 70;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#003300';
context.stroke();
</script>
The resultant DOM however just looks like:
<canvas id="myCanvas" width="578" height="200"></canvas>
If you compare this to SVG, you've got the actual circle within the DOM:
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>

Related

Scaling a html canvas

I have a canvas:
<canvas id="canvas" width="400" height="400"/>
that I want to be able to scale dynamically using javascript, so I want to use scale(). But this is not working:
document.getElementById("canvas").getContext('2d').scale(2, 2);
The canvas remains at 400x400, even though I want it to be 800x800.
Live demo at https://jsfiddle.net/c8sjpr37/3/ using vue. Why doesn't it work?
The scale command is an internal canvas command. It changes the scale at which things are drawn. For example:
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.strokeRect(5, 5, 25, 15);
ctx.scale(2, 2);
ctx.strokeRect(5, 5, 25, 15);
<div id="app">
<canvas id="canvas" width="400" height="400"/>
</div>
This snippet displays a rectangle, and then another rectangle twice the size of the first one and twice the distance from the translation point, because all of the pixel values were doubled with the .scale() command. I don't think this is what you wanted.
There is a way to edit the height and width styling, though. Canvases are picky, so if you change one, the other will change to match the initial ratio, but you can do this with something like:
document.getElementById("canvas").style.height = "800px";

Clipping image to an svg

Is there a way to clip an image to an SVG shape with a CanvasRenderingContext2D?
I'm trying to use different SVG shapes to display various parts of an image on demand.
For example – an SVG with an <ellipse> element (or an equivalent <path>) would allow me to show a circular portion of the image.
CanvasRenderingContext2D.clip() seems to be the close to what I need, but I can't find any information about how to use it with an SVG, or alternatively how to draw an SVG as a path.
Another direction I'm thinking about is saving the clipping area as a <path> element and manually transforming it to CanvasRenderingContext2D equivalent methods such as lineTo and arc.
Three steps:
Draw the SVG to your canvas.
Set context.globalCompositeOperation = 'source-in';
Draw the image.
The image will only be drawn where the SVG has already filled in pixels with colour, effectively clipping the image to whatever has already been drawn.
You can either set the globalCompositeOperation back to 'source-over' (the default), or use context.save() before and context.restore() after to put the canvas back into a "normal" drawing mode.
#Kaiido has some good suggestions for you (see his comment to the question).
Draw the SVG to canvas & use globalCompositeOperation instead of clipping. But Firefox has a bug in applying gCO directly to an svg image, which force you to first draw your svg on a second canvas ; and that IE prior to Edge will taint the canvas when an svg is drawn to it.
Parse your svg first and then use the canvas API to draw those shapes (path commands are quite similar so it's not so hard and library like fabricjs can even handle it in a nice way for you)
Another option is to convert your SVG drawings to .png format and use that image + globalCompositeOperation to clip your image inside the .png shape. This avoids the cross-browser problems with SVG.
But, if your clipping shapes are just simple SVG paths (ovals, etc), then you might forget SVG and draw your path using canvas path commands.
I'll repost a previous SO Q&A to illustrate clipping inside a canvas path:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/kidwallpaper.jpg";
function start(){
// resize the canvas to equal the image size
var iw=img.width;
var ih=img.height;
cw=canvas.width=iw;
ch=canvas.height=ih;
// calculate the scaling needed to max the display of the image
// inside the oval
if(iw>ih){
var scaleX=iw/ih
var scaleY=1;
var r=ih/2;
}else{
var scaleX=1;
var scaleY=ih/iw;
var r=iw/2;
}
// scale so the circle (arc) becomes an oval
ctx.scale(scaleX,scaleY);
ctx.arc(cw/scaleX/2,ch/scaleY/2,r,0,Math.PI*2);
ctx.fill();
// undo the scaling
ctx.scale(1/scaleX,1/scaleY);
// draw the image centered inside the oval using compositing
ctx.globalCompositeOperation='source-atop';
ctx.drawImage(img,cw/2-img.width/2,ch/2-img.height/2);
ctx.globalCompositeOperation='source-atop';
}
body{ background-color: black; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
You cannot do this directly. You can:
Draw the SVG into a canvas and use that for compositing to clip. Drawback is that some browsers has limited support due to security (external links, tainting the canvas and so on).
Parse the SVG manually
Here is an example of how you can build a simple parser for SVG. It cannot be used for generic use but assumes you know the SVG in question. You can build on top of this to support different units, transform lists etc.
The path that is extracted can be stored on a Path2D object instead of directly as shown below (Path2D may need poly-fill in some browsers), or store custom objects/arrays etc. This is entirely up to you.
The example is more a proof-of-concept.
Example SVG parser to canvas Path2D
var ctx = c.getContext("2d"),
mask = document.getElementById("mask"); // get a SVG element
// some random graphics
for(var i=30,r=Math.random;i--;) {
ctx.fillStyle = "hsl(" + (360*r()) + ",50%,50%)";ctx.fillRect(280*r(),120*r(),50,50)}
// parse SVG element
if (mask.localName) {
switch(mask.localName) {
case "rect":
ctx.rect(v("x"), v("y"), v("width"), v("height"));
break;
case "ellipse":
// need polyfill in some browsers
ctx.ellipse(v("cx"), v("cy"), v("rx"), v("ry"), 0, 6.28);
break;
// more cases here
}
// use path from SVG to clip
ctx.globalCompositeOperation = "destination-in";
ctx.fill();
}
// helper - obtains a numeric value for SVG element property
function v(name) {return mask[name].baseVal.value}
#c, svg {border:1px solid #777}
<h4>SVG (showing mask)</h4>
<svg xmlns="http://www.w3.org/2000/svg"
width="300" height="150">
<rect id="mask" x="50" y="30" width="200" height="100" />
</svg>
<h4>Canvas (mask applied from SVG)</h4>
<canvas id=c></canvas>

HTML5 canvas - text on rotated rectangle

I'm trying to place text on sqlare rotated 45 degrees. The problem is my text is under the rectangle shape. Can I change position of text?
my code is like
var c = document.getElementById("myCanvas");
var square= c.getContext("2d");
var text= c.getContext("2d");
text.fillStyle = "red";
text.fillText("1", 40, 50);
text.fillStyle = "#000000";
square.rotate(Math.PI / 4);
square.fillRect(50, 0, 50, 50);
jsfiddle
You're drawing the text first, then the rectangle, and then wondering why the text is behind the rectangle?
Firstly, you only need to getContext once, not twice.
Second, draw things in the right order: background first, then foreground.
It appears you may have a misconception about how the canvas context works.
You see, you only need one instance of the context, which you can then use to create all your shapes, paths and even write text on the canvas.
Also, you are drawing the text before the rectangle, which would cover it up.
With that in mind, I've created a new JSFiddle where you can check out a correct approach to do this.
HTML
<canvas id="myCanvas" width="300" height="150">
Your browser does not support the HTML5 canvas tag.
</canvas>
JavaScript
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d"); //we get the canvas context
ctx.save(); //save context properties
ctx.rotate(Math.PI / 4);
ctx.fillRect(50, 0, 50, 50);
ctx.restore(); //restore saved properties
ctx.fillStyle = "red";
ctx.fillText("1", 40, 50);
As you can see, we only take one instance of the context, and we draw from there.
The context save() and restore() functions help prevent the rotation from affecting the text. You could also rotate the same amount in the opposite direction.
Hope it helps!

HTML canvas coordinates are different when created using JS versus embed in HTML

Playing with HTML5 canvas and JS, I found a strange behaviour when a canvas is added to the HTML body directly versus creating a canvas using JS.
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas id="test" width="200" height="200" style="border:1px solid #000000;">
</canvas>
<script>
var c=document.getElementById("test");
ctx=c.getContext("2d");
ctx.fillStyle = "#9ea7b8";
ctx.fill();
ctx.moveTo(0,0);
ctx.lineTo(200,200);
ctx.stroke();
// creating canvas using JS
c = document.createElement("canvas");
c.id="MyCanvas";
c.style.width="200px";
c.style.height="200px";
c.style.border="1px solid #000000";
ctx=c.getContext("2d");
ctx.fillStyle = "#9ea7b8";
ctx.fill();
ctx.moveTo(0,0);
ctx.lineTo(200,200);
ctx.stroke();
document.body.appendChild(c);
</script>
</body>
</html>
Please see the code & ouput here
I expected the line (stroke) to be a consistent diagonal across the canvas but alas!. Please help me know where am I going wrong!
Note: I forgot to mention, I tried this on Chrome only not sure if the behaviour is consistent for other browsers.
So, basically if you change from style to attribute it works.
Why ?
It seems that the width and height attributes determine the width or height of the canvas's coordinate system, whereas the CSS properties just determine the size of the box in which it will be shown.
Source
Like this it will work fine:
var c = document.getElementById("test");
ctx = c.getContext("2d");
ctx.fillStyle = "#9ea7b8";
ctx.fill();
ctx.moveTo(0, 0);
ctx.lineTo(200, 200);
ctx.stroke();
// creating canvas using JS
c = document.createElement("canvas");
c.id = "MyCanvas";
c.setAttribute("width", "200px")
c.setAttribute("height", "200px")
c.style.border = "1px solid #000000";
ctx = c.getContext("2d");
ctx.fillStyle = "#9ea7b8";
ctx.fill();
ctx.moveTo(0, 0);
ctx.lineTo(200, 200);
ctx.stroke();
document.body.appendChild(c);
<canvas id="test" width="200" height="200" style="border:1px solid #000000;"></canvas>
Canvas width and height attributes are not the same as its CSS width and height. Setting canvas.width/height attributes determines the total drawable pixel area, which can be (but does not need to be) scaled with CSS to be larger or smaller on the screen.
Normal scenario: Make canvas attribute bounds larger than CSS bounds
In fact, to make a high density display canvas it is necessary to set canvas.width and canvas.height twice as large as the css. In other words you might do:
// Two canvas pixels per screen pixel so it looks nice
// on a high density (in this case pixel ratio: 2) display
canvas.width = 800;
canvas.height = 800;
canvas.style.width = '400px';
canvas.style.height = '400px';
Normal scenario: Make canvas attribute bounds smaller than CSS bounds
On the flip side in order to make some apps like games fast canvas.width and canvas.height might be restricted to 640x480 (or something small) and then scaled with CSS to take up the whole screen. Since the total number of pixels handled on the canvas is small, the game will be faster than if you used a really large canvas and filled the screen. Obviously the game will look different, since CSS will be scaling the graphics (for better or worse).

Grid with clicable irregular shapes

I'm having a bit of trouble here to develop this functionality since it must work on IE9+ so css clip-path is not an option ( http://caniuse.com/#feat=css-clip-path ).
The issue:
I need to create a grid composed of 6 elements.
Each element is an image.
The images can be different according to user answers before getting to the grid page.
Eeach element / image must be clicable and will acquire a "selected" class that will overlay div with text and background image.
image:
What is the best way to achieve this?
One way to do this could be to save out each combination of the six images you require into one big image. Then, depending on the user's answer combination, you insert the corresponding image as a background-image of a div. You then overlay click-able hotspots within the same div that roughly correlate to the dividing edges.
This may however not be the most practical solution and largely depends on how many answers/images you are dealing with.
Alternatively you could draw SVG shapes and set their fills to the images you require.
I can recommend Raphael.js as a starting point. You should be able to find what you need in the documentation
Another option would be to use HTML5 canvas:
http://jsfiddle.net/julienbidoret/GKP7X/1/
(credit goes to julienbidoret for the jsfiddle)
Javascript:
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var img = document.createElement('IMG');
img.onload = function () {
ctx.save();
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(240, 0);
ctx.lineTo(220, 240);
ctx.lineTo(0, 240);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
}
img.src = "http://upload.wikimedia.org/wikipedia/commons/2/2b/Clouds.JPG";
HTML:
<canvas id="c" width="300" height="300" ></canvas>
Both SVG and canvas are supported in IE9.

Categories

Resources