Canvas: add overlay except button that should be clickable - javascript

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>

Related

Make image horizontally and vertically centered over canvas drawing with HTML, CSS, and JS

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.

How to set an element's background as the same as a specific portion of web page

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>

CSS/JavaScript: issue when styling canvas (for mouse drawing)

I have this simple code for drawing with mouse in canvas. But if I try to style the canvas, like altering the width or centering, the pointer and line drawn get separated. How do I solve this?
JavaScript:
var el = document.getElementById('canvas');
var ctx = el.getContext('2d');
var isDrawing;
el.onmousedown = function(e) {
isDrawing = true;
ctx.moveTo(e.clientX, e.clientY);
};
el.onmousemove = function(e) {
if (isDrawing) {
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
};
el.onmouseup = function() {
isDrawing = false;
};
HTML:
CSS:
canvas {
border: 2px solid #ccc;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 800px;
}
Here's a Fiddle
NEVER set canvas's width / height using css. It's really a bad idea. Always use the native width / height property of the canvas.
Also, you should probably be using e.offsetX and e.offsetY to get the x and y coordinates of the mouse.
Here's a working fiddle

How to layer elements with CSS position property?

I've looked at all the questions on here asking the same thing and tried every suggestion, none of it is working for me.
I want to layer two dynamically created canvas elements on top of each other, inside the div with an id of "plotPlaceholder".
It continues to just show one below the other, like so:
// grab elements from form
var wid = document.getElementById("wid").value;
var hei = document.getElementById("hei").value;
// grab div container and create canvas elements
var canvasPlaceholder = document.getElementById("plotPlaceholder");
var baseCanvas = document.createElement("canvas");
var canvasElement = document.createElement("canvas");
canvasElement.width = wid;
canvasElement.height = hei;
baseCanvas.width = wid;
baseCanvas.height = hei;
baseCanvas.style.zIndex = "1";
canvasElement.style.zIndex = "2";
baseCanvas.id = "canvasToHoldGrid";
canvasElement.id = "plottingCanvas";
canvasPlaceholder.appendChild(baseCanvas);
canvasPlaceholder.appendChild(canvasElement);
canvas {
border: 2px solid #27a3ea;
position: absolute;
top: 0;
left: 0;
}
#plotPlaceholder {
position: relative;
}
<div id="plotPlaceholder"></div>
I found out what was happening - there was an extra div being created (parent of the canvas elements) with a class of 'canvas-container'. The position property was being overwritten.
.canvas-container {
position: absolute !important;
top: 0;
left: 0;
}
That !important fixed it, although I'm sure it's not best practice in most circumstances.

blurred text when a canvas is placed on top of another canvas

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>

Categories

Resources