I'm trying to crop an image using a canvas, and position that crop over the original image in a seamless way, so that the separation wouldn't be visible.
Here's what I would have expected to work:
const image = document.querySelector("img")
const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")
canvasCoords = canvas.getBoundingClientRect()
ctx.drawImage(
image,
canvasCoords.left, canvasCoords.top,
canvasCoords.width, canvas.height,
0, 0,
canvasCoords.width, canvasCoords.height
)
I want the crop to fill my canvas, hence the last 4 properties. canvasCoords. Also I am aware that a real-life solution would involve getting the canvas offset relatively to the image, but this is just a simplified example.
Here is a JS Fiddle of that example.
Calculate the width and height scales using image.naturalWidth / image.width and image.naturalHeight / image.height like below:
const image = document.querySelector("img")
const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")
canvasCoords = canvas.getBoundingClientRect()
const widthScale = image.naturalWidth / image.width
const heightScale = image.naturalHeight / image.height
ctx.drawImage(
image,
canvasCoords.left * widthScale, canvasCoords.top * heightScale,
canvasCoords.width * widthScale, canvas.height * heightScale,
0, 0,
canvasCoords.width, canvasCoords.height
)
body {
margin: 0;
}
div {
position: relative;
}
img {
position: absolute;
top: 0;
left: 0;
width: 500px;
height: 300;
}
canvas {
position: absolute;
top: 140px;
left: 175px;
border: 2px solid red;
}
<div>
<img src="http://www.savoie-mont-blanc.com/var/smb/storage/images/media/images/visites-et-decouvertes/nature/lac-des-vaches-a-pralognan-la-vanoise-parc-national-de-la-vanoise/359903-1-fre-FR/Lac-des-Vaches-a-Pralognan-la-Vanoise-Parc-national-de-la-Vanoise_default_format.jpg">
<canvas width="150" height="50"></canvas>
</div>
If you can hide the image from the page and do everything in the canvas, you can use the trick of drawing the canvas again:
ctx.drawImage(
canvas,
0, 0,
150,50,
0,0,150,50
)
JS Fiddle
Related
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.
I'm trying to add an overlay using canvas. It needs to disable clicks so all elements above the overlay should be unclickable, except the element that I send to openOverlay function.
In addition, there is a button that I want to make it clickable. This button is sent to openOverlay function.
How can I do it?
This is my code:
https://codepen.io/anon/pen/QQqQae
The button needs to be clickable but not the div
I tried something like: ctx.clearRect in order to cut the piece that is found above the button:
function openOverlay(elem) {
var loc = elem.getBoundingClientRect();
var canvas = document.createElement("canvas");
canvas.className = "highlight";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.clearRect(loc.left - padding, loc.top - padding, loc.width + padding * 2, loc.height + padding * 2);
document.body.appendChild(canvas);
window.overlayCanvas = canvas;
}
Set z-index for "canvas" to -1 :
canvas{
z-index: -1;
}
But, the above method will make all the elements in the canvas clickable.
So, you can set the following style on the button to make it clickable :
button{
position: absolute; //or, relative
left: 0px;
top: 0px;
z-index: 2;
}
All the other elements in the canvas won't be clickable unless they have position absolute (or, relative) and higher z-index.
First, ctx.clearRect doesn't really have any effect on the mouse click event.
In the future, you may be able to use canvas hit regions, but they have limited support for now. See MDN AddHitRegion
But for now, you can put any button that is supposed to be clickable at a higher z-index than the overlay and give it either relative or absolute positioning.
function openOverlay(elem) {
var canvas = document.createElement("canvas");
canvas.className = "highlight";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.zIndex = 100;
var ctx = canvas.getContext("2d");
ctx.fillRect(4, 4, 300, 150);
elem.classList.add("clickable");
document.body.appendChild(canvas);
window.overlayCanvas = canvas;
}
var element= document.getElementById("button1")
openOverlay(element)
body{
background-color: grey;
}
.clickable{
position: relative;
z-index:200;
}
canvas{
z-index: 100;
position: absolute;
display: block;
top: 0;
left: 0;
background-color:blue;
opacity: 0.6;
}
<button id="button1" onclick="window.alert('Clicked button1')">Click Me</button>
<button id="button2" onclick="window.alert('Clicked button 2')">Don't Click Me</button>
I want to display the mouse's x position on canvas as mouse moves. Can someone please tell why the text is so big and blurry and also is there any way to achieve the transparency between these two canvases
<body style="margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden;">
<canvas id="myCanvas" style="display: block; height: 100%; width: 100%;">
Your browser does not support the HTML5 canvas tag.</canvas> <canvas id="temp">
Your browser does not support the HTML5 canvas tag.</canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var hgap = 0;
var vgap = 0;
var rows, cols;
var annotation_x = 1;
var row = 0; var col = 0;
//ctx.font = "14px Arial";
var t = document.getElementById("temp");
tctx = t.getContext("2d");
// tctx.lineWidth = 10;
tctx.lineJoin = 'round';
tctx.lineCap = 'round';
tctx.strokStyle = 'red';
var mouse = { x: 0, y: 0 };
c.addEventListener('mousemove', function (evt) {
// tctx.clearRect(0, 0, c.width, c.height);
ctx.clearRect(0, 0, c.width, c.height);
mouse.x = evt.pageX;
mouse.y = evt.pageY;
ctx.fillText(mouse.x, 10, 10);
}, false);
</script>
</body>
The problem isn't due to having two canvas.
The problem is that you scale your canvas using CSS, but you don't modify its number of pixels (height and width attributes). Then, the result is blurry.
If you don't scale it with CSS, the result is fine: Demo
Alternatively, you can try using image-rendering CSS property (e.g. crisp-edges, demo)
You can also modify height and width attributes when the browser is resized (resize event), and redraw the canvas.
<canvas id="myCanvas" style="display: block; height: 100%; width: 100%;">
^ You shouldn't scale a canvas element with CSS unless you want it's contents to be interpolated. Consider a 300x125px image, which you've scaled to the width and height of the browser window - the image will become blurry.
If you wish to have a full-screen canvas you need to scale it with JS:
var c = document.getElementById("myCanvas");
c.width = window.innerWidth;
c.height = window.innerHeight;
..or with HTML:
<canvas width="1920" height="1080"></canvas>
I have an image background in my HTML5 canvas.
var canvas = new fabric.Canvas('#canvas1');
canvas.setBackgroundImage(
'http://fabricjs.com/assets/jail_cell_bars.png',
canvas.renderAll.bind(canvas)
);
How to set dimensions of my canvas (width, height) to the dimensions of the background image?
Thanks.
I think the above answer does not use fabric.js, it uses normal canvas.
When you use fabric.js and its Fabric.Canvas class, the code should be:
image.onLoad = function() {
var fabric_canvas = new fabric.Canvas('canvas1');
fabric_canvas.setDimensions({width:image.width, height:image.height});
};
In my case,
var canvas = new fabric.Canvas("test_fabric");
canvas.setDimensions({width:800, height:200});
The result is:
<canvas id="test_fabric" width="800" height="200" class="lower-canvas" style="position: absolute; width: 800px; height: 200px; left: 0px; top: 0px; -webkit-user-select: none;"></canvas>
Both canvas.width and canvas.style.width are set.
These work as well!
canvas.setWidth(500);
canvas.setHeight(400);
You can use CSS or dom properties to set the dimensions of your canvas. If you know the dimensions of your image, you can do it in a stylesheet:
#canvas1 { width: XXXpx; height: YYYpx; }
Or in the dom:
<canvas id="canvas1" width="XXX" height="YYY"></canvas>
If the image is dynamic and want to set the dimensions in JS, you do something like this:
var image = new Image()
image.src = 'http://fabricjs.com/assets/jail_cell_bars.png';
image.onLoad = function () {
var canvas = document.getElementById('canvas1');
canvas.width = image.width;
canvas.height = image.height;
};
I have done a very tiny example with canvas, it's available on JsFiddle:
http://jsfiddle.net/yPtr5/
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
html, body {
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
}
#myCanvas {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<canvas id="myCanvas">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
var canvas = document.getElementById( "myCanvas" );
var context = canvas.getContext( "2d" );
context.id = "myContext";
context.beginPath();
context.arc( 95, 50, 40, 0, 2 * Math.PI );
context.stroke();
setTimeout( function() {
var rectWidth = 150;
var rectHeight = 75;
context.fillStyle = "blue";
context.fillRect( rectWidth / -2, rectHeight / -2, rectWidth, rectHeight );
}, 2000 );
</script>
</body>
</html>
As you are able to see, the rendering result has a very low quality:
So, I'm wondering, how can I draw various figures using Canvas in a good quality, I don't want to draw in small size, I want to draw in 100% size of page.
So, maybe I didn't define some anti aliasing filter or something else?
Thanks!
Problem
In most general cases we should avoid using CSS to set the canvas size.
The default size of canvas is 300 x 150 pixels (bitmap). If you set the size using CSS we'll just end up scaling those 300 x 150 pixels meaning the browser will start interpolating and smoothing the image, which is why you end up with a blurry result.
Solution
Remove these from the CSS-rule:
#myCanvas {
/*width: 100%;
height: 100%;*/
display: block;
}
and set the size in JavaScript like this:
var canvas = document.getElementById( "myCanvas" );
canvas.width = window.innerWidth; // equals window dimension
canvas.height = window.innerHeight;
You can of course set any other size you need (in pixels). You probably want to define position (i.e. fixed or absolute) for the canvas' CSS as well if your goal is full window size.
Hope this helps.
The height and width need to be set on the height and width attributes of the canvas tag and not in CSS. Any CSS sizing of the canvas element merely stretches the canvas and does not size it properly.
<canvas id="canvas" width="500px" height="500px">
Have a look at this project that I posted on my site: Creating an HTML5 Paint App
It includes a functionto resize the canvas when the browser window size changes (which you would have to modify):
this.onScreenSizeChanged = function (forceResize) {
if (forceResize || (this.canvas.width != window.innerWidth /*||
this.canvas.height != window.innerHeight*/)) {
var image = this.context.getImageData(0, 0,
this.canvas.width, this.canvas.height);
this.canvas.width = (window.innerWidth);
this.canvas.height = (window.innerHeight);
this.context.putImageData(image, 0, 0);
}
}
this.onScreenSizeChanged(true);
In my case, it was a problem with the screen pixel ratio.
To solve it, I created a canvas with a higher pixel ratio as follows:
function createHiPPICanvas(w, h) {
let ratio = window.devicePixelRatio;
let cv = document.createElement("canvas");
cv.width = w * ratio;
cv.height = h * ratio;
cv.style.width = w + "px";
cv.style.height = h + "px";
cv.getContext("2d").scale(ratio, ratio);
return cv;
}
Then I also increased accordingly the pixel ratio of the images used inside of the canvas.