I have two moving rectangles in my canvas, one is green and one is red. When they are on the same position and you can see only one of them, the red rectangle is always displayed while the green one simply is under the red one.
How can i accomplish that the green one is always displayed on top?
without any code it is hard to tell, but if you have the code for the green rectangle first it will be drawn first and the red will be drawn on top of it.
I think you should paint the green one AFTER the red one. so if your rectangles are in an array, you can iterate on it in reverse order
(function start() {
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth || $(window).width();
canvas.height = window.innerHeight || $(window).height();
const ctx = canvas.getContext('2d');
const draw = function(opts={color: 'yellow', x: 0, y: 0}) {
ctx.beginPath();
ctx.rect(opts.x, opts.y, 150, 100);
ctx.fillStyle = opts.color;
ctx.fill();
}
// COLUMN 1
draw({color: 'red',x: 20,y: 20});
draw({color: 'green',x: 40,y: 40});
// COLUMN 2
draw({color: 'green', x: 200, y: 20});
draw({color: 'red', x: 220, y: 40});
})();
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #000;
}
canvas {
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id='canvas'></canvas>
Related
I'm experiencing an issue when scaling an image inside a group the offset of the image changes. The group consists of a background (Rect) and an image. When scaling the group the offset if the image becomes different. Expected is that the offset remains the same as the background.
I'm using Fabric 4.0.0-beta.6
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
https://jsbin.com/migogekoxu/1/edit?html,css,js,output
Setting the strokeWidth property of the background element to 0 fixed the offset for me.
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
strokeWidth: 0, //<---
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
Obviously that's a bug with fabricJS. If you don't want to fix it on your own in it's source code you can workaround it however.
You can manually correct the position by adding an offset of 0.5 to the image's top and left properties.
For example:
const canvas = new fabric.Canvas('canvas');
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red'
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width);
img.left += 0.5;
img.top += 0.5;
const post = new fabric.Group([bg, img], {
left: 100,
top: 100
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
<canvas id="canvas" width="600" height="400"></canvas>
When a sphere falls I would like to put images inside but it doesn't work with the fillStyle render I can only color, now with sprite I can but they don't fit the circle unless the image is rounded, how could I round the image with javascript and matter.js
the code:
return Matter.Bodies.circle(280, 40, 11, {
restitution: 0.5,
render: {
// fillStyle: '#F00',
// strokeStyle: 'black',
// lineWidth: 3,
sprite: {
texture: img,
xScale: 0.3,
yScale: 0.3,
}
}
});
img get square images from ticktok, which I don't know how to make the round
I'm not 100% sure, if this is the best way to do it (in you specific usecase), but you could just :
create a helper canvas Element:
<canvas id='helper-canvas'></canvas>
or let canvas = document.createElement('canvas');
set the width/height of the canvas (width = desired texture width)
...
canvas.width='100';
canvas.height='100';
...
draw the image onto a canvas element(using the context).
...
ctx.drawImage(img, 0, 0); // "img" is a HtmlImageElement
...
set composite-mode and draw a circle that should have the desired size
...
ctx.globalCompositeOperation='destination-in';
ctx.beginPath();
ctx.arc(img.width/2,img.width/2,img.width/2,0,Math.PI*2);
ctx.closePath();
ctx.fill();
...
generate the url of the new "create image"
...
let imgUrl = img.toDataURL('image/png');
...
And than simply create the matter-body, with that image:
Matter.Bodies.circle(280, 40, 11, {
restitution: 0.5,
render: {
sprite: {
texture: imgUrl,
xScale: 0.3,
yScale: 0.3,
}
}
});
You might be pushing the built-in renderer beyond its intent for simple use cases (debugging, prototyping). Consider using MJS headlessly along with a custom renderer that is better suited to a typical game or animation's rendering complexity.
You can use a similar technique as described in Matter.js Text inside a rectangle and make circles with, for example, HTML and CSS:
document.querySelector("img")
.addEventListener("load", () => {
const engine = Matter.Engine.create();
const circle = {
body: Matter.Bodies.circle(150, 0, 50),
elem: document.querySelector("#circle"),
render() {
const {x, y} = this.body.position;
this.elem.style.top = `${y - 50}px`;
this.elem.style.left = `${x - 50}px`;
this.elem.style.transform = `rotate(${this.body.angle}rad)`;
},
};
const ground = Matter.Bodies.rectangle(
200, 200, 400, 120, {isStatic: true}
);
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.body}
);
Matter.Composite.add(
engine.world, [circle.body, ground, mouseConstraint]
);
(function rerender() {
circle.render();
Matter.Engine.update(engine);
requestAnimationFrame(rerender);
})();
});
#circle {
position: absolute;
background: #111;
height: 100px;
width: 100px;
top: -100px;
left: -100px;
cursor: move;
border-radius: 50%;
}
#circle img {
border-radius: 50%;
}
#ground {
position: absolute;
background: #666;
top: 140px;
height: 120px;
width: 400px;
}
html, body {
position: relative;
height: 100%;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div id="circle">
<img
draggable="false"
src="https://4.bp.blogspot.com/-DmPeZ5KQhnM/TvDXvxxb_WI/AAAAAAAAAKI/aRDOjVpBtfM/s1600/poptarticon.gif"
>
</div>
<div id="ground"></div>
You can use the CSS background property instead of the <img> here.
Sorry if the title is vague, it was hard to figure out what to call this problem. I have a canvas and an image overlaying each other like this:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function newLine(){
let value = (Math.floor(Math.random() * 100) + 1) * 0.06283185307179587;
ctx.clearRect(0, 0, c.width, c.height);
ctx.lineWidth = 10;
ctx.strokeStyle = '#00FF00';
ctx.beginPath();
ctx.arc(100, 75, 55, 0, value);
ctx.stroke();
}
setInterval(()=>{
newLine()
}, 100)
img{
width: 100px;
border-radius: 50px;
position: absolute;
left: 58px;
top: 33px;
}
<img src="https://media-exp1.licdn.com/dms/image/C4E0BAQHikN6EXPd23Q/company-logo_200_200/0/1595359131127?e=2159024400&v=beta&t=S5MNjBDjiH433VCWzjPeiopNDhxGwmfcMk4Zf1P_m_s"></img>
<canvas id="myCanvas"></canvas>
This works, but when I go to add multiple, or add a div above them, because of the image's absolute position, the image ends up over the div, not the canvas. Here's an example illustrating my issues:
var can = document.getElementsByClassName("canvas");
for (var i = 0; i < can.length; i++) {
let c = can.item(i)
var ctx = c.getContext("2d");
function newLine(){
let value = (Math.floor(Math.random() * 100) + 1) * 0.06283185307179587;
ctx.clearRect(0, 0, c.width, c.height);
ctx.lineWidth = 10;
ctx.strokeStyle = '#00FF00';
ctx.beginPath();
ctx.arc(100, 75, 55, 0, value);
ctx.stroke();
requestAnimationFrame(newLine)
}
newLine()
}
img{
width: 100px;
border-radius: 50px;
position: absolute;
left: 58px;
top: 33px;
}
div{
background-color: red;
}
canvas{
display: inline-block;
}
<div class="top">
<h1>Some text</h1>
</div>
<img src="https://media-exp1.licdn.com/dms/image/C4E0BAQHikN6EXPd23Q/company-logo_200_200/0/1595359131127?e=2159024400&v=beta&t=S5MNjBDjiH433VCWzjPeiopNDhxGwmfcMk4Zf1P_m_s"></img>
<canvas class="canvas"></canvas>
<img src="https://media-exp1.licdn.com/dms/image/C4E0BAQHikN6EXPd23Q/company-logo_200_200/0/1595359131127?e=2159024400&v=beta&t=S5MNjBDjiH433VCWzjPeiopNDhxGwmfcMk4Zf1P_m_s"></img>
<canvas class="canvas"></canvas>
(If you could figure out why the first one doesn't play, that would be amazing also)
The issues are:
The first canvas doesn't play
The images (because it's position is absolute) are stacked up and not over the second canvas
Is it possible to do this but with relative positioning? Please let me know if this was a confusing question. Thanks
Avoid mixing images <img> and canvas, instead just draw the image inside the canvas with drawImage nothing special just a few more lines on JavaScript
Here is an example:
(And I also fixed your issue where the first one does not play)
class Shape {
constructor(ctx, width, height, image) {
this.ctx = ctx;
this.width = width;
this.height = height;
this.image = image;
this.value = Math.random() * 5
this.inc = 0.03
}
draw() {
this.value += this.inc;
if (this.value > 2 * Math.PI || this.value < 0.05) this.inc *= -1;
this.ctx.clearRect(0, 0, this.width, this.height);
// Draw centered round image
this.ctx.beginPath();
this.ctx.drawImage(this.image , 50, 24, 100, 100);
this.ctx.lineWidth = 50;
this.ctx.strokeStyle = 'white';
this.ctx.arc(100, 75, 75, 0, 2 * Math.PI);
this.ctx.stroke();
// Draw green progress bar
this.ctx.beginPath();
this.ctx.lineWidth = 12;
this.ctx.strokeStyle = '#00FF00';
this.ctx.arc(100, 75, 55, 0, this.value);
this.ctx.stroke();
}
}
var can = document.getElementsByClassName("canvas");
var animations = []
function loop() {
animations.forEach(anim => anim.draw());
requestAnimationFrame(loop)
}
var image = new Image();
image.src = "https://media-exp1.licdn.com/dms/image/C4E0BAQHikN6EXPd23Q/company-logo_200_200/0/1595359131127?e=2159024400&v=beta&t=S5MNjBDjiH433VCWzjPeiopNDhxGwmfcMk4Zf1P_m_s";
image.onload = imageLoaded;
function imageLoaded() {
for (var i = 0; i < can.length; i++) {
let c = can.item(i)
animations.push(new Shape(c.getContext("2d"), c.width, c.height, image));
}
loop()
}
div {
background-color: red;
}
canvas {
display: inline-block;
border: 1px solid gray;
}
<div class="top">
<h2>Some text</h2>
</div>
<canvas class="canvas" width=200></canvas>
<canvas class="canvas" width=200></canvas>
There are two problems: positioning of the images above the canvases and only one canvas playing.
First positioning. The img is given position absolute. To find out where to position it the system goes back up the tree to find the first element it comes across that has a position set. It then positions the img relative to that.
The canvas is at the same level as the img so that doesn't influence the img's position, going back up the first thing it hits with a position is the body element (which is positioned by default) and so it puts the img slightly set off from the top of the page.
To remedy this we can put each pair of img/canvas into their own div and position that inline-block. The img will then be positioned relative to that div and so will be placed over the canvas as required.
Second only one canvas playing. The code within the newline function uses a c and ctx. These are not set within the function so it takes the last one that was set. In addition, requestAnimationFrame with its callback is called for each can.length times each time so you end up with more than one of these. They stack up. The browser gets 'confused' and (at least on my laptop) it ended up not responding.
To cure this, call requestAnimationFrame just once per 'cycle' and repaint all the canvases at the same call to the callback - so put the definition of c and ctx inside the for loop so every canvas is set up on each frame.
var can = document.getElementsByClassName("canvas");
function newLine(){
for (var i = 0; i < can.length; i++) {
let c = can.item(i)
var ctx = c.getContext("2d");
let value = (Math.floor(Math.random() * 100) + 1) * 0.06283185307179587;
ctx.clearRect(0, 0, c.width, c.height);
ctx.lineWidth = 10;
ctx.strokeStyle = '#00FF00';
ctx.beginPath();
ctx.arc(100, 75, 55, 0, value);
ctx.stroke();
}
requestAnimationFrame(newLine)
}
newLine()
img{
width: 100px;
border-radius: 50px;
position: absolute;
left: 58px;
top: 33px;
}
div{
background-color: red;
}
canvas{
display: inline-block;
}
.wrapper {
display: inline-block;
position: relative;
}
<div class="top">
<h1>Some text</h1>
</div>
<div class="wrapper">
<!-- <img src="https://media-exp1.licdn.com/dms/image/C4E0BAQHikN6EXPd23Q/company-logo_200_200/0/1595359131127?e=2159024400&v=beta&t=S5MNjBDjiH433VCWzjPeiopNDhxGwmfcMk4Zf1P_m_s"></img>-->
<img src="https://i.stack.imgur.com/ZBclx.jpg" />
<canvas class="canvas"></canvas>
</div>
<div class="wrapper">
<!-- <img src="https://media-exp1.licdn.com/dms/image/C4E0BAQHikN6EXPd23Q/company-logo_200_200/0/1595359131127?e=2159024400&v=beta&t=S5MNjBDjiH433VCWzjPeiopNDhxGwmfcMk4Zf1P_m_s"></img>-->
<img src="https://i.stack.imgur.com/ZBclx.jpg" />
<canvas class="canvas"></canvas>
</div>
Note, the actual logo image didn't show on your snippet - I guess it cannot be downloaded by just anybody - so I put it on imgur. The positioning is slightly off because the top and left positions had been previously set to compensate for its being positioned relative to the body. These need a bit of adjustment.
There is quite a lot of GPU being used (around 20% on my laptop, obviously that will differ depending on the user's equipment). It might be worth investigating whether a CSS animation could be used instead of having to redraw several canvases on each frame.
The animated canvases are very flickery. I don't know whether this is intended or whether it was just a test, but they may be upsetting for those with conditions such as epilepsy.
Intro:
I have a sticky header and a body which has linear gradient.
Goal:
I'd like to set the header's background as the same of a specific area, that is to say where it initially sits on the top.
Solution tried:
Using the dev tool color picker to find the first value on the top and the second value where the header ends.
Result:
this works.
In this way the header looks like is integrated and part of the body's linear gradient.
It maintains its background as I scroll down the page.
Issue:
If the page's height changes the body's linear gradient will be recalculated indeed.
So now the header's background need a recalculation as well.
As I am new to programming I would appreciate any suggestion that can help me to solve this dynamically.
Guess it's gonna be Javascript helping out.
Here I found a user with the same issue.
Thanks a lot for your time guys!
Instead of using a CSS background gradient, you can create a canvas with z-index -1 and the same size of your page. In the canvas you can render your gradient. This has the advantage, that you can query the canvas for the color at a specific position, which is not possible with the CSS background gradient. By this you can update the background color of your header, whenever a resize or scroll event occurs.
var canvas = document.getElementById ('background');
var ctx = canvas.getContext ('2d');
var header = document.getElementById ('header');
function scroll()
{
var y = window.scrollY + header.getClientRects()[0].height;
var rgba = ctx.getImageData (0, y, 1, 1).data;
header.style.backgroundColor = 'rgba(' + rgba.join(',') + ')';
}
function draw()
{
var colors = ['red', 'orange', 'yellow', 'green',
'blue', 'indigo', 'violet'];
var gradient = ctx.createLinearGradient (0, 0, 0, canvas.height);
for (var i=0; i < colors.length; i++) {
gradient.addColorStop (i/(colors.length-1), colors[i]);
}
ctx.fillStyle = gradient;
ctx.fillRect (0, 0, canvas.width, canvas.height);
scroll();
}
function resize()
{
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
draw();
}
window.addEventListener('resize', resize, false);
window.addEventListener('scroll', scroll, false);
resize();
body {
height: 100rem;
overflow: scroll;
margin: 0;
}
canvas {
display: block;
height: 100%;
width: 100%;
z-index: -1;
margin: 0;
}
#header {
position: fixed;
top: 0;
left: 50%;
right: 0;
height: 50%;
border-bottom: 1pt solid white;
}
<body>
<canvas id="background"></canvas>
<div id="header">
Header
</div>
<script src="gradient.js"></script>
</body>
I have a rather trivial example where I'm attempting to insert an image on a Fabric.js canvas with the image centered at the mouse coordinates. The center of the cursor should be the exact center of the image.
I calculate the center of the image by halving its width and height and apply them as offsets to the left and top coordinates of the canvas.Image I'm inserting. Simple enough:
// Coordinates from the mouse click event.
// `x` and `y` are aliases for `clientX`, `clientY`, respectively
const x = event.e.x,
y = event.e.y
image.set({
left: x - (image.width / 2),
top: y - (image.height / 2)
})
canvas.add(image);
When the image is added, it's not quite center. In fact, there's a difference of 8 pixels on both x and y axes that I cannot account for.
This is the result:
The center of the image should be under the cursor.
If I manually set the offset to 28, the image is properly centered. But since I cannot account for the 8 extra pixels, this hack is unacceptable.
Working sample:
const canvas = new fabric.Canvas('c', { width: 400, height: 150 });
// Add an image centered x & y at the exact point the user clicks.
canvas.on('mouse:up', (opt) => {
canvas.clear();
const x = opt.e.x,
y = opt.e.y;
fabric.Image.fromURL('https://i.imgur.com/htyNxF6.png', (image) => {
/* Calculate the offset based on the image dimensions:
This does not work as expected. A 40x40 image has a x/y offsets of 20.
If we set the offset to 28, the image is centered at the cursor. Why 28?
Check the "Apply ..." checkbox to see this in action.
*/
const offsetX = chkOffset.checked ? 28 : (image.width / 2),
offsetY = chkOffset.checked ? 28 : (image.height / 2);
const left = x - offsetX,
top = y - offsetY;
image.set({
left: left,
top: top,
stroke: 0,
padding: 0,
centeredScaling: true,
hasControls: false,
strokeWidth: 0,
hasBorders: 0
});
canvas.add(image);
writeDebug(`Mouse at: x=${x}, y=${y};
Image placed at: x=${left}, y=${top}
Difference of ${Math.abs(left-x)}, ${Math.abs(top-y)}`);
});
// Show coordinates on mouse move
canvas.on('mouse:move', (opt) => {
const x = opt.e.x,
y = opt.e.y;
writeDebug(`Mouse coordinates: x=${x}, y=${y}`);
});
});
function writeDebug(message) {
document.getElementById('debug').innerText = message;
}
body {
font-family: Consolas;
}
#c {
border: 1px solid #ececec;
box-shadow: 2px 2px 5px #c0c0c0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.1.0/fabric.min.js"></script>
<canvas id="c"></canvas>
<label for="chkOffset">
<input type="checkbox" id="chkOffset" />
Apply 28px offset
</label>
<div id="debug">Click in the canvas to add the image</div>
When I was adding sample code to my question, I stumbled upon the answer. I set the padding of every element to 10 using CSS (* { padding: 10px }) to make everything look better and upon doing so discovered that the offset gap had increased.
Getting mouse coordinates using MouseEvent.e.x and MouseEvent.x.y are the culprit. I compared their values with MouseEvent.e.offsetX and MouseEvent.e.offsetY and found that the x and y values did not account for padding and margins.
Using offsetX and offsetY seemed to do the trick.
It seems if you want to get relevant mouse coordinates in Fabric.js, using offsetX and offsetY is the best bet as it accounts for offsets caused by CSS.
const canvas = new fabric.Canvas('c', { width: 400, height: 150 });
// Add an image centered x & y at the exact point the user clicks.
canvas.on('mouse:up', (opt) => {
canvas.clear();
// Use .offset instead:
const x = opt.e.offsetX,
y = opt.e.offsetY;
fabric.Image.fromURL('https://i.imgur.com/htyNxF6.png', (image) => {
const offsetX = (image.width / 2),
offsetY = (image.height / 2);
const left = x - offsetX,
top = y - offsetY;
image.set({
left: left,
top: top,
stroke: 0,
padding: 0,
centeredScaling: true,
hasControls: false,
strokeWidth: 0,
hasBorders: 0
});
canvas.add(image);
writeDebug(`Mouse at: x=${x}, y=${y};
Image placed at: x=${left}, y=${top}
Difference of ${Math.abs(left-x)}, ${Math.abs(top-y)}`);
});
// Show coordinates on mouse move
canvas.on('mouse:move', (opt) => {
const x = opt.e.offsetX,
y = opt.e.offsetY;
writeDebug(`Mouse coordinates: x=${x}, y=${y}`);
});
});
function writeDebug(message) {
document.getElementById('debug').innerText = message;
}
body {
font-family: Consolas;
}
#c {
border: 1px solid #ececec;
box-shadow: 2px 2px 5px #c0c0c0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.1.0/fabric.min.js"></script>
<canvas id="c"></canvas>
<div id="debug">Click in the canvas to add the image</div>