I have a function that, when called, clears the canvas.
function ClearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
The problem I'm having is that when I try to draw something onto the canvas again using fillRect() the items I want to draw appear on the bottom of the canvas, only a few of them showing up. The second time I try, nothing shows up.
To see my full code and a test run, go here
var width = 50;
var height = 50;
var interpolate = d3.interpolate('white', 'black');
var elevation = [];
var colormap = [];
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
var rectY = 0;
Generate2DArray(elevation, 0, width, height);
Generate2DArray(colormap, 0, width, height);
var gen = new SimplexNoise();
function Generate2DArray(EmptyArray, fill, width, height) {
for(var i = 0; i < height; i++) {
EmptyArray.push([]);
for(var j = 0; j < width; j++) {
EmptyArray[i][j] = fill;
}
}
}
function noise(nx, ny) {
// Rescale from -1.0:+1.0 to 0.0:1.0
return gen.noise2D(nx, ny) / 2 + 0.5;
}
function ClearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function GenTerrain() {
for(var y = 0; y < height; y++) {
for(var x = 0; x < width; x++) {
var nx = x/width - 0.5, ny = y/height - 0.5;
elevation[y][x] = noise(nx * 2.57, ny * 2.57);
colormap[y][x] = interpolate(elevation[y][x]);
ctx.fillStyle = colormap[y][x];
ctx.fillRect(x*10, y+rectY, 10, 10);
}
rectY += 9
}
}
Your rectY is being declared at the top of your code, at the global level:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
var rectY = 0;
So, every time GenTerrain runs, it references that variable and adds to it:
rectY += 9
Fix it by encapsulating rectY inside GenTerrain so it starts at 0 each time the function is called.
function GenTerrain() {
var rectY = 0;
for(var y = 0; y < height; y++) {
for(var x = 0; x < width; x++) {
var nx = x/width - 0.5, ny = y/height - 0.5;
elevation[y][x] = noise(nx * 2.57, ny * 2.57);
colormap[y][x] = interpolate(elevation[y][x]);
ctx.fillStyle = colormap[y][x];
ctx.fillRect(x*10, y+rectY, 10, 10);
}
rectY += 9
}
}
Related
I have Matrix canvas on my site and there is problem with responsiveness when I change the width of the viewport the canvas doesn't update to fit.
https://codepen.io/ems-sin/pen/zYjpree
HTML
<canvas id="bglitch" ></canvas>
CSS
*{
margin:0;
background-color:#010b13;
overflow: hidden;
}
#bglitch{
opacity: 0.5;
z-index: -10;
top: 0;
right:0;
}
Javascript
var bglitch = document.getElementById("bglitch");
var ctx = bglitch.getContext("2d");
bglitch.height = window.innerHeight;
bglitch.width = window.innerWidth;
var matrix = "アィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽ";
matrix = matrix.split("");
var font_size = 24;
var columns = bglitch.width/font_size;
var drops = [];
for(var x = 0; x < columns; x++)
drops[x] = 1;
function draw()
{
ctx.fillStyle = "rgba(0, 0, 0, 0.04)";
ctx.fillRect(0, 0, bglitch.width, bglitch.height);
ctx.fillStyle = "#f4427d";
ctx.font = font_size + "px arial";
for(var i = 0; i < drops.length; i++){
var text = matrix[Math.floor(Math.random()*matrix.length)];
ctx.fillText(text, i*font_size, drops[i]*font_size);
if(drops[i]*font_size > bglitch.height && Math.random() > 0.975)
drops[i] = 0;
drops[i]++;
}
}
setInterval(draw, 35);
You need to make your code into a function which gets called on the resize event so that the dimensions are updated.
This snippet also clears the current timing interval (if there is one) before starting another one.
<style>
<canvas id="bglitch"></canvas>
</style>
<canvas id="bglitch"></canvas>
<script>
var bglitch = document.getElementById("bglitch");
var ctx = bglitch.getContext("2d");
let timer = '';
function init() {
bglitch.height = window.innerHeight;
bglitch.width = window.innerWidth;
var matrix = "アィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽ";
matrix = matrix.split("");
var font_size = 24;
var columns = bglitch.width / font_size;
var drops = [];
for (var x = 0; x < columns; x++)
drops[x] = 1;
function draw() {
ctx.fillStyle = "rgba(0, 0, 0, 0.04)";
ctx.fillRect(0, 0, bglitch.width, bglitch.height);
ctx.fillStyle = "#f4427d";
ctx.font = font_size + "px arial";
for (var i = 0; i < drops.length; i++) {
var text = matrix[Math.floor(Math.random() * matrix.length)];
ctx.fillText(text, i * font_size, drops[i] * font_size);
if (drops[i] * font_size > bglitch.height && Math.random() > 0.975)
drops[i] = 0;
drops[i]++;
}
}
clearInterval(timer);
timer = setInterval(draw, 35);
}
window.onresize = init;
window.onload = init;
</script>
You need to add resize event listener and recalculate columns accordingly.
Add this at the end of your javascript
window.addEventListener('resize', () => {
bglitch.height = window.innerHeight;
bglitch.width = window.innerWidth;
var columns = bglitch.width/font_size;
for(var x = 0; x < columns; x++) drops[x] = 1;
})
Full working resize
var bglitch = document.getElementById("bglitch");
var ctx = bglitch.getContext("2d");
bglitch.height = window.innerHeight;
bglitch.width = window.innerWidth;
var matrix = "アィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽ";
matrix = matrix.split("");
var font_size = 24;
var columns = bglitch.width/font_size;
var drops = [];
for(var x = 0; x < columns; x++)
drops[x] = 1;
function draw()
{
ctx.fillStyle = "rgba(0, 0, 0, 0.04)";
ctx.fillRect(0, 0, bglitch.width, bglitch.height);
ctx.fillStyle = "#f4427d";
ctx.font = font_size + "px arial";
for(var i = 0; i < drops.length; i++){
var text = matrix[Math.floor(Math.random()*matrix.length)];
ctx.fillText(text, i*font_size, drops[i]*font_size);
if(drops[i]*font_size > bglitch.height && Math.random() > 0.975)
drops[i] = 0;
drops[i]++;
}
}
setInterval(draw, 35);
window.addEventListener('resize', () => {
bglitch.height = window.innerHeight;
bglitch.width = window.innerWidth;
var columns = bglitch.width/font_size;
for(var x = 0; x < columns; x++) drops[x] = 1;
})
*{
margin:0;
background-color:#010b13;
overflow: hidden;
}
#bglitch{
opacity: 0.5;
z-index: -10;
top: 0;
right:0;
}
<canvas id="bglitch" ></canvas>
It's better practice to separate your code into functions. For example init() function that sets all initial variables and run function that will run setTimeout and so on.
let bgData = {
element: undefined,
g: undefined,
matrix: "アィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽ".split(''),
fontSize: 24,
columns: undefined,
drops: [],
};
function init() {
bgData.element = document.getElementById("bglitch");
bgData.g = bgData.element.getContext("2d");
resize();
window.addEventListener('resize', resize)
}
function resize() {
bgData.element.height = window.innerHeight;
bgData.element.width = window.innerWidth;
bgData.columns = bgData.element.width / bgData.fontSize;
for(let x = 0; x < bgData.columns; x++) bgData.drops[x] = 1;
}
function draw() {
bgData.g.fillStyle = "rgba(0, 0, 0, 0.04)";
bgData.g.fillRect(0, 0, bgData.element.width, bgData.element.height);
bgData.g.fillStyle = "#f4427d";
bgData.g.font = bgData.fontSize + "px arial";
for(let i = 0; i < bgData.drops.length; i++) {
const text = bgData.matrix[Math.floor(Math.random()*bgData.matrix.length)];
bgData.g.fillText(text, i * bgData.fontSize, bgData.drops[i] * bgData.fontSize);
if(bgData.drops[i] * bgData.fontSize > bgData.element.height && Math.random() > 0.975) bgData.drops[i] = 0;
bgData.drops[i]++;
}
}
let interval;
function stop() {
clearInterval(interval);
window.removeEventListener('resize', resize)
}
function run() {
stop();
init();
interval = setInterval(draw, 35);
}
run();
*{
margin:0;
background-color:#010b13;
overflow: hidden;
}
#bglitch{
opacity: 0.5;
z-index: -10;
top: 0;
right:0;
}
<canvas id="bglitch" ></canvas>
This was narrowed down from a much larger script, but you can see that drawing a gradient based on the x value takes much more time than just passing it value 255.
Why should this matter? In either case, they are both drawing one pixel at a time, and they are both doing the work to derive the value of x.
By the way, my goal is not to find a faster way of drawing gradients.
var canvas = document.getElementById('canvas')
canvas.width = 600
canvas.height = 600
var ctx = canvas.getContext('2d')
init();
function init()
{
requestAnimationFrame(draw)
}
function draw()
{
const t0 = performance.now();
for(x = 0; x < canvas.width; x++)
{
for(y = 0; y < canvas.height; y++)
{
setPixelWhiteColor(x,x,y)
//setPixelWhiteColor(255,x,y) // <- faster?
}
}
const t1 = performance.now();
document.getElementById("debug1").innerHTML = t1 - t0
requestAnimationFrame(draw)
}
function setPixelWhiteColor(w,x,y)
{
ctx.fillStyle = "rgb("+w+","+w+","+w+")";
ctx.fillRect( x, y, 1, 1 );
}
<canvas id="canvas"></canvas>
<div id="debug1"></div>
I had to work with raw image data using putImageData like Wiktor said:
var canvas = document.getElementById('canvas')
canvas.width = 600
canvas.height = 600
var ctx = canvas.getContext('2d')
var id = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = id.data;
init();
function init()
{
requestAnimationFrame(draw)
}
function draw()
{
const t0 = performance.now();
for(x = 0; x < canvas.width; x++)
{
for(y = 0; y < canvas.height; y++)
{
var w = x
if(w > 255){
w = 255
}
setPixelWhiteColor(w,x,y)
}
}
putImageData()
const t1 = performance.now();
document.getElementById("debug1").innerHTML = t1 - t0
requestAnimationFrame(draw)
}
function setPixelWhiteColor(w,x,y)
{
var r = w
var g = w
var b = w
var off = (y * id.width + x) * 4;
pixels[off] = r;
pixels[off + 1] = g;
pixels[off + 2] = b;
pixels[off + 3] = 255;
}
function putImageData(){
ctx.putImageData(id, 0, 0);
}
<html>
<body>
<canvas id="canvas"></canvas>
<div id="debug1"></div>
<div id="debug2"></div>
<script>
</script>
</body>
</html>
Could someone tell me what's wrong with this code ? I am trying to fill canvas with squares as objects but as the loop is done and i am trying to draw that square on canvas nothing happens...
var canvas = document.getElementById('c');
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(35, 180, 218)";
var rectHeight = 5;
var rectWidth = 5;
var cells = [];
for (var i = 0; i <= canvas.width/rectWidth; i++) {
for (var x = 0; x <= canvas.height/rectHeight; x++) {
cells[i] = {
posX : i*rectWidth,
posY : x*rectHeight,
draw : function() {
ctx.fillRect(posX, posY, rectWidth, rectHeight);
},
clear : function() {
ctx.clearRect(posX, posY, rectWidth, rectHeight);
}
};
}
}
cells[2].draw;
var canvas = document.getElementById('c');
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
var rectHeight = 15;
var rectWidth = 15;
var cells = [];
for (var i = 0; i <= canvas.width/rectWidth; i++) {
for (var x = 0; x <= canvas.height/rectHeight; x++) {
cells[i] = {
posX : i*rectWidth,
posY : x*rectHeight,
draw : function() {
ctx.fillRect(this.posX, this.posY, rectWidth, rectHeight);
},
clear : function() {
ctx.clearRect(positionX, positionY, rectWidth, rectHeight);
}
};
}
}
cells[2].draw();
<canvas id="c" width="500" height="400"></canvas>
Thanks for the help #Xufox #SimranjitSingh and #Andreas..
Code now works like this:
var canvas = document.getElementById('c');
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(35, 180, 218)";
var rectHeight = 5;
var rectWidth = 5;
var cells = [];
for (var i = 0; i <= canvas.width/rectWidth; i++) {
for (var x = 0; x <= canvas.height/rectHeight; x++) {
cells[i*x] = {
posX : i*rectWidth,
posY : x*rectHeight,
draw : function() {
ctx.fillRect(this.posX, this.posY, rectWidth, rectHeight);
},
clear : function() {
ctx.clearRect(this.posX, this.posY, rectWidth, rectHeight);
}
};
}
}
cells[2].draw();
Goal
The stripes in the background remain fixed while the cones rotate about the center.
Current State
live demo:
https://codepen.io/WallyNally/pen/yamGYB
/*
The loop function is around line 79.
Uncomment it to start the animation.
*/
var c = document.getElementById('canv');
var ctx = c.getContext('2d');
var W = c.width = window.innerWidth;
var H = c.height = window.innerHeight;
var Line = function() {
this.ctx = ctx;
this.startX = 0;
this.startY = 0;
this.endX = 0;
this.endY = 0;
this.direction = 0;
this.color = 'blue';
this.draw = function() {
this.ctx.beginPath();
this.ctx.lineWidth = .1;
this.ctx.strokeStlye = this.color;
this.ctx.moveTo(this.startX, this.startY);
this.ctx.lineTo(this.endX, this.endY);
this.ctx.closePath();
this.ctx.stroke();
}
this.update = function() {
//for fun
if (this.direction == 1) {
this.ctx.translate(W/2, H/2);
this.ctx.rotate(-Math.PI/(180));
}
}//this.update()
}//Line();
objects=[];
function initLines() {
for (var i =0; i < 200; i++) {
var line = new Line();
line.direction = (i % 2);
if (line.direction == 0) {
line.startX = 0;
line.startY = -H + i * H/100;
line.endX = W + line.startX;
line.endY = H + line.startY;
}
if (line.direction == 1) {
line.startX = 0;
line.startY = H - i * H/100;
line.endX = W - line.startX;
line.endY = H - line.startY;
}
objects.push(line);
line.draw();
}
}
initLines();
function render(c) {
c.clearRect(0, 0, W, H);
for (var i = 0; i < objects.length; i++)
{
objects[i].update();
objects[i].draw();
}
}
function loop() {
render(ctx);
window.requestAnimationFrame(loop);
}
//loop();
What I have tried
The translate(W/2, H/2) should place the context at the center of the page, then this.ctx.rotate(-Math.PI/(180)) should rotate it one degree at a time. This is the part that is not working.
Using save()and restore() is the proper way to keep some parts of an animation static while others move. I placed the save and restore in different parts of the code to no avail. There are one of two types of result : Either a new entirely static image is produced, or some erratic animation happens (in the same vein of where it is now).
Here is the changed pen: http://codepen.io/samcarlinone/pen/LRwqNg
You needed a couple of changes:
var c = document.getElementById('canv');
var ctx = c.getContext('2d');
var W = c.width = window.innerWidth;
var H = c.height = window.innerHeight;
var angle = 0;
var Line = function() {
this.ctx = ctx;
this.startX = 0;
this.startY = 0;
this.endX = 0;
this.endY = 0;
this.direction = 0;
this.color = 'blue';
this.draw = function() {
this.ctx.beginPath();
this.ctx.lineWidth = .1;
this.ctx.strokeStlye = this.color;
this.ctx.moveTo(this.startX, this.startY);
this.ctx.lineTo(this.endX, this.endY);
this.ctx.closePath();
this.ctx.stroke();
}
this.update = function() {
//for fun
if (this.direction == 1) {
this.ctx.translate(W/2, H/2);
this.ctx.rotate(angle);
this.ctx.translate(-W/2, -H/2);
}
}//this.update()
}//Line();
objects=[];
function initLines() {
for (var i =0; i < 200; i++) {
var line = new Line();
line.direction = (i % 2);
if (line.direction == 0) {
line.startX = 0;
line.startY = -H + i * H/100;
line.endX = W + line.startX;
line.endY = H + line.startY;
}
if (line.direction == 1) {
line.startX = 0;
line.startY = H - i * H/100;
line.endX = W - line.startX;
line.endY = H - line.startY;
}
objects.push(line);
line.draw();
}
}
initLines();
function render(c) {
c.clearRect(0, 0, W, H);
for (var i = 0; i < objects.length; i++)
{
ctx.save();
objects[i].update();
objects[i].draw();
ctx.restore();
}
}
function loop() {
render(ctx);
window.requestAnimationFrame(loop);
angle += Math.PI/360;
}
loop();
First I added a variable to keep track of rotation and increment it in the loop
Second I save and restore for each individual line, alternatively if all lines were going to perform the same transformation you could move that code before and after the drawing loop
Third to get the desired affect I translate so the center point is in the middle of the screen, then I rotate so that the lines are rotated, then I translate back because all the lines have coordinates on the interval [0, H]. Instead of translating back before drawing another option would be to use coordinates on the interval [-(H/2), (H/2)] etc.
I tried to look most of the suggested search results concerning the error message I receive, but unfortunately none is talking about a case that is similar to mine. So I believe this is not a duplicate.
(In particular I don't use jQuery, I don't want to use it. Besides, the accepted answer given is correct and it does not involve jQuery, it elaborates the understanding of hoisting in JavaScript.)
I wonder Why the code below (2nd code snippet) does not work? I can't figure that out.
This code works. I instantiate Cell objects outside the drawGrid function.
'use strict';
var dim = 20;
var side_length = 25;
var canvas = document.getElementById('world');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
drawGrid(ctx);
}
var Cell = function(x, y, alive, context) {
this.x = x;
this.y = y;
this.alive = alive;
this.ctx = context;
};
Cell.prototype.draw = function() {
if (this.alive === true) {
this.ctx.beginPath();
this.ctx.arc(this.x + side_length / 2, this.y + side_length / 2, 10, 0, 2 * Math.PI);
this.ctx.fill();
}
};
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
var x = i * canvas.width / dim,
y = j * canvas.height / dim;
new Cell(x, y, true, ctx).draw();
}
}
function drawGrid(ctx) {
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
var x = i * canvas.width / dim,
y = j * canvas.height / dim;
ctx.strokeRect(x, y, side_length, side_length);
}
}
}
<canvas id='world'></canvas>
This code does not work. I instantiate the Cell objects inside the drawGrid function.
'use strict';
var dim = 20;
var side_length = 25;
var canvas = document.getElementById('world');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
drawGrid(ctx);
}
var Cell = function(x, y, alive, context) {
this.x = x;
this.y = y;
this.alive = alive;
this.ctx = context;
};
Cell.prototype.draw = function() {
if (this.alive === true) {
this.ctx.beginPath();
this.ctx.arc(this.x + side_length / 2, this.y + side_length / 2, 10, 0, 2 * Math.PI);
this.ctx.fill();
}
};
function drawGrid(ctx) {
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
var x = i * canvas.width / dim,
y = j * canvas.height / dim;
ctx.strokeRect(x, y, side_length, side_length);
new Cell(x, y, true, ctx).draw();
}
}
}
<canvas id='world'></canvas>
The problem is here:
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
drawGrid(ctx);
}
var Cell = function(x, y, alive, context) {
You're assigning Cell after you call drawGrid. So, inside drawGrid, Cell is undefined.
Simple fix 1, use a standard function declaration to have the assignment hoisted along the variable declaration :
function Cell(x, y, alive, context) {
Simple (more readable) fix 2: just move the call to drawGrid at the end.