I have two canvases. The first is supposed to be the background and it's content is rendered via raw WebGL (3D). The second canvas is supposed to overlay the first one and is mainly transparent. It's content is rendered via three.js (3D content). Unfortunately the second canvas is not drawn on top of the first one, but next to it.
How can I draw the second canvas above the first one with transparent three.js scene background?
Update:
I integrated gaitat's idea. However it's still not working (see Fullscreen screenshot. Twice rendered grey box due to Oculus Rift support.). Canvas 2 is still not on top of the first one and it's background is not transparent as well. As a reminder: The grey square is drawn via raw WebGL on a canvas called webglcanvas. The box is rendered via three.js on leapcanvas.
Code 1 (Initialization of canvases and divs [Xtend/Java]):
Browser::getDocument().getElementById("view").appendChild(webglDiv)
val Element webGLCanvasDiv = Browser::getDocument().createDivElement()
webGLCanvasDiv.style.setCssText("position: absolute")
webGLCanvasDiv.style.setCssText("z-index: 8")
webGLCanvasDiv.setId("webGLCanvasDiv")
Browser::getDocument().getElementById("webglDiv").appendChild(webGLCanvasDiv)
val webGLCanvas = Browser::getDocument().createCanvasElement()
webGLCanvas.setWidth(viewportWidth)
webGLCanvas.setHeight(viewportHeight)
webGLCanvas.style.setCssText("border-bottom: solid 1px #DDDDDD")
webGLCanvas.setId("webglcanvas")
Browser::getDocument().getElementById("webGLCanvasDiv").appendChild(webGLCanvas)
val Element webGLLeapDiv = Browser::getDocument().createDivElement()
webGLLeapDiv.style.setCssText("position: absolute")
webGLLeapDiv.style.setCssText("z-index: 10")
webGLLeapDiv.setId("webGLLeapDiv")
Browser::getDocument().getElementById("webglDiv").appendChild(webGLLeapDiv)
val leapCanvas = Browser::getDocument().createCanvasElement()
// canvas size is handled via renderer in Javascript
leapCanvas.setId("leapcanvas")
Browser::getDocument().getElementById("webGLLeapDiv").appendChild(leapCanvas)
Code 2 (Rendering of three.js scene [Javascript])
var foreground = $doc.getElementById("leapcanvas");
var camera, scene, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
camera = new $wnd.THREE.PerspectiveCamera(75, 500 / 500, 1, 10000);
camera.position.z = 500;
scene = new $wnd.THREE.Scene();
geometry = new $wnd.THREE.BoxGeometry(200, 200, 200);
material = new $wnd.THREE.MeshNormalMaterial();
mesh = new $wnd.THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new $wnd.THREE.WebGLRenderer({
canvas : foreground,
alpha : true
});
renderer.setSize(viewportWidth / 2, viewportHeight);
renderer.setClearColor(0x000000, 0);
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
Assuming you have in your .html
<div id="container">
<div id="webglContainer"></div>
<div id="threeContainer"></div>
</div>
in your .css you need
/* make the canvases, children of the this container */
#container {
position: relative;
}
#webglContainer {
position: absolute;
pointer-events: none; /* this element will not catch any events */
z-index: 8; /* position this canvas at bottom of the other one */
}
#threeContainer {
position: absolute;
z-index: 10; /* position this canvas on top of the other one */
}
Related
When the canvas that contains a createjs is zoomed using a zoom style (e.g. zoom: 0.5) buttons on the canvas have a different click target that is either on only one side of the button or no target at all.
var stage, label;
function init() {
stage = new createjs.Stage("demoCanvas");
stage.name = "stage";
var background = new createjs.Shape();
background.name = "background";
background.graphics.beginFill("red").drawRoundRect(0, 0, 150, 60, 10);
label = new createjs.Text("foo", "bold 24px Arial", "#FFFFFF");
label.name = "label";
label.textAlign = "center";
label.textBaseline = "middle";
label.x = 150/2;
label.y = 60/2;
var button = new createjs.Container();
button.name = "button";
button.x = 75;
button.y = 40;
button.addChild(background, label);
stage.addChild(button);
// listeners
var targets = [stage,button,label,background];
for (var i=0; i<targets.length; i++) {
var target = targets[i];
target.on("click", toggleText, null, false, null, false);
}
stage.update();
}
function toggleText(evt) {
label.text = (label.text == 'foo') ? 'bar' : 'foo';
stage.update();
}
body{
margin: 0;
text-align: center;
}
#demoCanvas{
background: tan;
width: 500px;
height: 300px;
transform-origin: 0 0;
transform: scale(0.2); // Firefox = This does not cause problem with button clicking
zoom: 0.2; // Other browsers = This causes a problem with button clicking in Chrome
}
<html>
<head>
<script src="https://code.createjs.com/easeljs-0.8.2.min.js"></script>
</head>
<body onload="init();">
<canvas id="demoCanvas"></canvas>
</body>
</html>
Note that this will not fail in the SO snippet so I have also made this available as a Pen.
This behaviour presents itself in Chrome but not in Firefox (which recognises the transform: scale() style instead).
If the canvas needs to be scaled in the DOM how can this be done in Chrome without causing buttons to fail?
The zoom css style is considered non-standard so for Chrome and other Browsers you can use transform : scale() as well. I can confirm that zoom does break CreateJS - if you wanted to, you could add an issue to their EaselJS GitHub - not sure if it is worth fixing when transform : scale() can be used. Cheers.
CSS ZOOM: "Non-standard
This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future."
This may be more of a "you're going to have to do it the long way" type of deal, but here it goes...
When I apply CSS to control the opacity of only one HTML element that is the container of a Three.JS scene in where there are multiple elements that each are a container for their own scene, the CSS applied (even at an inline level) to only one of those elements containing a scene is being applied to all elements that contain a scene and not specifically the one targeted. This happens with any post applied CSS attribute, not just opacity.
The reason why I am attempting this approach at controlling opacity this way is that per my research there is no direct way to set an opacity on a Three.JS group object that contains 1 to N number of meshes. I am (in theory) trying not to have to define all materials with transparency set to "true" and then having to do recursive updates to all meshes in a Three.JS Group object where an animation would fade in/out.
Some of the group objects I'm working with will have many meshes defined in them. Thus, rather than update the opacity of each individual mesh itself contained within a Three.JS group object, my goal was/is to have individual scenes for each type of animation that is capable of having any amount of transparency applied to it run as is then just adjust the HTML element containing that animation's opacity property.
I've tried using one camera and multiple cameras to no avail. I've also tried nesting the containers in one additional element and setting CSS on the parent element but the same issue occurs. I have not tried using multiple renderers as from what I gather in research is that doing so is frowned upon and can lead to performance issues and context limits. The render loop also has "autoClear" set to false so that all scenes render together.
Here is the HTML syntax. You will notice that the first element has a inline style for opacity set to 0.5 and the second element has no inline styling applied:
<div class="three-js-container" id="scene-container-1" style="opacity:0.5;"></div>
<div class="three-js-container" id="scene-container-2"></div>
Here is the Javascript code:
/* Only one renderer instance is created */
var universalRenderer = new THREE.WebGLRenderer({antialias: true, alpha:true});
/* references to all containers are made */
var containerForScene1 = document.getElementById("scene-container-1");
var containerForScene2 = document.getElementById("scene-container-2");
/* two different cameras are created */
var cameraForScene1 = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.001, 1000);
var cameraForScene2 = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.001, 1000);
/* two different scenes are created, one for each element container */
var scene1 = new THREE.Scene();
scene1.userData.element = containerForScene1;
var scene2 = new THREE.Scene();
scene2.userData.element = containerForScene2;
/* the renderer is applied to both scene containers */
containerForScene1.appendChild(universalRenderer.domElement);
containerForScene2.appendChild(universalRenderer.domElement);
When both animations are played, both scenes are rendered at 1/2 opacity rather than just the first scene itself.
Is there a reason why CSS styling applied to one HTML scene containing element is applied to all of the other scene containing elements? Will I just have to suck it up and go the long way around in controlling mesh opacity?
Thanks.
Setting transparency for a THREE.Group:
A Group is just a container. As such, it has children, which are potentially other Groups. But you can only apply transparency to a Material, which is assigned at the Mesh level, not Groups. However, not all is lost, because you can monkey patch Group to allow you to perform the operation seamlessly.
// MONKEY PATCH
Object.defineProperty(THREE.Group.prototype, "transparent", {
set: function(newXP) {
this.traverse(node => {
if (node.material) {
node.material.transparent = newXP
node.material.opacity = (newXP) ? 0.5 : 1
}
})
}
})
// Set up the renderer
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
})
document.body.appendChild(renderer.domElement)
renderer.setSize(window.innerWidth, window.innerHeight)
const scene = new THREE.Scene()
const size = new THREE.Vector2()
renderer.getSize(size)
const camera = new THREE.PerspectiveCamera(28, size.x / size.y, 1, 1000)
camera.position.set(0, 20, 100)
camera.lookAt(scene.position)
scene.add(camera)
camera.add(new THREE.PointLight(0xffffff, 1))
function render() {
renderer.render(scene, camera)
}
const axis = new THREE.Vector3(0, 1, 0)
function animate() {
requestAnimationFrame(animate)
camera.position.applyAxisAngle(axis, 0.005)
camera.lookAt(scene.position)
render()
}
animate()
// Populate the scene
const cubeGeo = new THREE.BoxBufferGeometry(5, 5, 5)
let opaqueCubes = []
let transparentCubes = []
const randomInRange = () => Math.random() * ((Math.random() <= 0.5) ? -10 : 10)
const opaqueGroup = new THREE.Group()
scene.add(opaqueGroup)
for (let i = 0; i < 10; ++i) {
opaqueGroup.add(new THREE.Mesh(cubeGeo, new THREE.MeshPhongMaterial({
color: "red"
})))
opaqueGroup.children[i].position.set(randomInRange(), randomInRange(), randomInRange())
}
const transparentGroup = new THREE.Group()
scene.add(transparentGroup)
for (let i = 0; i < 10; ++i) {
transparentGroup.add(new THREE.Mesh(cubeGeo, new THREE.MeshPhongMaterial({
color: "green"
})))
transparentGroup.children[i].position.set(randomInRange(-10, 10), randomInRange(-10, 10), randomInRange(-10, 10))
}
// Control the transparency from the input
const xparent = document.getElementById("xparent")
xparent.addEventListener("change", (e) => {
transparentGroup.transparent = xparent.checked
})
html,
body {
padding: 0;
margin: 0;
overflow: hidden;
}
#control {
position: absolute;
top: 0;
left: 0;
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.js"></script>
<div id="control">
<label>Make the green ones transparent:<input id="xparent" type="checkbox" /></label>
<div>
I am trying to fix our parallax effect on our demo site however for the life of me I cannot get it working correctly. The parallax effect works perfectly however the positioning of the image repeats below. The issue occurs when the browser window is not full width.
background: URL(http://www.oddpandadesign.co.uk/albaband/wp-content/uploads/2014/03/parallax_head.png) 50% 0 fixed;
background-size: cover;
jQuery
jQuery(document).ready(function(){
// cache the window object
$window = jQuery(window);
jQuery('section[data-type="background"]').each(function(){
// declare the variable to affect the defined data-type
var $scroll = jQuery(this);
jQuery(window).scroll(function() {
// HTML5 proves useful for helping with creating JS functions!
// also, negative value because we're scrolling upwards
var yPos = -($window.scrollTop() / $scroll.data('speed'));
// background position
var coords = '50% '+ yPos + 'px';
// move the background
$scroll.css({ backgroundPosition: coords });
}); // end window scroll
}); // end section function
}); // close out script
/* Create HTML5 element for IE */
document.createElement("section");
I am not sure if its the image (though we have tried several) or the code is incorrect.This is not the first experience with parallax and it generally is simple so im a bit confused
Thanks for any help
I had to change
background: URL(http://www.oddpandadesign.co.uk/albaband/wp-content/uploads/2014/03/parallax_head.png) 50% 0 fixed;
to
background: URL(http://www.oddpandadesign.co.uk/albaband/wp-content/uploads/2014/03/parallax_head.png) fixed;
background-position: center top!important;
I'm having trouble figuring out how to make the following layout work. I'm not restricted to pure CSS - I know JS will be involved to make it cross-browser - but a CSS solution would be awesome. Here's what I am trying to achieve:
I've tried the following code, skewing the container and then skewing the image in the opposite direction, but it just gives me a square image. Chrome inspector shows me that the container is being skewed properly, but skewing the image back makes it square again. Adding an overflow:hidden to the container kind of works but the edges of the angle become jagged. Here's what I have tried:
http://codepen.io/anon/pen/ubrFz
Please help! :)
Need to tweak the positioning and the size of the container so you can crop it, and apply the backface-visibility rule:
.skew {
-webkit-backface-visibility : hidden; /* the magic ingredient */
-webkit-transform : skew(16deg, 0);
overflow : hidden;
width : 300px;
height : 260px;
position : relative;
left : 50px;
border : 1px solid #666
}
.skew img {
-webkit-transform : skew(-16deg, 0);
position : relative;
left : -40px;
}
http://codepen.io/anon/pen/HLtlG <- before (aliased)
http://codepen.io/anon/pen/wnlpt <- after (anti-aliased)
In lieu of a CSS solution, you could also achieve the effect by using a canvas and some JS; and compositing a series of cropped images onto that canvas. The benefit of the canvas method being that you'll potentially get smoother edges on the crops, and it is potentially a bit better supported.
A canvas element in HTML;
<canvas id="mycanvas"></canvas>
And JS;
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.src = '../my/image1.jpg';
img2.src = '../my/image2.jpg';
img3.src = '../my/image3.jpg';
var can = document.getElementById("mycanvas");
var ctx = can.getContext('2d');
var imgs = [img1, img2, img3]; //array of JS image objects that you've set up earlier
can.width = 1000;
can.height = 100;
for (var i=0; i < imgs.length; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(800 - (200 * i), 0);
ctx.lineTo(900 - (200 * i), 100);
ctx.lineTo(0, 100);
ctx.closePath();
ctx.clip();
ctx.drawImage(imgs[i], 0, 0);
}
The code is just off the top of my head - I haven't tested it. But basically - lets say you have a canvas that is a maximum of 1000px wide and 100px high. What happens above is, you set up a clipping area with a diagonal line across the canvas from point (800,0) to (900,100) and then draw the image into that clipping area... Then set up a new clipping path 200 pixels shorter for each image (note the '200 * i' bit).
Obviously the math needs to be adjusted for an arbitrary number of images and so on... But the idea is there.
A bit trickier than pure CSS maybe - but as I said - possibly a bit better supported cross-browser (IE's notwithstanding...).
EDIT
Did a quick test - looks like you need to set the canvas dimensions - and also obviously wait for all images to load properly before you can composite them on the canvas.
I need a white box (I'm using a Panel because I'm so close, but it could be anything) with all four corners rounded, and the top portion of the box needs to have a gradient that starts out one color (let's say Grey) and ends up as the background color (white). The gradient does not go down the entire box.
The top two corners must remain rounded, so the gradient must fill those rounded regions too.
What I have so far is an mx:Panel with the following styles:
Panel {
borderColor: #ffffff;
borderAlpha: 1;
borderThickness: 13;
borderThicknessLeft: 0;
borderThicknessTop: 0;
borderThicknessBottom: 11;
borderThicknessRight: 0;
roundedBottomCorners: true;
cornerRadius: 16;
headerHeight: 50;
backgroundAlpha: 1;
highlightAlphas: 0.29, 0;
headerColors: #999999, #ffffff;
backgroundColor: #ffffff;
dropShadowEnabled: false;
}
As you can see, there is one tiny single-pixel line that cuts across the header. If I can just get rid of that single-pixel line, that would be perfect! I have tried changing the borderStyle property to "solid" and still cannot get the right combination of styles to get rid of that line.
I have also tried using another container with a background image for the gradient, but the rounded corners become an issue.
Any help would be greatly appreciated!
The culprit for the line under the title bar is actually the default titleBackgroundSkin associated with the Panel component (mx.skins.halo.TitleBackground). If you look at the source of the updateDisplayList method you see a section where it draws the background that includes the following lines:
g.lineStyle(0, colorDark, borderAlpha);
g.moveTo(0, h);
g.lineTo(w, h);
g.lineStyle(0, 0, 0);
If you create your own implementation that does everything the same with the exception of these lines, you should get what you need. At least I did in my limited test case with a gradient header.
The following css gets rid of the line, but prevents you from using a gradient header.
.richTextEditor
{
titleBackgroundSkin: ClassReference("mx.skins.ProgrammaticSkin"); /** Gets rid of the line that was there **/
}
I didn't find a solution to the specific Panel issue above, but I did find an overall solution that works for any container: set a background image using bitmapFill and round the corners.
This is what worked for me (using a programmatic skin):
[styles.css]
HBox {
borderSkin: ClassReference("assets.programmatic.GradientHeaderSkin");
}
[GradientHeaderSkin.as]
package assets.programmatic
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import mx.skins.ProgrammaticSkin;
public class GradientHeaderSkin extends ProgrammaticSkin
{
[Embed(source="../imgs/panelHeaderGradient.png")]
private var _backgroundImageClass:Class;
private var _backgroundBitmapData:BitmapData;
private var _backgroundImage:Bitmap;
private var _backgroundColor:uint;
public function GradientHeaderSkin()
{
super();
_backgroundImage = new _backgroundImageClass();
_backgroundBitmapData = new BitmapData(_backgroundImage.width, _backgroundImage.height);
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
_backgroundBitmapData.draw(_backgroundImage);
graphics.clear();
graphics.beginBitmapFill(_backgroundBitmapData, null, false, false);
// specify corner radius here
this.graphics.drawRoundRectComplex(0, 0, this.width, this.height, 20, 20, 20, 20);
}
}
}
Here is also an example which loads an external image:
http://books.google.com/books?id=7fbhB_GlQEAC&pg=PA106&lpg=PA106&dq=flex+filling+rounded+corners+with+graphic&source=bl&ots=HU_jainH4F&sig=F793bjL0a4nU5wx5wq608h_ZPr0&hl=en&ei=GQd3Spi-OYiYMcLDyLEM&sa=X&oi=book_result&ct=result&resnum=1#v=onepage&q=&f=false