html5/css/javascript : How to superpose two canvas in a div - javascript

function drawAll() {
// Upper zone, 8 grey transparent buttons
let canvas0 = document.getElementById("layer0");
canvas0.width = 1000;
canvas0.height = 100;
let bandeau = canvas0.getContext("2d");
bandeau.fillStyle = "rgba(128,128,80,0.3)";
for (var i = 0; i < 8; i++) {
bandeau.beginPath;
bandeau.arc(50 + 110 * i, 50, 45, 0, 2 * Math.PI);
bandeau.fill();
}
// Lower zone, a red rectangle partially under the buttons
let canvas1 = document.getElementById("layer1");
canvas1.width = 1000;
canvas1.height = 1000;
let dessin = canvas1.getContext("2d");
dessin.fillStyle = "red";
dessin.fillRect(30, 50, 800, 200);
canvas0.style.visibility = "visible";
canvas1.style.visibility = "visible";
}
drawAll()
body {
background-color: rgb(249, 249, 250);
}
.container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -10;
}
.scrollable {
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
}
.fixed {
position: absolute;
top: 0px;
left: 0px;
z-index: 2;
}
<div class="container">
<canvas id="layer0" class="scrollable"></canvas>
<canvas id="layer1" class="fixed"></canvas>
</div>
Hello
I'm stuck on a superposition problem of two canvas. Here is a simplified example. Note that in the real application, buttons and drawings are far more complicated and that I want to keep the structure with html5 / css / javascript.
I suppose that I miss something in the css to succeed to have these two canvas superposed, buttons partially covering the red rectangle, but what ?
Thanks if somebody can help.

The problem is that <body> doesn't have any height, which makes the .container height of 100% equally zero.
Absolutely positioned elements do no contribute to their parent's height. As soon as you start giving .container an actual height, you can see its content. In the example below, I went for 100vw and 100vh for width and height, but since your canvases are 1000px wide, you could also use that or any other value.
An absolutely positioned element no longer exists in the normal document layout flow. Instead, it sits on its own layer separate from everything else.
Source: MDN Web Docs
The other option is to remove overflow: hidden; from .container and show everything outside of it.
function drawAll() {
// Upper zone, 8 grey transparent buttons
let canvas0 = document.getElementById("layer0");
canvas0.width = 1000;
canvas0.height = 100;
let bandeau = canvas0.getContext("2d");
bandeau.fillStyle = "rgba(128,128,80,0.3)";
for (var i = 0; i < 8; i++) {
bandeau.beginPath;
bandeau.arc(50 + 110 * i, 50, 45, 0, 2 * Math.PI);
bandeau.fill();
}
// Lower zone, a red rectangle partially under the buttons
let canvas1 = document.getElementById("layer1");
canvas1.width = 1000;
canvas1.height = 1000;
let dessin = canvas1.getContext("2d");
dessin.fillStyle = "red";
dessin.fillRect(30, 50, 800, 200);
canvas0.style.visibility = "visible";
canvas1.style.visibility = "visible";
}
drawAll()
body {
background-color: rgb(249, 249, 250);
}
.container {
position: relative;
overflow: hidden;
z-index: -10;
height: 100vh;
width: 100vw;
}
.scrollable {
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
}
.fixed {
position: absolute;
top: 0px;
left: 0px;
z-index: 2;
}
<div class="container">
<canvas id="layer0" class="scrollable"></canvas>
<canvas id="layer1" class="fixed"></canvas>
</div>

Related

coordinate mismatch with canvas api

I have been trying to make an application like open-board. For that, I am using canvas API. The problem is that the lines are drawn below the clicked point. I have already subtracted offset top and offset left but still facing the issue.
Also, one thing that I have noticed, the distance between the clicked point and the drawn figure increases as the clicked point goes farther away from the origin. Like this: image
here is my code:
canvas.addEventListener("mousedown",(e)=>{
let x = drawArea.offsetTop
let y = drawArea.offsetLeft
if(lineToolSelected == false)
{
lineToolSelected = true;
tool.beginPath()
tool.moveTo(e.clientX-y,e.clientY-x)
}else if( lineToolSelected == true){
lineToolSelected = false
tool.lineTo(e.clientX-y,e.clientY-x)
tool.stroke()
}
})
.draw-area{
/* position: fixed; */
position: absolute;
top:0rem;
left:0rem;
height: 175px;
width: 350px;
background:white;
}
.draw-area-cont{
/* position: fixed; */
position: absolute;
top:10rem;
left:2rem;
width:74rem;
background:#eeeded;
height: 37rem;
overflow: hidden;
/* z-index:1; */
}
#canvas{
/* position: fixed; */
height: 100%;
background-color: white;
width: 100%;
color: white;
}
<div class="draw-area-cont">
<div class="draw-area">
<canvas id="canvas"></canvas>
</div>
</div>
If anyone encounters the same problem, I did this:
var scale = 2;
canvas.height = canvas.offsetHeight * scale;
canvas.width = canvas.offsetwidth * scale;
I don't think you even need to multiply with scale, just doing this should work too:
canvas.height = canvas.offsetHeight;
canvas.width = canvas.offsetwidth;
I multiplied with scale to increase the resolution of the image.

Use an image as a mask for another image

I'm making a website that I wanted to be a white page that you could stamp to make another image appear under. So when you click, you make a holepunch.
Like this exemple :
So I managed to have a randomized image in the background as I click which is fine for what I want, and to be able to .append() the holepunches.
But I don't know how to do the mask thing I've been digging online for a few things and help, and managed to make it work in certain cases but not that one...
It should be like that (I guess) :
image in the background
white shape in front
the star shape is making a holepunch in the white shape
For now, the only thing I managed to do is to have the picture besides a bigger holepunch (which is my original img) but when I click it doesn't make any holepunch, it justs add the stamp.
Here is the code :
var images = ["https://icatcare.org/app/uploads/2018/07/Thinking-of-getting-a-cat.png", "https://ichef.bbci.co.uk/news/1024/cpsprodpb/151AB/production/_111434468_gettyimages-1143489763.jpg"];
$(document.body).click(function(c) {
var tw = 100 / 2;
var th = 30 / 2;
var x = Math.floor((Math.random() * images.length));
document.getElementById('random').src = images[x];
$("#random").css({
position: 'absolute',
display: "block",
left: 0,
top: 0
});
var tw = 50 / 2;
var th = tw;
$('#holepunch:last').clone().appendTo(this).css({
position: 'absolute',
display: "block",
left: c.pageX - tw - $(this).position().left,
top: c.pageY - th + $(this).scrollTop()
});
});
body{
background: lightgrey;
width: 100vw;
height: 100vh;
z-index: 1;
opacity: 1;
}
.fauxbody{
z-index: 100;
position: fixed;
width: 100vw;
height: 100vh;
background-color: white;
top: 0;
left: 0;
-webkit-mask:
-moz-element(#holepunch) 1vw 1vh no-repeat,
linear-gradient(#fff 0 0);
mask-composite:exclude;
}
#random{
z-index: -100;
width: 100vw;
height: auto;
}
#holepunch{
width: 50px;
height: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
<img id="random">
<div class ="fauxbody">
<img id="holepunch" src="https://oshi.at/iimtXg/Jqtz.png">
</div>
</body>
Here is an idea using multiple mask and CSS variables. The trick is to add an extra layer on each click. I removed the code related to background generation since it's irrelevant and quite easy to be added
var mask = "";
w = 60;
h = 60;
document.documentElement.addEventListener("click", function (c) {
mask+="url(https://i.ibb.co/FzmCjLL/Jqtz.png)"+(c.pageX-w/2)+"px "+(c.pageY-h/2)+"px/"+w+"px "+h+"px no-repeat,";
document.documentElement.style.setProperty("--mask", mask)
});
html {
background:url(https://picsum.photos/800/800) center/cover;
}
html::before {
content:"";
position: fixed;
background-color: #f3f3f3;
inset: 0;
-webkit-mask:
var(--mask)
linear-gradient(#fff 0 0);
-webkit-mask-reepat: no-repeat;
-webkit-mask-composite: destination-out;
mask-composite: exclude;
}
Also like below without mask-composite:
var mask = "";
w = 60;
h = 60;
document.documentElement.addEventListener("click", function(c) {
if (mask != "")
mask += ",";
mask += "url(https://i.ibb.co/FzmCjLL/Jqtz.png)" + (c.pageX - w / 2) + "px " + (c.pageY - h / 2) + "px/" + w + "px " + h + "px no-repeat";
document.documentElement.style.setProperty("--mask", mask)
});
html::before {
content: "";
position: fixed;
background: url(https://picsum.photos/800/800) center/cover;
inset: 0;
-webkit-mask: var(--mask, linear-gradient(#0000 0 0));
}
I would try to use canvas with white background and add a mouseclick event listener, which cuts out the canvas. I found another question on stack overflow what may can help you:
HTML5 Cut out circle from previous drawn strokes

Centering overflow:auto content inside DIV

I'm trying to create a grid inside of a DIV viewport with overflow set to auto and want to add a zoom feature to it. The user should be able to zoom in/out but I want to keep the content center-aligned throughout the zoom process.
When zooming, the overflowing content on the horizontal axis is centered and, when scrolled left and right, I can see all 8 grid boxes on that axis - this is perfect. However, for some strange reason, the overflowing content on the Y axis is not centering when zoomed and the uppermost part of the grid disappears out of the viewport and annoyingly can't be seen or scrolled to.
When you zoom in, pay attention to how the horizontal scrollbar remains centered and that you can scroll left and right and see all 8 boxes but the vertical axis doesn't work the same way.
I welcome your suggestions and/or a solution. Thanks.
$(function() {
var $container = $('.container');
var current_zoom = 1;
var current_cell_size = 20;
var cells_x = 8;
var cells_y = 8;
var grid_size_x = cells_x * current_cell_size;
var grid_size_y = cells_y * current_cell_size;
var $grid = $('<div />').addClass('grid').css({
'width': grid_size_x,
'height': grid_size_y
});
for (var x = 0; x < cells_y; x++) {
var $row = $('<div />').addClass('row');
for (var y = 0; y < cells_x; y++) {
var $cell = $('<div />').addClass('cell').css({
'width': current_cell_size,
'height': current_cell_size
});
$row.append($cell);
}
$grid.append($row);
}
$container.append($grid);
center_viewport();
// ----- EVENTS
$('.zoom-in-btn').click(zoom_in);
$('.zoom-out-btn').click(zoom_out);
// ----- FUNCTIONS
function center_viewport() {
$container.scrollLeft((grid_size_x - $container.width()) / 2);
$container.scrollTop((grid_size_y - $container.height()) / 2);
}
function zoom_in() {
current_cell_size = current_cell_size + 10;
grid_size_x = cells_x * current_cell_size;
grid_size_y = cells_y * current_cell_size;
$grid.css({
'width': grid_size_x,
'height': grid_size_y
}).find('.cell').css({
'width': current_cell_size,
'height': current_cell_size
});
center_viewport();
}
function zoom_out() {
current_cell_size = current_cell_size - 10;
grid_size_x = cells_x * current_cell_size;
grid_size_y = cells_y * current_cell_size;
$grid.css({
'width': grid_size_x,
'height': grid_size_y
}).find('.cell').css({
'width': current_cell_size,
'height': current_cell_size
});
center_viewport();
}
});
html,
body {
position: relative;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
html .container,
body .container {
position: fixed;
top: 30px;
right: 30px;
bottom: 30px;
left: 30px;
overflow: auto;
border: solid 1px #000;
}
html .container .grid,
body .container .grid {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
html .container .grid .row,
body .container .grid .row {
display: flex;
}
html .container .grid .row:nth-child(odd) .cell:nth-child(odd),
body .container .grid .row:nth-child(odd) .cell:nth-child(odd) {
background: #EEE;
}
html .container .grid .row:nth-child(even) .cell:nth-child(even),
body .container .grid .row:nth-child(even) .cell:nth-child(even) {
background: #EEE;
}
html .container .zoom-btns,
body .container .zoom-btns {
position: fixed;
z-index: 2;
right: 50px;
bottom: 50px;
}
html .container .zoom-btns button,
body .container .zoom-btns button {
margin-left: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<div class="container">
<div class="zoom-btns">
<button class="zoom-in-btn">Zoom In</button>
<button class="zoom-out-btn">Zoom Out</button>
</div>
</div>
Demo Fiddle here.
I think that instead of using width/height CSS properties to zoom, you should consider using transform and transform-origin properties. You can use the scale value of transform to zoom, then assign the coordinates of the center of the item as the transform-origin value.
CSS Transform
CSS Transform Origin

Background zooms in incorrectly when following mouse movement

I've created a button, when you hover over it a div with a background image enlarges. The background should follow the cursor's movements. Meaning that the background image follows your cursor as you hover over the button.
When I hover over the bottom right part of the button, the image will zoom in to the top left before moving to the cursor's location. Check out the example below to see what I mean. I'd like to make it zoom directly to the cursor's location, any ideas?
var imgZoomIn = 1.2; //% of how much the image will enlarge
function buttonImgResize(x, e){
//Location of the cursor (as percentage of the container's size)
var imgContPosition = x.getBoundingClientRect();
var mousePosX = (e.clientX - imgContPosition.left) / x.offsetWidth;
var mousePosY = (e.clientY - imgContPosition.top) / x.offsetHeight;
//Finding how far to move the image from top and left
var leftImgOverlap = x.offsetWidth * (imgZoomIn - 1) * mousePosX;
var topImgOverlap = x.offsetHeight * (imgZoomIn - 1) * mousePosY;
//implementing changes
x.firstElementChild.style.left = 0 - leftImgOverlap + "px";
x.firstElementChild.style.top = 0 - topImgOverlap + "px";
}
//Reseting values
function buttonImgDownsize(x){
x.firstElementChild.style.left = "0";
x.firstElementChild.style.top = "0";
}
/*Container*/
.showCaseBtn{
overflow: hidden;
position: relative;
width: 500px; height: 300px;
}
/*div with background image*/
.showcaseBtn_Back{
position: absolute; top: 0; left: 0;
width: 100%; height: 100%;
background: black no-repeat center center / cover;
transition: 0.4s;
}
.showCaseBtn:hover .showcaseBtn_Back{
height: 120%; width: 120%;
}
<div class="showCaseBtn" onmousemove="buttonImgResize(this, event)" onmouseout="buttonImgDownsize(this)">
<div class="showcaseBtn_Back" style="background-image: url(https://media.contentapi.ea.com/content/dam/need-for-speed/common/2017/10/nfs-payback-jaguar-f-type-2x.jpg.adapt.1920w.jpg);"></div>
</div>
The problem is caused by the css transition. If you remove it, the zooming works but no fancy scaling will happen.
var imgZoomIn = 1.2;
function buttonImgResize(x, e){
var imgContPosition = x.getBoundingClientRect();
var mousePosX = (e.clientX - imgContPosition.left) / imgContPosition.width;
var mousePosY = (e.clientY - imgContPosition.top) / imgContPosition.height;
var leftImgOverlap = imgContPosition.width * (imgZoomIn - 1) * mousePosX;
var topImgOverlap = imgContPosition.height * (imgZoomIn - 1) * mousePosY;
x.firstElementChild.style.left = - leftImgOverlap + "px";
x.firstElementChild.style.top = - topImgOverlap + "px";
}
function buttonImgDownsize(x){
x.firstElementChild.style.left = "0";
x.firstElementChild.style.top = "0";
}
.showCaseBtn{
overflow: hidden;
position: relative;
width: 500px; height: 300px;
}
.showcaseBtn_Back{
position: absolute; top: 0; left: 0;
width: 100%; height: 100%;
background: black no-repeat center center / cover;
}
.showCaseBtn:hover .showcaseBtn_Back{
height: 120%; width: 120%;
}
<div class="showCaseBtn" onmousemove="buttonImgResize(this, event)" onmouseout="buttonImgDownsize(this)">
<div class="showcaseBtn_Back" style="background-image: url(https://media.contentapi.ea.com/content/dam/need-for-speed/common/2017/10/nfs-payback-jaguar-f-type-2x.jpg.adapt.1920w.jpg);"></div>
</div>

Animating newly created objects with different keyframes values

So I'm trying to make simple animation. When you press somewhere inside blue container, a circle should be created in this place and then go up. After some research I found how to put JS values into keyframes, but it's changing values for every object not just for freshly created. If you run snipped and press somewhere high and then somewhere low you will see what I'm talking about.
I found some AWESOME solution with Raphael library, but I'm a beginner and I'm trying to make something like this in JS. Is it even possible? How?
var bubble = {
posX: 0,
posY: 0,
size: 0
};
var aquarium = document.getElementById("container");
var ss = document.styleSheets;
var keyframesRule = [];
function findAnimation(animName) { //function to find keyframes and insert replace values in them
for (var i = 0; i < ss.length; i++) {
for (var j = 0; j < ss[i].cssRules.length; j++) {
if (window.CSSRule.KEYFRAMES_RULE == ss[i].cssRules[j].type && ss[i].cssRules[j].name == animName) {
keyframesRule.push(ss[i].cssRules[j]);
}
}
}
return keyframesRule;
}
function changeAnimation (nameAnim) { //changing top value to cursor position when clicked
var keyframesArr = findAnimation(nameAnim);
for (var i = 0; i < keyframesArr.length; i++) {
keyframesArr[i].deleteRule("0%");
keyframesArr[i].appendRule("0% {top: " + bubble.posY + "px}");
}
}
function createBubble(e) {
"use strict";
bubble.posX = e.clientX;
bubble.posY = e.clientY;
bubble.size = Math.round(Math.random() * 100);
var bubbleCircle = document.createElement("div");
aquarium.appendChild(bubbleCircle);
bubbleCircle.className = "bubble";
var bubbleStyle = bubbleCircle.style;
bubbleStyle.width = bubble.size + "px";
bubbleStyle.height = bubble.size + "px";
bubbleStyle.borderRadius = (bubble.size / 2) + "px";
//bubbleStyle.top = bubble.posY - (bubble.size / 2) + "px";
bubbleStyle.left = bubble.posX - (bubble.size / 2) + "px";
changeAnimation("moveUp");
bubbleCircle.className += " animate";
}
aquarium.addEventListener("click", createBubble);
//console.log(bubble);
body {
background-color: red;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
position: fixed;
top: 80px;
left: 0;
background-color: rgb(20,255,200);
}
#surface {
width: 100%;
height: 40px;
position: fixed;
top: 40px;
opacity: 0.5;
background-color: rgb(250,250,250);
}
.bubble {
position: fixed;
border: 1px solid blue;
}
.animate {
animation: moveUp 5s linear;//cubic-bezier(1, 0, 1, 1);
-webkit-animation: moveUp 5s linear;//cubic-bezier(1, 0, 1, 1);
}
#keyframes moveUp{
0% {
top: 400px;
}
100% {
top: 80px;
}
}
#-webkit-keyframes moveUp{
0% {
top: 400px;
}
100% {
top: 80px;
}
}
<body>
<div id="container">
</div>
<div id="surface">
</div>
</body>
Here is a possible solution. What I did:
Remove your functions changeAnimation () and findAnimation() - we don't need them
Update the keyframe to look like - only take care for the 100%
#keyframes moveUp { 100% {top: 80px;} }
Assign top of the new bubble with the clientY value
After 5 seconds set top of the bubble to the offset of the #container(80px) - exactly when animation is over to keep the position of the bubble, otherwise it will return to initial position
var bubble = {
posX: 0,
posY: 0,
size: 0
};
var aquarium = document.getElementById("container");
function createBubble(e) {
"use strict";
bubble.posX = e.clientX;
bubble.posY = e.clientY;
bubble.size = Math.round(Math.random() * 100);
var bubbleCircle = document.createElement("div");
aquarium.appendChild(bubbleCircle);
bubbleCircle.className = "bubble";
var bubbleStyle = bubbleCircle.style;
bubbleStyle.width = bubble.size + "px";
bubbleStyle.height = bubble.size + "px";
bubbleStyle.borderRadius = (bubble.size / 2) + "px";
bubbleStyle.top = bubble.posY - (bubble.size / 2) + "px";
bubbleStyle.left = bubble.posX - (bubble.size / 2) + "px";
bubbleCircle.className += " animate";
// The following code will take care to reset top to the top
// offset of #container which is 80px, otherwise circle will return to
// the position of which it was created
(function(style) {
setTimeout(function() {
style.top = '80px';
}, 5000);
})(bubbleStyle);
}
aquarium.addEventListener("click", createBubble);
body {
background-color: red;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
position: fixed;
top: 80px;
left: 0;
background-color: rgb(20, 255, 200);
}
#surface {
width: 100%;
height: 40px;
position: fixed;
top: 40px;
opacity: 0.5;
background-color: rgb(250, 250, 250);
}
.bubble {
position: fixed;
border: 1px solid blue;
}
.animate {
animation: moveUp 5s linear;
/*cubic-bezier(1, 0, 1, 1);*/
-webkit-animation: moveUp 5s linear;
/*cubic-bezier(1, 0, 1, 1);*/
}
#keyframes moveUp {
100% {
top: 80px;
}
}
#-webkit-keyframes moveUp {
100% {
top: 80px;
}
}
<body>
<div id="container"></div>
<div id="surface"></div>
</body>
The problem about your code was that it is globally changing the #keyframes moveUp which is causing all the bubbles to move.
The problem with your code is that you're updating keyframes which are applied to all bubbles. I tried another way of doing it by using transition and changing the top position after the element was added to the DOM (otherwise it wouldn't be animated).
The main problem here is to wait the element to be added to the DOM. I tried using MutationObserver but it seems to be called before the element is actually added to the DOM (or at least rendered). So the only way I found is using a timeout which will simulate this waiting, although there must be a better one (because it may be called too early, causing the bubble to directly stick to the top), which I would be happy to hear about.
var bubble = {
posX: 0,
posY: 0,
size: 0
};
var aquarium = document.getElementById("container");
function createBubble(e) {
"use strict";
bubble.posX = e.clientX;
bubble.posY = e.clientY;
bubble.size = Math.round(Math.random() * 100);
var bubbleCircle = document.createElement("div");
aquarium.appendChild(bubbleCircle);
bubbleCircle.classList.add("bubble");
var bubbleStyle = bubbleCircle.style;
bubbleStyle.width = bubble.size + "px";
bubbleStyle.height = bubble.size + "px";
bubbleStyle.borderRadius = (bubble.size / 2) + "px";
bubbleStyle.top = bubble.posY - (bubble.size / 2) + "px";
bubbleStyle.left = bubble.posX - (bubble.size / 2) + "px";
setTimeout(function() {
bubbleCircle.classList.add("moveUp");
}, 50);
}
aquarium.addEventListener("click", createBubble);
body {
background-color: red;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
position: fixed;
top: 80px;
left: 0;
background-color: rgb(20, 255, 200);
}
#surface {
width: 100%;
height: 40px;
position: fixed;
top: 40px;
opacity: 0.5;
background-color: rgb(250, 250, 250);
}
.bubble {
position: fixed;
border: 1px solid blue;
transition: 5s;
}
.moveUp {
top: 80px !important;
}
<body>
<div id="container">
</div>
<div id="surface">
</div>
</body>
Also, I used the classList object instead of className += ... because it is more reliable.

Categories

Resources