Using canvas layers that are not positioned using position:absolute - html

I am trying to use this example:
http://html5.litten.com/using-multiple-html5-canvases-as-layers/
to create multiple html5 layers (Actually only need 2) for a background and then an animation on top of that background.
The problem is the example, and many other solutions that suggest layering canvases using z-index, etc. all seem to position the canvas at left:0 and top:0 in absolute position.
For example:
html5 - canvas element - Multiple layers
html5 - canvas element - Multiple layers
However, what I would like to do, is have the position be dynamic but always so the two canvases are layered on top of each other.
What I've had to do so far is this:
<div id="canvasesdiv" align=center; style="text-align:center; float:center">
<canvas id="bottomlayer-background" style="z-index: 1; border:1px dotted;" align=center>
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<canvas id="toplayer-movingstuff" style="z-index: 2; border:1px dotted; position:absolute; left:530px; top:83px">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
The problem with this approach is that I had to manually figure out where the top left of the bottom layer was and then input that into the top layer. But this position is only true for one browser, on one monitor, on full screen, etc. Obviously, not workable.
When I try and have both just be align=center, then what happens is the canvases appear side-by-side instead of layered on top of each other.
When I try to do absolute position for both, the problem with that is the other stuff in the windows, that were originally below the canvases (i.e. text, tables, etc.) suddenly appear underneath the canvases.
Has anyone else been able to solve this problem?
Thanks!

The way that absolute positioning works is that the target element is absolutely positioned within its closest positioned ancestor. This means that if the containing div is positioned absolute or relative, then the target element will be absolutely positioned within the containing div.
Note: You don't really need the z-index unless you've messed with z-index somewhere else
Another Note: If you want your canvases to behave, set the width and height attributes on them, otherwise things will scale weird.
http://jsfiddle.net/mobidevelop/4WDJz/
HTML
<p>Other Content</p>
<p>Other Content</p>
<p>Other Content</p>
<div id="contain">
<canvas id="surface1" width="480" height="160">
</canvas>
<canvas id="surface2" width="480" height="160">
</canvas>
</div>
<p>Other Content</p>
<p>Other Content</p>
<p>Other Content</p>​
CSS:
#contain {
width: 480px;
height: 160px;
margin: 0 auto;
position: relative;
}
#surface1,
#surface2 {
top: 0;
left: 0;
position: absolute;
}
​And, for good measure, JS:
var surface1 = document.getElementById('surface1');
if (surface1 != null) {
if (surface1.getContext) {
var context = surface1.getContext("2d");
if (context != null) {
context.fillStyle = "rgba(255,0,0,0.25)";
context.fillRect(0,0,480,160);
}
}
}
surface2 = document.getElementById('surface2');
if (surface2 != null) {
if (surface2.getContext) {
var context = surface2.getContext("2d");
if (context != null) {
var x = 0;
context.fillStyle = "rgba(0,0,0,1.0)";
last = new Date();
setInterval
(
function()
{
var now = new Date();
var del = (now - last) / 1000.0
last = now;
context.clearRect(0,0, 480, 160);
context.fillRect(x, 10,32,32);
x += 10 * del;
if (x > 480) {
x = -32;
}
}, 15
);
}
}
}
​

#canvasesdiv{margin:0 auto; position:relative; width:300px;}
#bottomlayer-background, #toplayer-movingstuff{
position:absolute;
z-index: 1;
border:1px dotted blue;
height:200px;
width:300px;
}
#toplayer-movingstuff{
z-index: 2;
border:1px solid red;
}
​
Working Fiddle
The above should work. Just give the containing element a relative position, which will make the child elements positioned with absolute relative to the parent.

Related

On click, animate the div element to the top and make it sticky

I have a parent div, that contains multiple child divs inside it, On clicking any child element,I am trying to animate that child element from its current position to the top position according to its current parent, then after moving to the top with proper animation, I want that child element to be sticky, so that other child elements can be easily scrolled underneath it and then, if any other child element is clicked then it will animate to top and the recent child element will move back to its old position.
Any help using angular and/or html and css will be really appreciative.
I am also attaching stackblitz angular project link for my initial code.
https://stackblitz.com/edit/angular-ivy-abbnjo
Thanks
I find this question interesting, so I did a quick sample of how you can do this via jQuery. There are certainly libraries there that probably does this already, but with the interest of sharing the logic behind it, here's a quick demo in JSFiddle. It may need some more love though.
I hope this helps!
JSFiddle Link: https://jsfiddle.net/qo6x42za/1/
HTML
<div>
<div class="sticky"></div>
<div class="container">
<div class="box" data-order="1">Box1</div>
<div class="box" data-order="2">Box2</div>
<div class="box" data-order="3">Box3</div>
<div class="box" data-order="4">Box4</div>
<div class="box" data-order="5">Box5</div>
</div>
</div>
CSS
.container {
position: relative;
height: 250px;
overflow-y: scroll;
border: 1px solid #000;
}
.box {
width: 100px;
height: 50px;
padding: 20px;
background-color: #595959;
color: #fff;
border: 1px solid #c90000;
}
Javascript
$('.box').each(function(index) {
$(this).on('click', function() {
const target = $(this);
const sticky = $('.sticky');
const container = $('.container');
const position = $(sticky).position();
// after animation completes
const options = {
complete: () => {
// detach previous item from sticky container and place back to original position
if ($(sticky).children().length > 0) {
const firstChild = $(sticky).children().first();
const order = $(firstChild).data('order');
const previousChild = order - 1;
if (order > 1) {
$(firstChild).detach().insertAfter($(`[data-order=${previousChild}]`));
} else {
$(firstChild).detach();
$(container).prepend($(firstChild));
}
}
// attach item to sticky container
$(sticky).append($(target));
// remove the style attribute as we no longer need it
$(target).removeAttr('style');
}
};
// animate to position
$(target).css({ position: 'absolute'});
$(target).animate({
top: position.top
}, options);
});
})

How do I draw <div> elements on top of a three.js canvas with a transparent background?

I was reading this answer but the approach proposed is purely theoretical. In my HTML5 code I've tried something like this:
<div id='gameCanvas'>
<div id="insideText">First trial</div>
</div>
and in the CSS I've put this:
#insideText{
background-color: transparent;
}
I'm quite a newbie when it comes to HTML/CSS, so I'm probably making some easy mistake, but this way I get a black line above my canvas, and the text appears in that black line. I want the text to appear over the canvas without this black line, and I would also like to know how to place some text on different areas of the canvas (my canvas is not fullscreen).
Edit:
If I put the <div> outside the canvas element it appears with a transparent background indeed, but always as a separate element from the canvas.
Your question isn't specific to three or canvas. It's just a basic HTML/CSS question which I'm sure is answered 1000 times on this site but since search sucks I'll answer again and leave it to someone with more patience to close as a dupe
To make 2 or more elements overlap you generally need a parent element. That element has to have css position: relative; or position:absolute;. That makes it the anchor/origin/base for its children. Note: <body> is already marked this way.
Then for all the children you can use position:absolute and set their positions with left, top, right, or bottom
Example
const ctx = document.querySelector('canvas').getContext('2d');
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, 300, 150);
ctx.fillStyle = 'red';
ctx.arc(150, 75, 125, 0, Math.PI * 2, true);
ctx.fill();
<h1>Some text</h1>
<div style="position: relative; display: inline-block;">
<canvas></canvas>
<div style="position: absolute; left: 1em; top: 1em;">foo</div>
<div style="position: absolute; right: 1em; bottom: 1em;">bar</div>
</div>
note: the display: inline-block; is to make the outer element fit the content rather than stretch to width of its parent (the body in this case). There are 100s of other ways to set the size of elements.
note that elements are transparent by default so no need to set the background color as transparent unless you set it somewhere else as non-transparent
const renderer = new THREE.WebGLRenderer();
document.querySelector('#gameCanvas').appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.add(
new THREE.Mesh(
new THREE.SphereGeometry(1),
new THREE.MeshBasicMaterial({color:'red'})
)
);
scene.background = new THREE.Color('yellow');
const camera = new THREE.PerspectiveCamera(45, 2, .1, 10);
camera.position.z = 2;
renderer.render(scene, camera);
#gameCanvas {
position: relative;
}
#insideText {
position: absolute;
left: 20px;
top: 20px;
}
<h1>some text</h1>
<div id='gameCanvas'>
<div id="insideText">First trial</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js"></script>

How to limit the view of a larger HTML <canvas> "inside" its parent <div>?

I've got a 5500 pxl wide panorama img (with usemap='#imagemap') on which I've overlain a canvas. These are parented inside a div.
Everything works as expected; the img view is restricted to the div, and can be scrolled. But the complete (wiiiide) canvas is visible in the browser window.
In addition, the canvas doesn't scroll with the div/img. I have to move it in an onscroll().
Edit: Here's a fiddle http://jsfiddle.net/91y2upam/1/ showing the canvas (outlined in green) escaping from the div.
Can a canvas be kept "inside" its parent div, with restricted view, and scrolled?
If so, how so?
The CSS:
#divPano {
width: 100%;
overflow: auto;
}
#cvsPano {
pointer-events: none; /* make the canvas transparent to the mouse - needed since canvas is position infront of image */
position: absolute;
}
and HTML:
<div name="divPano" id="divPano">
<canvas id='cvsPano'></canvas>
<img name='imgPano' id='imgPano' usemap='#mapPano' src='Pano/Pano0H500s.jpg' >
</div>
and the overlay JS (called from onload()):
function cvsInit(cvs, img) {
var x, y, w, h;
// get it's position and width+height
x = img.offsetLeft;
y = img.offsetTop;
w = img.width;
h = img.height;
// place cvsPano in front of the image
cvs.style.zIndex = 1;
cvs.parentNode.style.zIndex = 2; // <- this didn't work :-(
// position it over the image
cvs.style.left = x+'px';
cvs.style.top = y+'px';
// make same size as the image
cvs.setAttribute('width', w+'px');
cvs.setAttribute('height', h+'px');
// get it's context
hdc = cvs.getContext('2d');
// set the 'default' values for the colour/width of fill/stroke operations
hdc.strokeStyle = '#007700';
hdc.lineWidth = 1;
} // function cvsInit()
Make the parent position:relative and the oversized child canvas position:absolute.
Add scrollbars with overflow:scroll or
Pan programmatically with the parent overflow:hidden and moving the canvas with left:-100px.
Pan using scrollbars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload=function(){
ctx.drawImage(img,0,0,img.width,img.height,0,0,canvas.width,canvas.height);
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/canvas%20compositing.png";
body{ background-color: ivory; padding:50px; }
canvas{position:absolute; border:1px solid red;}
#parent{position:relative; overflow:scroll; width:300px; height:300px; border:2px solid blue; }
<div id=parent>
<canvas id="canvas" width=800 height=500></canvas>
</div>
Pan programatically
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
$myslider=$('#myslider');
$myslider.attr({min:-200,max:0}).val(0);
$myslider.on('input change',function(){
$('#canvas').css('left',parseInt($(this).val()));
});
var img=new Image();
img.onload=function(){
ctx.drawImage(img,0,0,img.width,img.height,0,0,canvas.width,canvas.height);
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/canvas%20compositing.png";
body{ background-color: ivory; padding:50px; }
canvas{position:absolute; left:0px; border:1px solid red;}
#parent{position:relative; overflow:hidden; width:300px; height:300px; border:2px solid blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input id=myslider type=range>
<br>
<div id=parent>
<canvas id="canvas" width=800 height=500></canvas>
</div>
In the css, try changing the property overflow to something else like:
overflow: scroll;
Maybe that way it will scroll with the page.

Absolute div overlay iframe borders?

I'm wondering if there is a way to have a div, absolutely positioned, hover over the border of the iframe that div is in. Can this be done?
My case:
I have an iframe with a list of files in it, on the right end of each file there is a button. I want to have a div-popup with some functions like a contextmenu. But because this button is at the edge of the iframe the absolutely positioned div is put behind/outside the iframe viewport. I want it to overlay in the rest of my document, outside the iframe.
​<iframe width="100" height="100">
div would be in here, say 300 x 100 px.
</iframe>
overlayed div should be visible here as well, basically the div should overlay the iframe.​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
Well, technically you can't do that. However, if you hijack the events in the iframe, you can recreate the context menu in the main window and use the relative position of the div within the iframe + the absolute position of the iframe itself.
So, to sum up, the context menu can be outside the iframe, and manipulated by the events from within the iframe.
Let me show you how it can be done. I don't have your code, so I'm just making a very crude proof of concept. :)
Example | Code
HTML
<iframe id='my_frame'></iframe>
<div id='copy_to_frame'>
<ul id='files_list'>
<li>data.dat</li>
<li>manual.html</li>
<li>readme.txt</li>
<li>model1.obj</li>
<li>human_model.obj</li>
</ul>
</div>
<div class='context_menu'>
<ul>
<li>Delete</li><li>Open</li><li>Move</li><li>Copy</li>
</ul>
</div>
Javascript
//Declare the necessary variables, good practice
var frame = $("#my_frame"),
frame_contents = frame.contents(),
frame_body = frame_contents .find("body"),
copy_list = $("#copy_to_frame"),
context_menu = $(".context_menu");
var bInside = false;
//Fill the iframe with a list
frame_body.html(copy_list.html());
copy_list.hide();
paint();
//Attach event handler for context menu popup etc.
$("#files_list li", frame_body).click(function(e){
var $this = $(this);
var rel_x = $this.position().left + $this.outerWidth() + 5,
rel_y = $this.position().top + $this.outerHeight()/2 - context_menu.outerHeight()/2 - frame_body.scrollTop(),
abs_x = frame.offset().left,
abs_y = frame.offset().top;
e.stopPropagation();
context_menu.css({
top: rel_y + abs_y,
left: rel_x + abs_x
});
//Show the context menu in this window
context_menu.show();
paint($this);
});
//Hide when clicking outside the context menu
$(document).add(frame_body).click(function(){
if(!bInside){
context_menu.hide();
paint();
}
});
//Determine if mouse is inside context menu
context_menu.mouseenter(function(){
bInside = true;
}).mouseleave(function(){
bInside = false;
});
function paint(el){
$("#files_list li", frame_body).css({
"background-color": "white",
"border": "1px solid transparent"
});
if(el){
el.css({
"background-color": "#ddecfd",
"border": "1px solid #7da2ce"
});
}
}
CSS
#my_frame{
position: absolute;
border: 1px solid gray;
width: 200px;
height: 100px;
top: 50%; left: 50%;
margin-top: -62.5px;
margin-left: -100px;
z-index: 1;
}
.context_menu{
display: none;
position: absolute;
top: 0;
left: 0;
background-color: white;
z-index: 2;
}
.context_menu ul{
border: 1px solid black;
border-right: 0;
display: inline-block;
}
.context_menu li{
display: inline-block;
border-right: 1px solid black;
padding: 2px;
text-align: center;
margin: 0px;
cursor: default;
}
.context_menu li:hover{
background-color: lightgray;
}
This is a bit of a guess based on the minimal information that was provided, but...
You can manipulate the contents of an <iframe> from within the parent document using jQuery, like so:
$('#myFrame').contents().find('a').click(function() { /*...*/ });
This allows you to detect when the user has clicked inside the <iframe>. Then you can work out where to position your overlay <div>.
Your overlay <div> will need to have position: fixed set. You can use jQuery's .offset() method to get the coordinates of the <iframe> and the link that was clicked inside the <iframe>. You can use these two values to calculate where to position the overlay <div> in the parent document. For example, to position the overlay to the left of the <iframe> and on the same vertical level as the link that was clicked you can do this:
$('#overlayDiv')
.offset({
left: $('#myFrame').offset().left - $('#overlayDiv').width(),
top: $('#myFrame').offset().top + $(this).offset().top
})
See this fiddle for a basic example of how it could work: http://jsfiddle.net/Gxd3M/2/
(Note that this assumes that the contents of the parent document and the iframe both come from the same server, i.e. they have the same origin.)

Canvas 3D drawing using both 2D and 3D context

Since the webgl/opengl doesn't support text drawing, so it possible to draw 3D object using 3D context and text drawing using 2D context ?
No, unfortunately not.
The HTML 5 spec says that if you call getContext on a canvas element that is already in a different context mode and the two contexts are not compatible then return null.
Unfortunately "webgl" and "2d" canvases are not compatible and thus you will get null:
var canvas = document.getElementById('my-canvas');
var webgl = canvas.getContext("webgl"); // Get a 3D webgl context, returns a context
var twod = canvas.getContext("2d"); // Get a 2D context, returns null
As stated, you cannot do this.
However you can put one canvas on top of another and draw to them separately. I've done this before and it can work out quite well.
Create the text as a texture using canvas 2D, then render it in 3D. See here for a tutorial.
What I've been doing, whether I just need troubleshooting feedback or 2D text content on a 3D canvas, is just use CSS to put some HTML elements on top of the canvas.
You can make a canvas and a group of text fields share the same space, and ensure that the text fields are on top as follows:
HTML:
<div id="container">
<canvas id="canvas"></canvas>
<div id="controlsContainer">
<label>Mouse Coordinates</label>
<div>
<label for="xPos">X</label>
<span id="xPos"></span>
</div>
<div>
<label for="yPos">Y</label>
<span id="yPos"></span>
</div>
</div>
</div>
CSS:
canvas {
margin: 0px;
position: relative;
top: 0px;
left: 0px;
width: 100%;
}
#container {
position: relative;
}
#controlsContainer {
position: absolute;
left: 10px;
top: 10px;
z-index: 3;
}
#controlsContainer div {
padding-left: 8px;
}
#controlsContainer label {
color: white;
z-index: 4;
}
#controlsContainer span {
color: white;
z-index: 4;
}
The z-index will ensure which elements are in front, and position: relative for the container and position: absolute, in coordination with the top and left for the controls will ensure that they share the same space.
I have been having very good luck with this, and the troubleshooting feedback is invaluable.
An example of the Javascript (ES6 in this case) is:
let xPos = document.getElementById("xPos");
let yPos = document.getElementById("yPos");
let x = event.clientX - containerDiv.offsetLeft -parseInt(window.getComputedStyle(pageBody).getPropertyValue("margin-left"));
let y = event.clientY - containerDiv.offsetTop - parseInt(window.getComputedStyle(pageBody).getPropertyValue("margin-top"));
xPos.innerText = x;
yPos.innerText = y;
which I've placed in a mousemove handler.