I've been trying to make a parallax-esque effect using HTML5 canvas, for various reasons. I've been using the window.scrollY property to determine how far down the user has scrolled and therefore can calculate the transformations using this value.
I suppose this is best explained through an example:
function draw() {
scrollOffset = window.scrollY;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#000";
ctx.fillRect((width/2) - (size/2), ((height/2) - (size/2)) + scrollOffset, size, size);
requestAnimationFrame(draw);
}
As you can see, depending on what browser you use, there will be a varying level of "glitchiness" caused by you scrolling. The square in the center should remain in the same place the entire time, however there are issues with keeping up with how far the user has scrolled.
How noticeable this problem is depends on what browser you are using; Chrome is only noticeable if you change your scrolling direction in rapid succession whereas Firefox and Edge are noticeable regardless of how slowly you scroll.
Would there be a better way to go about this?
Note: Not a solution but a suggestion
The parallax should happen only when one scrolls on the page. Should be something like the snippet below:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let width;
let height;
window.addEventListener("resize", resizeCanvas);
function resizeCanvas() {
width = canvas.width = document.documentElement.clientWidth;
height = canvas.height = document.documentElement.clientHeight;
}
resizeCanvas();
let scrollOffset;
let size = 50;
function draw() {
scrollOffset = window.scrollY;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#000";
ctx.fillRect((width/2) - (size/2), ((height/2) - (size/2)) + scrollOffset, size, size);
}
draw();
window.onscroll = function (e)
{
draw();
}
html, body {
margin: 0;
}
canvas {
display: block;
}
#padder {
height: 100vh;
background-color: #F00;
}
<canvas id="canvas"></canvas>
<section id="padder"></section>
Also, since it's browser specific, for Firefox, check scrolling options in the advanced settings or you shall try changing the mouseWheel scrolling behavior in about:config
Related
I'm quite new to svelte and I'm trying to get a canvas to render on the full screen using svelte. Sounds quite easy to do, but I can't get it to work properly. I'm binding a width and height variable to the clientWidth/clientHeight of the parent and using these variables to set the dimensions of the canvas. The issue now is that when onMount is called, the width and height variables are set but they are not applied to the canvas element yet. This means when the canvas renders for the first time, it still has the initial dimensions and not the ones of the parent. Only when I render it a second time it has the proper dimensions. How can get the canvas to have the right dimensions on the first render or render the canvas again when it has the proper dimensions?
Here you can find a "working" version.
<script>
import { onMount } from "svelte";
let canvas;
let ctx;
let width = 1007;
let height = 1140;
const draw = () => {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.moveTo(width/2 - 50, height/2);
ctx.arc(width/2, height/2, 50, 0, 2 * Math.PI);
ctx.fill();
}
onMount(() => {
ctx = canvas.getContext("2d");
draw();
setTimeout(draw, 5000);
});
</script>
<style>
.container {
width: 100%;
height: 100%;
}
</style>
<div
class="container"
bind:clientWidth={width}
bind:clientHeight={height}>
<canvas bind:this={canvas} {width} {height} />
</div>
You can solve this by waiting for the next 'tick'
import { onMount, tick } from 'svelte';
onMount(async () => {
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
await tick()
draw();
});
What await tick() does is to effectively pause execution until all current changes have been applied to the dom
tick in the docs
i was testing some code with javascript, about resizing the html5 canvas. i've asked similar question before, when i was testing the same thing with jquery. and i've got the desired solution that time, reference (eg_1). in this (eg_1), the result was, canvas element was resizing in every frame because of requestAnimationFrame($.fn.animate); is taking place in a recursive form.the visual result for that is here the width attribute updates when i resize the console window. and if i close the console the canvas resizes automatically, i don't need to reload the window, to update the attribute values. means canvas is full screen, all time. but i am having problem with the same thing, in javascript. if i close the console, the width of the console cuts (or resizes) the canvas with a white space. here is the visual result the white space is my problem. i've to reload the window, to make the canvas full screen again. in short i'm not having the same effect as (eg_1). here i tried so far, reference (eg_2).
(eg_1)
html : <canvas></canvas>
css :
* {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
background-color: turquoise;
}
jquery :
$(document).ready(function() {
var view = $(window),
canvas = $("canvas"),
ctx = canvas[0].getContext("2d"),
width = view.width(),
height = view.height();
$.fn.windowResize = function() {
var width = view.width();
height = view.height();
canvas.attr("width", width);
canvas.attr("height", height);
};
view.on("resize", $.fn.windowResize);
canvas.attr("width", width);
canvas.attr("height", height);
$.fn.animate = function() {
requestAnimationFrame($.fn.animate);
ctx.clearRect(0, 0, width, height);
// start here
};
$.fn.animate();
});
(eg_2)
var canvas = document.querySelectorAll("canvas")[0];
var context = canvas.getContext("2d");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
function resize() {
setInterval(resize, 5);
clearInterval(resize);
canvas.width = windowWidth;
canvas.height = windowHeight;
}
resize();
Answer:
Your initial issue:
You need to get the updated values of window.innerWidth and window.innerHeight.
To do this you can turn the variables into functions that request the current values.
Example:
var canvas = document.querySelectorAll("canvas")[0];
var context = canvas.getContext("2d");
var windowWidth = () => window.innerWidth;
var windowHeight = () => window.innerHeight;
function resize() {
setInterval(resize, 5);
clearInterval(resize);
canvas.width = windowWidth();
canvas.height = windowHeight();
}
resize();
* {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
background-color: turquoise;
}
<canvas></canvas>
Other issues:
Your use of Timers is incorrect. clearInterval(resize) isn't going to clear anything, because resize is not a timer itself.
Secondly you shouldn't be using any timers when rendering to Canvas. It can often lead to things like the above( multiple timers ) or issues with constant, unnecessary, re-processing.
Instead of resizing perpetually you may want to just do so when the resize event is dispatched. Even if you were using requestAnimationFrame - which would effectively stop the render if anything else takes precedence or if you switch tabs, it's an unnecessary process in that you're constantly re-rendering the same thing without cause. Canvas Apps tend to get big real quick when you add in multiple rendering functions, so you'll want to mitigate unnecessary steps.
You can do this by attaching to appropriate events and creating requestAnimationFrame helpers like in the following example:
var canvas = document.querySelectorAll("canvas")[0];
var context = canvas.getContext("2d");
var windowWidth = () => window.innerWidth;
var windowHeight = () => window.innerHeight;
var request = fn => requestAnimationFrame(fn),
requestHandler = fn => () => request(fn);
function resize() {
canvas.width = windowWidth();
canvas.height = windowHeight();
}
request(resize);
window.addEventListener("resize", requestHandler(resize) );
* {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
background-color: turquoise;
}
<canvas></canvas>
I've made a little web game, I'm using this code below to scale the game to the page size.
var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
// Attempt at auto-resize
function resize_canvas(){
if (canvas.width != window.innerWidth)
{
canvas.width = window.innerWidth;
}
if (canvas.height != window.innerHeight)
{
canvas.height = window.innerHeight;
}
}
window.addEventListener("resize", resize_canvas);
window.addEventListener("orientationchange", resize_canvas);
However, I'm now adding a menu in the top of the game using some CSS and I'm running into problems.
The game takes the size of the inner window, so anything displayed along with the game will cause the content not to fit into the browser.
How can I make the game "fill" the rest of the window?
(I've found questions on how to scale the game to the window, but I'd like the game to fill the window instead)
An image to demonstrate the problem below:
(Notice the scrollbars, which I'd like to eliminate)
Edit:
I'm quite new to web development, but I have the feeling CSS doesn't really work well on canvas.
And if you remove the nav height ?
var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");
var menu = document.getElementById('nav')
resize_canvas();
document.body.appendChild(canvas);
// Attempt at auto-resize
function resize_canvas(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight - menu.offsetHeight;
}
window.addEventListener("resize", resize_canvas);
window.addEventListener("orientationchange", resize_canvas);
and
canvas {
position:relative;
}
How do I keep the canvas in the this demo centered at all times (including when the scrollbars appear) when the window is resized? The canvas element should always fill the window.
I tried playing around with settings like left, margin-left, etc. (including negative values) but to no avail.
Is this what you wanted?
This is a refactored version of your code that resizes the canvas to the smallest dimension of the window (width or height) on window resize. It also sets the display of the canvas to block and uses margin: 0 auto to center it.
Here is the demo.
UPDATE 1 I've refactored with comments to show where to change the code to resize the canvas to the largest dimension between the window height and width.
UPDATE 2 I've refactored to fit the canvas so the crosshairs are always centered by setting the width and height to the exact window.
I've also updated the CSS to remove all padding and margins.
The canvas is redrawn on window.onresize.
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
function setSize() {
var w = window.innerWidth;
var h = window.innerHeight;
//var size = (h > w) ? h : w; //<<<< resize to largest between height and width
//var size = (h < w) ? h : w; //<<<< resize to smallest between height and width
//canvas.width = size;
//canvas.height = size;
canvas.width = w; //<<<< exact resizing to width
canvas.height = h; //<<<< exact resizing to height
}
function draw() {
var context = canvas.getContext('2d');
context.fillRect(0, 0, canvas.width, canvas.height);
context.strokeStyle = 'red';
context.moveTo(canvas.width/2, 0);
context.lineTo(canvas.width/2, canvas.height);
context.moveTo(0, canvas.height/2);
context.lineTo(canvas.width, canvas.height/2);
context.stroke();
}
setSize();
draw();
window.onresize = function(e) {
setSize();
draw();
};
* {
padding: 0;
margin: 0;
}
canvas {
display: block;
margin: 0 auto;
}
Did you try overflow: hidden;?
JSFiddle
body {
overflow: hidden;
}
You can do something like
canvas{
position: fixed;
top: -500px;
}
To make the canvas resize with the page, try adding a window resize listener:
window.addEventListener("resize", function () {
canvas.width = window.innerWidth;
canvas.width = window.innerHeight;
});
If you want to remove margins so the canvas fills the entire page, try adding the following CSS to your page:
body {
margin: 0 !important;
padding: 0 !important;
}
After much fiddling, I think I figured it out. Here's the working demo (fullscreen version). I ended up using the following loop:
function render() {
requestAnimationFrame(render);
window.scrollTo(
document.documentElement.scrollWidth/2 - window.innerWidth/2,
document.documentElement.scrollHeight/2 - window.innerHeight/2
);
}
requestAnimationFrame(render);
which automatically scrolls the window to the middle of the entire scrollable area (including the non-visible portions) minus half the size of the viewing area itself.
Thanks to everyone for your help!
I am not sure, how i can be more specific, but i am putting my best effort to explain it.I am trying to understand, what should i be looking for to be more specific, so this is all i have got at this moment.
I was exploring HTML5 JavaScript games, and I noticed, that making the canvas height and width 100% distorts the animation within.
a simple example that i got from here.
If i change the canvas size to 100% (in the example I have provided) it breaks the animation of the game(for example the asteroid).
I would like to know, which property of HTML5 is responsible for this behaviour and what should I be looking for to get the HTML5 animation fit the complete screen size?
EDIT
I tried to run cordova to build the game to native platform,but the 1st problem i am encountering is that the canvas was not fitting the screen size. (that's why i wanted it to completely fit the browser screen, but i see a complete misfit when a canvas made for the browser screen is rendered to the native using cordova).
I explored phaser, about how they are solving this problem, and found this game which is using something called a ScalingManager.
So, my questions are
1. What is scaling a Game?
2. How is the scaling-manager of phaser working
3. without using the scaling manager why will a game not fit the moile screen size even though the canvas height and width are properly mentioned?
4. is there a small experment (without using any phaser or similar javascript game framework) that i can do to understand the need for scaling with simple HTML5 javasctipt and cordova?
A canvas has two distinct sizes:
the size of the element on the page
the size in pixel of the canvas
To avoid distortion and get a pixel-perfect graphic you need to ensure they end up equal... for example:
function redraw() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
...
}
For single-page HTML games where you just want to draw everything in a canvas a simple approach is:
<!DOCTYPE html>
<html>
<head>
<style type='text/css'>
* {
margin: 0;
padding: 0;
border: none;
}
body,html {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<script>
var canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
var body = document.body;
body.insertBefore(canvas, body.firstChild);
var context = canvas.getContext('2d');
var devicePixelRatio = window.devicePixelRatio || 1;
var backingStoreRatio = context.webkitBackingStorePixelRatio
|| context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio
|| context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1;
var ratio = devicePixelRatio / backingStoreRatio;
redraw();
window.addEventListener('resize', redraw, false);
window.addEventListener('orientationchange', redraw, false);
function redraw() {
width = (window.innerWidth > 0 ? window.innerWidth : screen.width);
height = (window.innerHeight > 0 ? window.innerHeight : screen.height);
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
width *= ratio;
height *= ratio;
if (canvas.width === width && canvas.height === height) {
return;
}
canvas.width = width;
canvas.height = height;
}
//draw things
</script>
</body>
</html>
You will also need to check for changes in innerWidth / innerHeight if your app has parts in which you're just waiting for user interaction (not looping on calling redraw) and the user instead resizes the browser window or tilts the phone/tab.
With Phaser 3, I succeeded by adding window.innerWidthand window.innerHeightto the Scale object into my config :
config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
scale: {
mode: Phaser.Scale.FIT,
parent: 'phaser-game',
autoCenter: Phaser.Scale.CENTER_BOTH,
width: window.innerWidth,
height: window.innerHeight
},
scene: [MainScene],
parent: 'gameContainer',
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0}
}
}
};
For the moment I don't see any problem, it makes the trick.