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

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.

Related

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>

HTML isometric view

If I create an isometric grid of tiles using only HTML (each grid item being a diamond-shaped image), the tiles overlap on the corners. So, clicking on one will likely click the image that is overlapping it.
I can use JavaScript to get the X/Y of the mouse click event and determine which image was clicked. I can use HTML5 and, similarly, translate the X/Y of the click into an image.
I'm looking into using SVG to rotate images 45 degrees. Then, they don't overlap. I can use an on-click on the SVG objects. So far, this appears to be the simplest method of handling click events in isometric view in HTML.
Is there a method of displaying non-square objects in HTML that I've overlooked?
Long time since this question was asked, so most probably you already found the answer that you were searching for, but I would like to clarify it for anyone reaching here with the same doubt.
If you apply CSS transformations to an HTML element, you don‘t need to make any JavaScript calculation to know if it was clicked. Looking at your comment it seems that you think that the mouse events work in the element boundary box instead of the element itself, but it doesn‘t work in that way. The mouse events are triggered when the area respective to the element is clicked, respecting its transformations.
Take a look at this small snippet. The tiles have been transformed with CSS transformations, click on the tiles so you can check that the events are triggered taking into account the diamond shape of each one.
document.querySelectorAll('.isometric').forEach(tile => {
tile.addEventListener('click', function () {
this.dataset.active = 1 - (this.dataset.active || 0);
});
});
body, html {
height: 100%;
margin: 0;
padding: 0;
}
.container {
left: 50%;
position: relative;
top: 50%;
transform: translateY(-100px);
}
.isometric {
background-color: #CCC;
height: 100px;
outline: 2px solid #FFF;
position: absolute;
width: 100px;
}
.isometric[data-active="1"] {
background-color: #F00;
}
<script src="https://unpkg.com/isometric-css#2.2.3/index.js"></script>
<div class="container">
<div class="isometric" data-view="top" ></div>
<div class="isometric" data-view="top" data-right="100"></div>
<div class="isometric" data-view="top" data-left="100" data-left="100"></div>
<div class="isometric" data-view="top" data-right="100" data-left="100"></div>
</div>

Processing.js canvas with some divs over it not working on iPad

I am building a webpage with a fullscreen background built with Processing that is being executed on a fixed positioned fullscreen html5 canvas. On top of this canvas I'm positioning some divs which are being displayed as expected. As my Processing animation responds on mouseX and mouseY user interaction, I had to pass the jQuery .mousemove x and y variables to my sketch because when I was using Processing's mouseX and mouseY everytime the cursor was over a div the animation stopped so that solved my problem. On desktop FF, Chrome and Safari it works great but on iPad I can't get the canvas to recognize the mouseX and mouseY coordinates if the divs are displayed, no matter if I'm using jQuery's .mousemove or Processing's mouseX and mouseY. I tried getting rid of all divs and the sketch worked as expected. I also tried with CSS pointer-events:none; but it didn't worked and, of course, all links stopped working as well. Can anyone help? Why is this happening on iPad when on desktop everything works great?
This is and example of my code:
HTML
<body>
<!-- Background P5 Canvas -->
<canvas id="p5can" data-processing-sources="noise/noise.pde"></canvas>
<header>
<div id="name">XXXX</div>
<div id="project-name">XXXX</div>
</header>
<div class="wrapper">
<div id="disclaimer">XXXX</div>
<div id="built">xxxx</div>
<div id="about-me-shooter">About me_</div>
<div id="contact-me-shooter">Contact me_</div>
</div><!-- Wrapper -->
CSS
canvas {
position: fixed;
}
header {
position: fixed;
top: 25px;
left: 0;
width: 1120px;
height: 150px;
z-index: 10;
}
.wrapper {
width: 1208px;
position: absolute;
top: 0;
left: 0;
z-index: 9;
}
PDE File Example
float t = 0;
void setup() {
background(#bdbdbd);
size(window.innerWidth, window.innerHeight);
smooth();
}
void draw() {
float x = mouseX;
while (x < width) {
line(x, mouseY * noise(x, t), x + 60, (mouseY + 60) * noise(x, t));
x = x + 1;
}
t = t + 0.01;
}
Solution was so easy... it took me hours to find it out! Seems Chrome & Safari on iPad needs to get the canvas size declared on the CSS otherwise it won't work when adding divs on top of the canvas. It doesn't matter if you declare the size in your PDE file. So adding width:100% and height:100% to the canvas object solved my problem. Hope this can help other people having the same problem :)

Using canvas layers that are not positioned using position:absolute

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.

Equivalent of canvas "destination-out" for normal html element?

I've searched around quite a bit and I'm fairly certain this doesn't exist, I'm mainly looking to confirm that. What I'd like to do is have a div that makes everything behind it transparent -- similar to what canvas' destination-out compositing option does.
For a little more context, here's the situation. I have an OpenGL window drawing behind a QtWebKit overlay. The OpenGL window has multiple "subwindows" that can be overlapping, which are decorated using the WebKit overlay. When they overlap though, because of this two layer system, the decorations for the overlapped windows do not get occluded.
The backup option is just to use a full-window canvas for this (the window trimmings are fairly simple), but it would be nicer not to. Note that because this is an embedded WebKit instance, it doesn't need to be cross-browser, and something WebKit (or QtWebKit) specific is fine.
EDIT
I can't answer my own question within 24 hours, so here's my solution, with thanks to #Kevin Peno
The following is a simplified version of what I was looking for. It creates two divs "visible" and "invisible". "invisible" masks off "visible" so that it displays the background image behind it instead of the "visible" div.
The real keys are -webkit-mask-image (http://www.webkit.org/blog/181/css-masks/) and -webkit-canvas (http://www.webkit.org/blog/176/css-canvas-drawing/), so this will only work with webkit-based browsers.
HTML:
<html>
<body>
<div id="visible"/>
<div id="invisible"/>
</body>
</html>
JavaScript:
function updateMask()
{
var w = $("#visible").width();
var h = $("#visible").height();
var context = document.getCSSCanvasContext("2d", "mask", w, h);
context.fillStyle = "rgba(255, 255, 255, 1.0)";
context.fillRect(0, 0, w, h);
var my_off = $("#visible").offset();
var inv_off = $("#invisible").offset();
var rel_left = inv_off.left - my_off.left;
var rel_top = inv_off.top - my_off.top;
context.clearRect(rel_left, rel_top, $("#invisible").width(), $("#invisible").height());
}
$(window).ready(function()
{
updateMask();
$("#invisible").draggable();
$("#invisible").bind("drag", function(e, ui)
{
console.log("drag");
updateMask();
e.preventDefault();
});
});
CSS:
body
{
background-image: url(http://www.google.com/images/logos/ps_logo2.png);
}
#visible
{
position: absolute;
background-color: red;
z-index: 0;
width: 1000px;
height: 1000px;
top: 0;
left: 0;
-webkit-mask-image: -webkit-canvas(mask);
}
#invisible
{
position: absolute;
z-index: 1;
width: 100px;
height: 100px;
top: 50px;
left: 50px;
cursor: move;
background-color: rgba(0, 255, 0, 0.5);
}
Here's a blog post about using css to apply an image mask to an element. It sounds pretty close to what you are looking for or will at least be good for some ideas. Let me know how it works out.
CSS Masks