Make my three.js animation as my page's background [duplicate] - javascript

I've been looking at using three.js for a fun experiment on a site. I would like to use a current experiment (for which I already have the code for) and use it as a background for my site.
Anybody know how to do this?
I saw it done here: http://janjorissen.be/
Three JS API: https://github.com/mrdoob/three.js/wiki/API-Reference

I'm going to add yet another answer. I'd use
canvas {
width: 100vw;
height: 100vh;
display: block;
position: fixed;
top: 0;
left: 0;
z-index: -9999;
}
Here's why:
Many people use canvas { width: 100%; height: 100% } but that arguably doesn't make a lot of sense. You don't want the canvas to be 100% of the body. You want it to 100% of the screen/window. That's what canvas { width: 100vw; height: 100vh; } does. It's 100% of the viewport width and viewport height.
This means you don't need to set the body to height: 100% which also would not make sense, especially if the page is taller than the window/screen
display: block; fixes some issues with scrollbars on certain browsers. Some pages use html, body { overflow: none; } but again that doesn't make sense if your page ends up needing to be taller than the screen/window.
position: fixed; makes the canvas position relative to the top of window so it won't scroll with the page. If you use position: absolute then the canvas will scroll off the top if the page is taller than the screen/window. For example this page.
top: 0; left 0; puts it at the top left. Without that it would default to it's default position which is inside the body's margins. Often this is solved by setting body { margin: 0; } but generally that means you end up needing some other container to add a margin back in otherwise your normal content gets positioned at the edge of the window.
z-index: -9999; is there to try to force it further back than anything else just in case the page itself is using some negative values for z-index
Here's an example as a snippet
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
var canvas = document.querySelector("canvas");
var renderer = new THREE.WebGLRenderer({canvas: canvas});
renderer.setClearColor(0xF0F0F0);
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true,
});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 1;
function resize() {
var width = canvas.clientWidth;
var height = canvas.clientHeight;
if (width != canvas.width || height != canvas.height) {
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
function render(time) {
time *= 0.001;
resize();
cube.rotation.x = time;
cube.rotation.y = time * 0.31;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
canvas {
width: 100vw;
height: 100vh;
display: block;
position: fixed;
top: 0;
left: 0;
z-index: -9999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<canvas></canvas>
<div>
some content that is in front of the canvas
</div>
And here's an example outside SO so you can view it easier full size.
iframes work as well
Note that there's the issue that if your canvas animation is interactive the elements in front of the canvas will eat the mouse/touch events. There's no easy solution I know of for that. You can mark everything but that canvas/iframe as pointer-events: none and mark the canvas/iframe as pointer-events: auto but then you run into the issue that no text on your page can be selected and no links can be clicked. You could then say set <a> tags to have pointer-events: auto so links work but I'm sure there will be issues here and there depending on what info is on your page (trying to copy an email address, or a location address, etc...)
One note: most three.js examples are structured different (less flexible) by referencing window.innerWidth and window.innerHeight and putting the canvas inside a div with an id="canvas" for some reason.
Here's a snippet using that structure. There's several more lines of code, redundant calls to renderer.setSize and setting the camera aspect in 2 places (not very D.R.Y.) but as far as this Q&A is concerned the only difference is #canvas instead of canvas as the CSS to size the div instead of the canvas.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
document.getElementById("canvas").appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xF0F0F0);
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true,
});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 1;
function onResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
window.addEventListener('resize', onResize);
function render(time) {
time *= 0.001;
cube.rotation.x = time;
cube.rotation.y = time * 0.31;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
#canvas {
width: 100vw;
height: 100vh;
display: block;
position: fixed;
top: 0;
left: 0;
z-index: -9999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<div id="canvas"></div>
<div>
some content that is in front of the canvas
</div>

usually i use iframe for that. Thus you dont have conflict with the base page.
<style>
iframe {
z-index : -9999;
position: absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
margin : 0;
padding : 0;
}
</style>
<iframe src="http://example.com/"></iframe>
an example of it
https://github.com/jeromeetienne/www.jetienne.com/blob/master/index-webgl.html#L128 for the source
http://jetienne.com/index-webgl.html for the living code

This is not an actual background, but a 100% width/height element that is displaying the animation, with the rest of the content "elevated" using z-index or similar above that fake background.

Following the very basic example on threejs.org (here), I only had to change the canvas style section to:
canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: -9999;
}
That moved the canvas to the background.

Related

How can I get this Apple-like image sequence animation to start in the middle of the website, instead of only on top?

I couldn't find an example where this image sequence animation is triggered at any other part except on the top of the website.
The animation is working fine as it is, the problem is: if i put any content above the canvas, the image sequence will still be linked to the top of the page. So when the user gets to the animation, they will see it already half way through.
I'd like to ask for your help to change the starting point of this animation to only when it enters the viewport, not necessarily on the top of the page.
I'll also leave a link to the same exact snippet running on codepen, just in case: https://codepen.io/querubim_reginaldo/pen/OJzoOXK
Thanks in advance!
const html = document.documentElement;
const canvas = document.getElementById("pro_display_animation");
const context = canvas.getContext("2d");
const currentFrame = index => (
`https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${(index + 1).toString().padStart(4, '0')}.jpg`
);
// total of image files
const frameCount = 160;
// sets the canvas size to full screen
canvas.width = document.documentElement.clientWidth * 1;
canvas.height = document.documentElement.clientHeight * 1;
// draws the first image on screen
const img = new Image();
img.src = currentFrame(1);
img.onload = function() {
context.drawImage(img, 0, 0);
};
// updates the image in sequence
const updateImage = index => {
img.src = currentFrame(index);
context.drawImage(img, 0, 0);
}
// links the scroll position with the correct image
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
body {
margin: 0;
padding: 0;
}
#section_canvas_animation {
display: flex;
justify-content: center;
align-items: flex-start;
height: 1200vh;
width: 100vw;
background: #efefef;
}
canvas {
position: sticky;
top: 0;
max-width: 100vw;
max-height: 100vh;
}
.website_content {
height: 400vh;
background: darkcyan;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 100px;
}
<body>
<div id="section_canvas_animation">
<canvas id="pro_display_animation"></canvas>
</div>
<div class="website_content">THIS IS SOME WEBSITE CONTENT</div>
</body>
`
Check my demo here: https://www.sfertons.dev/lab/apple-scroll-sequence-animation
The trick is to take the "animation sequence" offsetTop into account and use its scrollHeight to calculate the maxScrollTop:
const scrollTop = document.documentElement.scrollTop - heroSequence.offsetTop
const maxScrollTop = heroSequence.scrollHeight - window.innerHeight
The project is also on my Github: https://github.com/Spharian/apple-sequence-animation

Frosted glass effect over dyanmic video

I want to create a media control bar for a video player with the controls blurring the background. Currently I can get the glassy look but I can't get blurring to work.
I tried following this guide: https://medium.com/#AmJustSam/how-to-do-css-only-frosted-glass-effect-e2666bafab91 but it seems like it only works on static images.
Here's an example of what I want:
I also saw the webkit backdrop-filter which looks perfect but it's only stable on safari so I can't use that. Any advice for frosted glass on dynamic videos?
I was able to accomplish this by copying the bottom part of the video to a canvas every frame and blurring the canvas via CSS. It seems to work well in Chrome, but flashes sometimes in Firefox. Doing the blur programmatically in the canvas with something like Superfast Blur might be more performant, but that's an experiment for another day. Tomorrow, probably.
function initControls (player, blurRadius, controlHeight, videoWidth, videoHeight) {
// crop player to video size
let video = player.querySelector('video');
videoWidth = videoWidth || video.clientWidth;
videoHeight = videoHeight || video.clientHeight;
player.style.width = videoWidth + 'px';
player.style.height = videoHeight + 'px';
// crop control bar to video size
let controlBar = player.querySelector('.control-bar');
controlBar.style.width = videoWidth + 'px';
controlBar.style.height = controlHeight + 'px';
// canvas needs to be slightly taller than what gets displayed
// to blur cleanly
let canvas = player.querySelector('canvas');
canvas.width = videoWidth;
canvas.height = 2 * blurRadius + controlHeight;
canvas.style.filter = `blur(${blurRadius}px)`;
canvas.style.top = -2 * blurRadius + 'px';
// copy video to canvas
let ctx = canvas.getContext('2d');
let videoCropY = videoHeight - canvas.height;
function updateCanvas () {
ctx.drawImage(
video,
0, videoCropY, canvas.width, canvas.height,
0, 0, canvas.width, canvas.height
);
}
// update the canvas only when necessary
let hovering = false;
function renderLoop () {
updateCanvas();
if (hovering && !video.paused) {
window.requestAnimationFrame(renderLoop);
}
}
// no point in rendering to a canvas you can't see
player.addEventListener('mouseenter', () => {
hovering = true;
renderLoop();
});
player.addEventListener('mouseleave', () => { hovering = false; });
video.addEventListener('play', renderLoop);
}
document.addEventListener('DOMContentLoaded', event => {
// do the magic
initControls(document.querySelector('.player'), 4, 50, 320, 240);
// basic play button functionality
document.querySelector('.play-button').addEventListener('click', event => {
let v = event.target.closest('.player').querySelector('video');
if (v.ended) v.currentTime = 0;
if (v.paused) {
v.play();
} else {
v.pause();
}
});
});
/* styling required for blurred control background */
.player {
position: relative;
}
.player > video {
margin: 0;
display: block;
}
.control-bar > canvas {
margin: 0;
display: block;
left: 0;
position: absolute;
}
.control-bar {
margin: 0;
overflow: hidden;
position: absolute;
bottom: 0;
/* height of control bar is specified in javascript, sorry about that */
}
/* simple control-hiding mechanism; other methods also work */
/* javascript relies on mouseover and mouseout to decide whether to update canvas */
.player > .control-bar {
display: none;
}
.player:hover > .control-bar {
display: block;
}
/* styling actual controls; adjust to taste */
.play-button {
height: 30px;
line-height: 30px;
width: 100px;
color: white;
border: 1px solid white;
position: absolute;
top: 10px;
left: 50%;
margin-left: -50px;
text-align: center;
}
<div class="player">
<video src="https://archive.org/download/MedicalA1950/MedicalA1950_512kb.mp4"></video>
<div class="control-bar">
<canvas></canvas>
<div class="play-button">PLAY/PAUSE</div>
</div>
</div>

Canvas toggle filling whole page removing scrollbar

I've got a canvas on my page and want to toggle it filling the page and backwards. My page is usually "higher" then a screens height so there is a scrollbar on my page. This scroll-bar does't hide when I'm setting the size of my canvas like that in my css:
canvas {
display: inline;
outline: 0;
margin: 50;
border: 1px solid #E0E0E0;
}
canvas.fullscreen {
display: block;
position: absolute;
top: 0;
left: 0;
border: none;
margin: 0;
}
My javascript looks like this:
//toggle the fullscreen mode
function fullscreen() {
if (!fullWindowState) {
fullWindowState = true;
//canvas goes full Window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.className = "fullscreen"
} else {
fullWindowState = false;
//canvas goes normal
canvas.width = 820;
canvas.height = 600;
canvas.className = "";
}
}
The full code is on github too and the page is on gh-pages
http://patsimm.github.io/mandelight/
I really don't know what to do to remove the scrollbar when the canvas is in "fullscreen" mode. Every help is apreciated!
Thanks!
patsimm
This has something to do width <canvas> tag.
<canvas> will cause scrollbar if not set to display:block.
detail: http://colla.me/bug/show/canvas_cannot_have_exact_size_to_fullscreen
Try setting overflow to hidden when you're in fullWindowState;
//toggle the fullscreen mode
function fullscreen() {
if (!fullWindowState) {
fullWindowState = true;
//canvas goes full Window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.className = "fullscreen"
document.body.scrollTop = 0; // <-- pull the page back up to the top
document.body.style.overflow = 'hidden'; // <-- relevant addition
} else {
fullWindowState = false;
//canvas goes normal
canvas.width = 820;
canvas.height = 600;
canvas.className = "";
document.body.style.overflow = 'visible'; // <-- toggle back to normal mode
}
}
Use the following CSS style for <html> and <body> tags,
<style>
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
}
</style>
and the following javascript code to set width and height of the <canvas id="canvas"><canvas> element,
<script>
var canvas = document.getElementById('canvas');
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
</script>
If you go into full screen mode and use the developer tools to set visibility: hidden on the canvas, you can see that the page content behind the canvas is is too large, causing the scrollbars to appear. I was able to get rid of the scrollbars by setting display: none on the page footer. You could toggle the display property of the footer or other content while in full screen mode since it will be covered up by the canvas anyway.

Positioning the three.js container as an html div

I have an article container which has three aside tags inside it. The third aside element id='menuPuzzleType' contains a Three.js script which makes two cube meshes, and renders them in a Canvas renderer.
<article id="popup">
<aside id='close'><img src="img/close.png" onClick="aFunction()" /></aside>
<aside id='msgPuzzleType'>Please choose the puzzle type you want to play</aside>
<aside id='menuPuzzleType'>
<script>
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
cube.rotation.y -= 0.03;
cube2.rotation.y -=0.03;
renderer2.render(scene2, camera2);
}
var container2 = document.getElementById('menuPuzzleType');
//renderer
var renderer2 = new THREE.CanvasRenderer();
renderer2.setSize(400,400);//Was 100,100
container2.appendChild(renderer2.domElement);
//Scene
var scene2 = new THREE.Scene();
//Camera
var camera2 = new THREE.PerspectiveCamera(50,50/50,1,1000);
camera2.position.z = 240;//normal value is 100
camera2.position.y=60;
scene2.add(camera2);
//Axes
var axes2= new THREE.AxisHelper();
//Add texture for the cube
//Use image as texture
var img2 = new THREE.MeshBasicMaterial({ //CHANGED to MeshBasicMaterial
map:THREE.ImageUtils.loadTexture('img/2d.png')
});
img2.map.needsUpdate = true; //ADDED
//Add Cube
var cube = new THREE.Mesh(new THREE.CubeGeometry(40,40,40),img2);
cube.position.x =- 60;
scene2.add(cube);
var img3 = new THREE.MeshBasicMaterial({ //CHANGED to MeshBasicMaterial
map:THREE.ImageUtils.loadTexture('img/3d.png')
});
img3.map.needsUpdate = true; //ADDED
var cube2 = new THREE.Mesh(new THREE.CubeGeometry(40,40,40),img3);
cube2.position.x = 70; cube.position.y = 60; cube2.position.y=60;
scene2.add(cube2);
renderer2.render(scene2,camera2);
animate();
</script>
</aside>
</article>
I need to put the two cubes into the article container tag.
Here is my css code:
body{ overflow:hidden; }
article {
border:solid 1px #00CC33;
width:420px;
height:200px;
font-family: baskerville;
font-size: 16px;
margin-top: 50px;
/*For IE9 compatibility*/
behavior: url(border-radius.htc);
border-radius: 8px;
position:relative;
}
#popup {
bottom:50%;
position:relative;
margin: 0px;
left:30%;
}
#msgPuzzleType{ margin-left:60px; }
img{
behavior: url(border-radius.htc);
border-radius: 8px;
position:relative;
}
#menuPuzzleType{
background-color:#999999;
border:solid 1px #3300CC;
height:200px;
}
How can I move two cubes into the article container tag?
A "Three.js" solution to your problem is to do this:
renderer2.setSize( 400, 150 );
You also have hardwired the aspect ratio of your PerspectiveCamera, so you need to make it match the canvas dimensions.
var camera2 = new THREE.PerspectiveCamera( 50, 400/150, 1, 1000 );
This will make the canvas smaller so it fits. You then can move the camera closer so your objects are of the size you want.
Using your link provided, i changed the following to put the cubes into the article:
canvas {
position: absolute;
top: -80px;
}

Resize HTML5 canvas to fit window

How can I automatically scale the HTML5 <canvas> element to fit the page?
For example, I can get a <div> to scale by setting the height and width properties to 100%, but a <canvas> won't scale, will it?
I believe I have found an elegant solution to this:
JavaScript
/* important! for alignment, you should make things
* relative to the canvas' current width/height.
*/
function draw() {
var ctx = (a canvas context);
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
//...drawing code...
}
CSS
html, body {
width: 100%;
height: 100%;
margin: 0;
}
Hasn't had any large negative performance impact for me, so far.
The following solution worked for me the best. Since I'm relatively new to coding, I like to have visual confirmation that something is working the way I expect it to. I found it at the following site:
http://htmlcheats.com/html/resize-the-html5-canvas-dyamically/
Here's the code:
(function() {
var
// Obtain a reference to the canvas element using its id.
htmlCanvas = document.getElementById('c'),
// Obtain a graphics context on the canvas element for drawing.
context = htmlCanvas.getContext('2d');
// Start listening to resize events and draw canvas.
initialize();
function initialize() {
// Register an event listener to call the resizeCanvas() function
// each time the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas. In this case it's a blue, 5 pixel
// border that resizes along with the browser window.
function redraw() {
context.strokeStyle = 'blue';
context.lineWidth = '5';
context.strokeRect(0, 0, window.innerWidth, window.innerHeight);
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas() {
htmlCanvas.width = window.innerWidth;
htmlCanvas.height = window.innerHeight;
redraw();
}
})();
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
/* Disable scrollbars */
display: block;
/* No floating content on sides */
}
<canvas id='c' style='position:absolute; left:0px; top:0px;'></canvas>
The blue border shows you the edge of the resizing canvas, and is always along the edge of the window, visible on all 4 sides, which was NOT the case for some of the other above answers. Hope it helps.
Basically what you have to do is to bind the onresize event to your body, once you catch the event you just need to resize the canvas using window.innerWidth and window.innerHeight.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Canvas Resize</title>
<script type="text/javascript">
function resize_canvas(){
canvas = document.getElementById("canvas");
if (canvas.width < window.innerWidth)
{
canvas.width = window.innerWidth;
}
if (canvas.height < window.innerHeight)
{
canvas.height = window.innerHeight;
}
}
</script>
</head>
<body onresize="resize_canvas()">
<canvas id="canvas">Your browser doesn't support canvas</canvas>
</body>
</html>
Setting the canvas coordinate space width and height based on the browser client's dimensions requires you to resize and redraw whenever the browser is resized.
A less convoluted solution is to maintain the drawable dimensions in Javascript variables, but set the canvas dimensions based on the screen.width, screen.height dimensions. Use CSS to fit:
#containingDiv {
overflow: hidden;
}
#myCanvas {
position: absolute;
top: 0px;
left: 0px;
}
The browser window generally won't ever be larger than the screen itself (except where the screen resolution is misreported, as it could be with non-matching dual monitors), so the background won't show and pixel proportions won't vary. The canvas pixels will be directly proportional to the screen resolution unless you use CSS to scale the canvas.
A pure CSS approach adding to solution of #jerseyboy above.
Works in Firefox (tested in v29), Chrome (tested in v34) and Internet Explorer (tested in v11).
<!DOCTYPE html>
<html>
<head>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
canvas {
background-color: #ccc;
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.strokeRect(50,50,50,50);
}
</script>
</body>
</html>
Link to the example: http://temporaer.net/open/so/140502_canvas-fit-to-window.html
But take care, as #jerseyboy states in his comment:
Rescaling canvas with CSS is troublesome. At least on Chrome and
Safari, mouse/touch event positions will not correspond 1:1 with
canvas pixel positions, and you'll have to transform the coordinate
systems.
function resize() {
var canvas = document.getElementById('game');
var canvasRatio = canvas.height / canvas.width;
var windowRatio = window.innerHeight / window.innerWidth;
var width;
var height;
if (windowRatio < canvasRatio) {
height = window.innerHeight;
width = height / canvasRatio;
} else {
width = window.innerWidth;
height = width * canvasRatio;
}
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
};
window.addEventListener('resize', resize, false);
Set initial size.
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Update size on window resize.
function windowResize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.addEventListener('resize', windowResize);
2022 answer
The recommended way in 2022 to check if an element resized is to use ResizeObserver
const observer = new ResizeObserver(myResizeTheCanvasFn);
observer.observe(someCanvasElement);
It's better than window.addEventListener('resize', myResizeTheCanvasFn) or onresize = myResizeTheCanvasFn because it handles EVERY case of the canvas resizing, even when it's not related to the window resizing.
Similarly it makes no sense to use window.innerWidth, window.innerHeight. You want the size of the canvas itself, not the size of the window. That way, no matter where you put the canvas you'll get the correct size for the situation and won't have to re-write your sizing code.
As for getting the canvas to fill the window
html, body {
height: 100%;
}
canvas {
width: 100%;
height: 100%;
display: block; /* this is IMPORTANT! */
}
The reason you need display: block is because by default the canvas is inline which means it includes extra space at the end. Without display: block you'll get a scrollbar. Many people fix the scrollbar issue by adding overflow: hidden to the body of the document but that's just hiding the fact that the canvas's CSS was not set correctly. It's better to fix the bug (set the canvas to display: block than to hide the bug with overflow: hidden
Full example
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const observer = new ResizeObserver((entries) => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
});
observer.observe(canvas)
// not import but draw something just to showcase
const hsla = (h, s, l, a) => `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, ${a})`;
function render(time) {
const {width, height} = canvas;
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate(time * 0.0001);
const range = Math.max(width, height) * 0.8;
const size = 64 + Math.sin(time * 0.001) * 50;
for (let i = 0; i < range; i += size) {
ctx.fillStyle = hsla(i / range * 0.3 + time * 0.0001, 1, 0.5, 1);
ctx.fillRect( i, -range, size, range * 2);
ctx.fillRect(-i, -range, size, range * 2);
}
ctx.restore();
requestAnimationFrame(render)
}
requestAnimationFrame(render)
html, body {
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<canvas></canvas>
Note: there are other issues related to resizing the canvas. Specifically if you want to deal with different devicePixelRatio settings. See this article for more.
Unless you want the canvas to upscale your image data automatically (that's what James Black's answer talks about, but it won't look pretty), you have to resize it yourself and redraw the image. Centering a canvas
If your div completely filled the webpage then you can fill up that div and so have a canvas that fills up the div.
You may find this interesting, as you may need to use a css to use percentage, but, it depends on which browser you are using, and how much it is in agreement with the spec:
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#the-canvas-element
The intrinsic dimensions of the canvas
element equal the size of the
coordinate space, with the numbers
interpreted in CSS pixels. However,
the element can be sized arbitrarily
by a style sheet. During rendering,
the image is scaled to fit this layout
size.
You may need to get the offsetWidth and height of the div, or get the window height/width and set that as the pixel value.
CSS
body { margin: 0; }
canvas { display: block; }
JavaScript
window.addEventListener("load", function()
{
var canvas = document.createElement('canvas'); document.body.appendChild(canvas);
var context = canvas.getContext('2d');
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(0, 0); context.lineTo(canvas.width, canvas.height);
context.moveTo(canvas.width, 0); context.lineTo(0, canvas.height);
context.stroke();
}
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
draw();
}
window.addEventListener("resize", resize);
resize();
});
If you're interested in preserving aspect ratios and doing so in pure CSS (given the aspect ratio) you can do something like below. The key is the padding-bottom on the ::content element that sizes the container element. This is sized relative to its parent's width, which is 100% by default. The ratio specified here has to match up with the ratio of the sizes on the canvas element.
// Javascript
var canvas = document.querySelector('canvas'),
context = canvas.getContext('2d');
context.fillStyle = '#ff0000';
context.fillRect(500, 200, 200, 200);
context.fillStyle = '#000000';
context.font = '30px serif';
context.fillText('This is some text that should not be distorted, just scaled', 10, 40);
/*CSS*/
.container {
position: relative;
background-color: green;
}
.container::after {
content: ' ';
display: block;
padding: 0 0 50%;
}
.wrapper {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
}
canvas {
width: 100%;
height: 100%;
}
<!-- HTML -->
<div class=container>
<div class=wrapper>
<canvas width=1200 height=600></canvas>
</div>
</div>
Using jQuery you can track the window resize and change the width of your canvas using jQuery as well.
Something like that
$( window ).resize(function() {
$("#myCanvas").width($( window ).width())
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;">
Here's a tiny, complete Code Snippet that combines all the answers. Press: "Run Code Snippet" then press "Full Page" and resize the window to see it in action:
function refresh(referenceWidth, referenceHeight, drawFunction) {
const myCanvas = document.getElementById("myCanvas");
myCanvas.width = myCanvas.clientWidth;
myCanvas.height = myCanvas.clientHeight;
const ratio = Math.min(
myCanvas.width / referenceWidth,
myCanvas.height / referenceHeight
);
const ctx = myCanvas.getContext("2d");
ctx.scale(ratio, ratio);
drawFunction(ctx, ratio);
window.requestAnimationFrame(() => {
refresh(referenceWidth, referenceHeight, drawFunction);
});
}
//100, 100 is the "reference" size. Choose whatever you want.
refresh(100, 100, (ctx, ratio) => {
//Custom drawing code! Draw whatever you want here.
const referenceLineWidth = 1;
ctx.lineWidth = referenceLineWidth / ratio;
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.arc(50, 50, 49, 0, 2 * Math.PI);
ctx.stroke();
});
div {
width: 90vw;
height: 90vh;
}
canvas {
width: 100%;
height: 100%;
object-fit: contain;
}
<div>
<canvas id="myCanvas"></canvas>
</div>
This snippet uses canvas.clientWidth and canvas.clientHeight rather than window.innerWidth and window.innerHeight to make the snippet run inside a complex layout correctly. However, it works for full window too if you just put it in a div that uses full window. It's more flexible this way.
The snippet uses the newish window.requestAnimationFrame to repeatedly resize the canvas every frame. If you can't use this, use setTimeout instead. Also, this is inefficient. To make it more efficient, store the clientWidth and clientHeight and only recalculate and redraw when clientWidth and clientHeight change.
The idea of a "reference" resolution lets you write all of your draw commands using one resolution... and it will automatically adjust to the client size without you having to change the drawing code.
The snippet is self explanatory, but if you prefer it explained in English: https://medium.com/#doomgoober/resizing-canvas-vector-graphics-without-aliasing-7a1f9e684e4d
A bare minimum setup
HTML
<canvas></canvas>
CSS
html,
body {
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
JavaScript
const canvas = document.querySelector('canvas');
const resizeObserver = new ResizeObserver(() => {
canvas.width = Math.round(canvas.clientWidth * devicePixelRatio);
canvas.height = Math.round(canvas.clientHeight * devicePixelRatio);
});
resizeObserver.observe(canvas);
For WebGL
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
const resizeObserver = new ResizeObserver(() => {
canvas.width = Math.round(canvas.clientWidth * devicePixelRatio);
canvas.height = Math.round(canvas.clientHeight * devicePixelRatio);
gl.viewport(0, 0, canvas.width, canvas.height);
});
resizeObserver.observe(canvas);
Notice that we should take device pixel ratio into account, especially for HD-DPI display.
I think this is what should we exactly do: http://www.html5rocks.com/en/tutorials/casestudies/gopherwoord-studios-resizing-html5-games/
function resizeGame() {
var gameArea = document.getElementById('gameArea');
var widthToHeight = 4 / 3;
var newWidth = window.innerWidth;
var newHeight = window.innerHeight;
var newWidthToHeight = newWidth / newHeight;
if (newWidthToHeight > widthToHeight) {
newWidth = newHeight * widthToHeight;
gameArea.style.height = newHeight + 'px';
gameArea.style.width = newWidth + 'px';
} else {
newHeight = newWidth / widthToHeight;
gameArea.style.width = newWidth + 'px';
gameArea.style.height = newHeight + 'px';
}
gameArea.style.marginTop = (-newHeight / 2) + 'px';
gameArea.style.marginLeft = (-newWidth / 2) + 'px';
var gameCanvas = document.getElementById('gameCanvas');
gameCanvas.width = newWidth;
gameCanvas.height = newHeight;
}
window.addEventListener('resize', resizeGame, false);
window.addEventListener('orientationchange', resizeGame, false);
(function() {
// get viewport size
getViewportSize = function() {
return {
height: window.innerHeight,
width: window.innerWidth
};
};
// update canvas size
updateSizes = function() {
var viewportSize = getViewportSize();
$('#myCanvas').width(viewportSize.width).height(viewportSize.height);
$('#myCanvas').attr('width', viewportSize.width).attr('height', viewportSize.height);
};
// run on load
updateSizes();
// handle window resizing
$(window).on('resize', function() {
updateSizes();
});
}());
This worked for me.
Pseudocode:
// screen width and height
scr = {w:document.documentElement.clientWidth,h:document.documentElement.clientHeight}
canvas.width = scr.w
canvas.height = scr.h
Also, like devyn said, you can replace "document.documentElement.client" with "inner" for both the width and height:
**document.documentElement.client**Width
**inner**Width
**document.documentElement.client**Height
**inner**Height
and it still works.

Categories