I am trying to learn about fabricjs and noticed that when i create a new fabric.Canvas object it changes the position of my canvas.
HTML
<canvas id="c"></canvas>
Css
#c {
border: thin red solid;
position: absolute;
top: 50px;
left: 100px;
}
Javascript
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var img = new Image();
img.src = 'cheese.jpg';
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
// applying the below line shifts the canvas element back to 0,0 position
var cFabric = new fabric.Canvas('c');
Hoping you guys know what i am doing wrong.
You are not doing anything wrong. Calling the fabric.Canvas constructor on your native canvas element will result in your native canvas getting wrapped by a .canvas-container div. The original canvas gets an added .lower-canvas class and its left, right css styles are set to 0px. Other then that, a sibling canvas is added below your original canvas, with a class of upper-canvas. These two canvases act like layers, managed by the inner workings of Fabric.js (magic :O).
If you need to position and style your canvas, I recommend you wrap your html canvas with a wrapper div.
<div id="canvas-wrapper">
<canvas id="c"></canvas>
</div>
Next transfer your css rules to the wrapper
#canvas-wrapper {
position: absolute;
top: 50px;
left: 100px;
}
Here's a simple fiddle that I made by updating #Mullainathan's sample fiddle: https://jsfiddle.net/eo7vdg1t/1/
Related
I'm using p5.js to make a GIF animation but I have an issue when I want to export it.
I did a pixelased effect on my animation by adding these css properties to the Canva (140*140) :
image-rendering: pixelated;
width:500px;
height:500px;
My problem is that I can't export this Canva with the properties I added. (I'm using CCapture)
My gif is in 140x140 without the pixelated effect.
How can I get the rendering I need?
There is a difference between the width & height you can set for a HTML element e.g. <canvas width='140' height='140'> and the CSS width & height properties.
The former defines the actual size of the canvas - 140x 140in this case.
If we now set the CSS width & height to something deviating from the HTML element's width & height e.g. <canvas width='140' height='140' style='width: 500px; height:500px;'> the actual size in pixels of the canvas does not change - it stays 140 x 140 pixels. The CSS properties just control the displayed size of the element inside the browser, meaning the 140 x 140 are simply stretched to 500 x 500.
So if you get actual pixel data of the canvas - for exporting to gif/png - the final image will have the original dimensions of the canvas - not the rendered.
The fix is quite simple though. Instead of directly using the source canvas for exporting, draw it's content on a second canvas, the size of your desired resolution.
To force the 'export' canvas to not use any filtering/smoothing, you need to set the imageSmoothingEnabled property of it's context to false.
Here's an example (you can right-click and save both images to see the difference):
var ctx = document.getElementById('canvas').getContext('2d');
var image = new Image();
image.onload = function() {
ctx.drawImage(image, 0, 0);
let sourceCanvas = document.getElementById("canvas");
let virtualWidth = parseInt(getComputedStyle(sourceCanvas).width);
let virtualHeight = parseInt(getComputedStyle(sourceCanvas).height);
var canvas = document.getElementById("exportCanvas");
canvas.width = virtualWidth;
canvas.height = virtualHeight;
canvas.getContext('2d').imageSmoothingEnabled = false;
canvas.getContext('2d').drawImage(sourceCanvas, 0, 0, virtualWidth, virtualHeight);
}
image.src = 'https://mdn.mozillademos.org/files/12640/cat.png';
canvas {
width: 500px;
height: 500px;
}
<span>Rendered canvas</span><br>
<canvas id="canvas" width="140" height="140"></canvas><br>
<span>Export canvas</span><br>
<canvas id="exportCanvas"></canvas>
I have the following code for a beginner's flappy bird game, but am struggling to understand how to add a green-yellow block (using css and an html div and id) into the existing game which is using the context and canvas.
I have this code so far, and wish to insert into it (on the drawn canvas which is 400 by 400) a green-yellow block as generated by this css:
I want to add this in the html:
<div id="block"></div>
with this CSS:
<style>
#block{
width: 50px;
height: 500px;
background-color: greenyellow;
position: relative;
left: 400px;
animation: block 2s infinite linear;
}
#keyframes block{
0%{left:400px}
100%{left:-50px}
}
</style>
The code I have currently is here (all in one file):
<body style="height: 100vh; background: #111; text-align: center;">
<canvas id="c" width="400" height="400"></canvas>
<script>
//set up context
context = c.getContext("2d");
//create bird
const bird = new Image();
bird.src = "bird.png";
//create variables
var canvasSize = 400;
var birdSize=30;
var birdX = 0;
var birdY =200;
var birdDY = 0;
var score = 0;
var bestScore=0;
var interval = 30; //the speed at which the game is played
c.onclick = () => (birdDY = 9) ;
//expand the avove out into an if statement?
setInterval(() => {
context.fillStyle = "skyblue";
context.fillRect(0,0,canvasSize,canvasSize); // Draw sky
birdY -= birdDY -= 0.5; // Gravity
context.drawImage(bird, birdX, birdY, birdSize * (524/374), birdSize); // Draw bird
context.fillStyle = "black";
context.fillText(`Flappy Birds`, 170, 10); //x and y
context.fillText(`Score: ${score++}`,350, 380); // Draw score
}, interval)
</script>
</body>
I have tried to integrate my css generated block like so, but while it does generate the blocks on the screen, they are not inside the canvas.
<style>
#block{
width: 50px;
height: 500px;
background-color: greenyellow;
position: relative;
left: 400px;
animation: block 2s infinite linear;
}
#keyframes block{
0%{left:400px}
100%{left:-50px}
}
</style>
<body style="height: 100vh; background: #111; text-align: center;">
<canvas id="c" width="400" height="400"></canvas>
<div id="block"></div>
<script>
//set up context
context = c.getContext("2d");
//create bird
const bird = new Image();
bird.src = "bird.png";
//create variables
var canvasSize = 400;
var birdSize=30;
var birdX = 0;
var birdY =200;
var birdDY = 0;
var score = 0;
var bestScore=0;
var interval = 30; //the speed at which the game is played
c.onclick = () => (birdDY = 9) ;
//expand the avove out into an if statement?
setInterval(() => {
context.fillStyle = "skyblue";
context.fillRect(0,0,canvasSize,canvasSize); // Draw sky
birdY -= birdDY -= 0.5; // Gravity
context.drawImage(bird, birdX, birdY, birdSize * (524/374), birdSize); // Draw bird
context.fillStyle = "black";
context.fillText(`Flappy Birds`, 170, 10); //x and y
context.fillText(`Score: ${score++}`,350, 380); // Draw score
}, interval)
</script>
</body>
For an answer, I would like
a) a solution as to how to generate the css blocks INSIDE the current context/canvas
b) Explanation as to the workings of it, and why this approach did not work.
I have previously never worked with context generated entities before.
c)It would also be useful to see (separately) how the same thing is achieved using context/canvas as opposed to CSS to compare which one is more efficient.
I've also tried adding the div blocks inside the interval function but that didn't work at all.
Importantly, if I continue down this line of using these two different approaches, will I be able to code in collision detection? Please explain if there are difficulties that would crop up with this approach.
This question might be a duplicate of Html over the Canvas?.
However...
a) Solution is to use position: absolute; instead of position: relative; and then use left and top for positioning.
b) The canvas is a DOM element as the div. There isn't really a way to overlap different DOM elements without making them absolute to the window. Using position: absolute; the block will ignore the canvas in regards to position and styling.
c) You could create the blocks using JS. If so, you would want to use something like this:
.block {
position: absolute;
width: 50px;
background-color: greenyellow;
animation: block 2s infinite linear;
}
#keyframes block{
0%{left:400px}
100%{left:-50px}
}
let block = document.createElement('div');
block.setAttribute('left', 400px);
block.setAttribute('top', 280px);
block.setAttribute('height', 100px);
document.appendChild(block);
Then you could change the left tag using block.style.left inside your loop. You could also get the value to make collision detection. You would also probably use unique IDs for each block or having them in an array.
However this seems to be way more complicated than just using the canvas and context.rectangle(). It is a bad idea to mix DOM and canvas in this way when you can use canvas for everything.
I think you are better of looking up a tutorial on how to use entities.
Tips:
The Coding Train, great tutorials. I think he have made a version of Flappy Bird.
Try using p5.js it is a great library for beginners but could be used for more advanced stuff. P5 is much simpler than using pure JS/Context canvas.
Started using fabric.js and trying to add a canvas inside another canvas, so that the top canvas stays constant and I'll add objects to inner canvas.
Here is the snippet of adding a canvas to another canvas.
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
canvas.add(innerCanvas);
and my html looks like this
<canvas id="artcanvas" width="500" height="500"></canvas>
<canvas id="innerCanvas" width="200" height="200" ></canvas>
Once adding these successfully, what I am going to do is , add the coordinates to the inner canvas, so that it looks like one on another to the end user.
However, ran into the below error for the tried code
Uncaught TypeError: obj.setCoords is not a function
at klass._onObjectAdded (fabric.js:6894)
at klass.add (fabric.js:231)
at main.js:60
at fabric.js:19435
at HTMLImageElement.fabric.util.loadImage.img.onload (fabric.js:754)
_onObjectAdded # fabric.js:6894
add # fabric.js:231
(anonymous) # main.js:60
(anonymous) # fabric.js:19435
fabric.util.loadImage.img.onload # fabric.js:754
Looking at the error message, just went to the line of error and here is what I found in chrome console
Can someone point the mistake in my codes ?
After going through no.of discussions and internet solutions, for time being I am using Fabric Rectangle as a clipper and setting it's boundaries so user can be able to drop/play with in that particular clipper.
Dotted red(image below) is my clipper and now I can bound the dropping and below is the code to add an image with a clipper.
function addImageToCanvas(imgSrc) {
fabric.Object.prototype.transparentCorners = false;
fabric.Image.fromURL(imgSrc, function(myImg) {
var img1 = myImg.set({
left: 20,
top: 20,
width: 460,
height: 460
});
img1.selectable = false;
canvas.add(img1);
var clipRectangle = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 150,
top: 150,
width: 200,
height: 200,
fill: 'transparent',
/* use transparent for no fill */
strokeDashArray: [10, 10],
stroke: 'red',
selectable: false
});
clipRectangle.set({
clipFor: 'layer'
});
canvas.add(clipRectangle);
});
}
Now while appending any image/layer to the canvas, I bind that image/layer/text to the clipper I created.
function addLayerToCanvas(laImg) {
var height = $(laImg).height();
var width = $(laImg).width();
var clickedImage = new Image();
clickedImage.onload = function(img) {
var pug = new fabric.Image(clickedImage, {
width: width,
height: height,
left: 150,
top: 150,
clipName: 'layer',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
clickedImage.src = $(laImg).attr("src");
}
And the looks like, after restriction of bounds,
Here is the fiddle I have created with some static image url.
https://jsfiddle.net/sureshatta/yxuoav39/
So I am staying with this solution for now and I really feel like this is hacky and dirty. Looking for some other clean solutions.
As far as I know you can't add a canvas to another canvas - you're getting that error as it tries to call setCoords() on the object you've added, but in this case it's another canvas and fabric.Canvas doesn't contain that method (see docs). I think a better approach would be to have two canvases and position them relatively using CSS - see this simple fiddle
HTML
<div class="parent">
<div class="artcanvas">
<canvas id="artcanvas" width="500" height="500"></canvas>
</div>
<div class="innerCanvas">
<canvas id="innerCanvas" width="200" height="200" ></canvas>
</div>
</div>
CSS
.parent {
position: relative;
background: black;
}
.artcanvas {
position: absolute;
left: 0;
top: 0;
}
.innerCanvas {
position: absolute;
left: 150px;
top: 150px;
}
JS
$(document).ready(function() {
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
var rect = new fabric.Rect({
fill: 'grey',
width: 500,
height: 500
});
canvas.add(rect);
var rect2 = new fabric.Rect({
fill: 'green',
width: 200,
height: 200
});
innerCanvas.add(rect2);
})
To handle the object serialization, you can do something like this:
var innerObjs = innerCanvas.toObject();
console.dir(innerObjs);
var outerObjs = canvas.toObject();
innerObjs.objects.forEach(function (obj) {
obj.left += leftOffset; // offset of inner canvas
obj.top += topOffset;
outerObjs.objects.push(obj);
});
var json = JSON.stringify(outerObjs);
This will then give you the JSON for all objects on both canvases
I have no understanding why you want to do this thing, but to put a canvas inside another canvas, you have one simple way:
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
imageContainer = new fabric.Image(innerCanvas.lowerCanvasEl);
canvas.add(imageContainer);
Then depending what you want to do, you may need additional tweaks, but this should work out of the box.
Don't create a canvas
Most objects in fabric (from my limited experience) are at some point converted to a canvas. Creating an additional fabric canvas to manage a group of objects is kind of pointless as you are just adding overhead and mimicking fabrics built in groups object.
Fabric objects are basically DOM canvases wrappers.
The following example shows how fabric uses a canvas to store the content of a group. The demo creates a group and adds it to the fabric canvas, then gets the groups canvas and adds it to the DOM. Clicking on the group's canvas will add a circle. Note how it grows to accommodate the new circles.
const radius = 50;
const fill = "#0F0";
var pos = 60;
const canvas = new fabric.Canvas('fCanvas');
// create a fabric group and add two circles
const group = new fabric.Group([
new fabric.Circle({radius, top : 5, fill, left : 20 }),
new fabric.Circle({radius, top : 5, fill, left : 120 })
], { left: 0, top: 0 });
// add group to the fabric canvas;
canvas.add(group);
// get the groups canvas and add it to the DOM
document.body.appendChild(group._cacheContext.canvas);
// add event listener to add circles to the group
group._cacheContext.canvas.addEventListener("click",(e)=>{
group.addWithUpdate(
new fabric.Circle({radius, top : pos, fill : "blue", left : 60 })
);
canvas.renderAll();
pos += 60;
});
canvas {
border : 2px solid black;
}
div {
margin : 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<div>Fabric's canvas "canvas = new fabric.Canvas('fCanvas');"</div>
<canvas id="fCanvas" width="256" height="140"></canvas>
<div>Fabric group canvas below. Click on it to add a circle.</div>
Use a group rather than a new instance of a fabric canvas.
As you can see a canvas is generated for you. Adding another fabric canvas (Note that a fabric canvas is not the same as a DOM canvas) will only add more work for fabric to do, which already has a lot of work to do.
You are best of to use a group and have that hold the content of the other fabric object you wish to shadow. That would also contain its content in a group.
Just an image
And just a side not, a DOM canvas is an image and can be used by fabric just as any other image. It is sometimes better to do the rendering directly to the canvas rather than via fabric so you can avoid rendering overheads that fabric needs to operate.
To add a DOM canvas to fabric just add it as an image. The border and text are not fabric object, and apart from the code to render them take up no memory, and no additional CPU overhead that would be incurred if you used a fabric canvas and objects.
const canvas = new fabric.Canvas('fCanvas');
// create a standard DOM canvas
const myImage = document.createElement("canvas");
// size it add borders and text
myImage.width = myImage.height = 256;
const ctx = myImage.getContext("2d");
ctx.fillRect(0,0,256,256);
ctx.fillStyle = "white";
ctx.fillRect(4,4,248,248);
ctx.fillStyle = "black";
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("The DOM canvas!",128,128);
// use the canvas to create a fabric image and add it to fabrics canvas.
canvas.add( new fabric.Image(myImage, {
left: (400 - 256) / 2,
top: (400 - 256) / 2,
}));
canvas {
border : 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<canvas id="fCanvas" width="400" height="400"></canvas>
innerCanvas.setCoords(); ìs a function, but you need it only after you set the coordinates. Or more precise, set these four elements:
innerCanvas.scaleX = 1;
innerCanvas.scaleY = 1;
innerCanvas.left = 150;
innerCanvas.top = 150;
innerCanvas.setCoords();
canvas.renderAll();
I'm struggling to get an image of a deck of small cards into spritesheet form using javascript. I need to get them into an array so as to shuffle them. Here's what I've got so far. Only the canvas shows up light blue against a dark blue background.
<html>
<head>
<style>
canvas#game-canvas {
position: absolute;
top: 5%;
left: 5%;
background: lightblue;
height: 281px;
width: 500px;
}
body {
background: darkblue;
}
</style>
<script type="text/javascript" src="jquery1_10_2_min.js"></script>
<script>
var canvas = document.getElementById("game-canvas");
var ctx = canvas.getContext("2d");
function SpriteSheet(path, frameWidth, frameHeight) {
this.image = new Image();
this.frameHeight = frameHeight;
this.frameWidth = frameWidth;
// calculate the number of frames in a row after the image loads
var self = this;
this.image.onload = function() {
self.framesPerRow = Math.floor(this.image.width / frameWidth);
};
this.image.src = path;
}
var spritesheet = new SpriteSheet('small_playing_cards.png', 54, 65);
</script>
</head>
<body>
<canvas id="game-canvas"></canvas>
</body>
</html>
That's an duplicated answer, anyway you can do it by creating one new canvas and drawing it on the main.
PS: that's not my costume code, so I'll do somethings I'd not do, as create variables, etc.
1. Create canvas element asigned as value from one variable;
var Area=document.createElement("canvas")
2. Set the frame size to this canvas;
Area.width=FrameWidth,
Area.height=FrameHeight
3. Draw the spritesheet in this canvas in the x/y (negative) where the frame you want is located;
var AContext=Area.getContext("2d");
AContext.drawImage(SpriteSheet,0-FrameX,0-FrameY);
4. Draw this canvas as image in the main canvas, but with x/y preference;
MainCanvas.drawImage(Area,PreferenceX,PreferenceY)
It's like these steps you can draw one frame from an spritesheet image. I'm not sure if it's that you want, I can't understand your question as well.
i want to know that, how to put the div tag generated with ajax dynamically onto the canvas.
I just create the div tage with ajax and append the generated divs to the another div tag on the jsp page. and i want to add main div on the canvas.
Thanks in advance.
You can't just render HTML into a canvas. Instead, one approach would be to use an SVG image containing the content you want to render.
To draw HTML content, you'd use a <foreignObject> element containing the HTML, then draw that SVG image into your canvas.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
'<foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
'<em>I</em> like ' +
'<span style="color:white; text-shadow:0 0 2px blue;">' +
'cheese</span>' +
'</div>' +
'</foreignObject>' +
'</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {
type: 'image/svg+xml;charset=utf-8'
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;
You can find more information here.
Limitations
This approach has a lot of limitations :
IE below Edge just doesn't support <foreignObject>, so this won't work at all + it will taint the canvas, even if the result is a blank rect, since these browser do so as soon as any svg images as been drawn on it, for security reasons.
Some browsers (at least Safari 9) do taint the canvas when a <foreignObject> is drawn on it, for security reasons.
Some elements do render weirdly (<button> doesn't have any style in FF, <video> is just a weird thing on some UA ...).
No external resource will be loaded into the <img> element, images will have to be converted to dataURL first, styles will have to be appended directly into the svg, or inline in the HTML tags, fonts will have to be dataURL encoded.
So the best approach, if you don't mind using a library, is still to use html2canvas.
If you do mind using a library, then you can try to do what it does :
use the native canvas drawing methods to draw each of the HTML elements and its styles.
canvas is an HTML element which can be used to draw graphics using scripting (usually JavaScript), so It's impossible to add an element inside canvas element, but you can play with css position to move the div inside canvas
<div class="container">
<canvas id="myCanvas" width="200" height="100"></canvas>
<div id="myDiv"></div>
</div>
css:
.container{postion: relative;}
#myCanvas{background: red;}
#myDiv{height: 50px;width: 50px;background: blue;
position: absolute; top:10px;
}
see JSFIDLE
A canvas can't actually contain a div (or any other HTML tag), it's a drawing surface you can draw on.
But you can position the div on top of the canvas in the z-order, e.g.:
// Lower
var ctx = document.getElementById("lower-canvas").getContext('2d');
var path = new Path2D();
path.arc(100, 100, 100, 0, Math.PI * 2, true);
ctx.fillStyle = ctx.strokeStyle = "blue";
ctx.fill(path);
// Upper
ctx = document.getElementById("upper-canvas").getContext('2d');
path = new Path2D();
path.arc(75, 75, 50, 0, Math.PI * 2, true);
ctx.fillStyle = ctx.strokeStyle = "green";
ctx.fill(path);
#lower-canvas, #upper-canvas {
width: 300px;
height: 300px;
position: absolute;
left: 20px;
top: 20px;
}
#upper-canvas {
z-index: 2;
}
#the-div {
position: absolute;
left: 20px;
top: 50px;
z-index: 1;
background-color: white;
}
<canvas id="lower-canvas" width="300" height="300"></canvas>
<canvas id="upper-canvas" width="300" height="300"></canvas>
<div id="the-div">This is the div</div>
In that example, we don't even need z-index because the div is absolutely positioned but the canvas isn't, and by default positioned elements are on a layer "nearer" the viewer than non-positioned elements. If you are positioning the canvas, you'd add something like z-index: 10 (using whatever value was appropriate) to ensure the div was "nearer" the viewer in the order for the positioned layer.