Hello I have been watching some tutorials on HTML canvas and animations and I wanted to create my own. I am having trouble though. :/
I am trying to create some clouds that start on the left side of the screen and move to the right side of the screen, and will eventually disappear when they get to a certain point. I don't have that code yet. I don't know how to handle transparency. But, that is not where my troubles lie.
Currently, my clouds do not move. I can generate 20 different clouds in different locations but they are failing to move. I have checked my code with other tutorials and I can not seem to find why it's not working. Maybe because I am using an image?? If I could find some help I would really appreciate it. Thank you.
$(function(){
var leftcloudsrc = "ls/pics/cloud1.png";
var rightcloudsrc = "ls/pics/cloud2.png";
var canvas = document.getElementById('cloud');
var cw = canvas.width;
var ch = canvas.height;
var cloudsArray = [];
createclouds();
function createclouds(){
for (var i=0; i < 20; i++){
var x = Math.random() * 150;
var y = Math.random() * 300;
var v = Math.random() * 4;
cloudsArray.push(new Cloud(x, y, v));
}
animate();
console.log(cloudsArray);
}
function animate(){
requestAnimationFrame(animate);
for (var i = 0; i<cloudsArray.length; i++){
cloudsArray[i].move();
//new Cloud(x, y, v).create();
}
}
function Cloud(x, y, v){
this.x = x;
this.y = y;
this.v = v;
this.create = function(){
img = new Image,
ctx = document.getElementById('cloud').getContext('2d');
img.src = leftcloudsrc;
var iw = img.naturalWidth;
var ih = img.naturalHeight;
ctx.drawImage(img, x, y);
}
this.move = function(){
this.x += this.v;
this.create();
}
}
// var cloud = new Cloud(0,0,0);
// cloud.create();
});
i have tried writing to the console to make sure the information is saving and sticking, and yes, it is. i have even tried writing the .move() function to console to make sure the data changes, and it does. but it does not reflect visually???
ctx.drawImage(img, x, y); // wrong
ctx.drawImage(img, this.x, this.y); // right
You arent updating x and y. You are updating this.x and this.y
full code
// test
var leftcloudsrc = "http://www.freepngimg.com/download/cloud/10-cloud-png-image.png";
var rightcloudsrc = "ls/pics/cloud2.png";
var canvas = document.getElementById('cloud');
// you dont have to define ctx again and again
var ctx = canvas.getContext('2d');
var cw = canvas.width;
var ch = canvas.height;
var cloudsArray = [];
createclouds();
function createclouds() {
for (var i = 0; i < 20; i++) {
var x = Math.random() * 150;
var y = Math.random() * 300;
var v = Math.random() * 4;
cloudsArray.push(new Cloud(x, y, v));
}
animate();
console.log(cloudsArray);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, cw, ch)
for (var i = 0; i < cloudsArray.length; i++) {
var c = cloudsArray[i]
c.move();
// remove when crosses the canvas width
if(c.x >= 500) {
cloudsArray.splice(i, 1);
}
//new Cloud(x, y, v).create();
}
}
function Cloud(x, y, v) {
this.x = x;
this.y = y;
this.v = v;
this.create = function() {
// invoke the constructor
var img = new Image;
img.src = leftcloudsrc;
var iw = img.naturalWidth;
var ih = img.naturalHeight;
ctx.drawImage(img, this.x, this.y);
}
this.move = function() {
this.x += this.v;
this.create();
}
}
<canvas id="cloud" width="500" height="400"></canvas>
Related
I have a canvas where I use "fillText" with a string, saying for example "stackoverflow". Then I read the imagedata of the canvas in order to pick out each pixel of that text.
I want to pick the following from the pixel: x position, y position and its color. Then I would like to loop over that array with those pixels so I can draw back the text pixel by pixel so I have full control of each pixel, and can for example animate them.
However, I dont get it as smooth as I want. Look at my attach image, and you see the difference between the top text and then the text I've plotted out using fillRect for each pixel. Any help on how to make the new text look like the "fillText" text does?
Thanks
UPDATE: Added my code
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var data32 = new Uint32Array(_ctx.getImageData(0, 0, _width, _height).data.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillRect(x, y, 1, 1);
_ctx.fillStyle = 'green';
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
}
}
Here is an update to your code that reuses the alpha component of each pixel. There will still be some detail lost because we do not keep the antialiasing of the pixels (which in effect alters the actual color printed), but for this example the alpha is enough.
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.imageSmoothingEnabled= false;
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var pixels = _ctx.getImageData(0, 0, _width, _height).data;
var data32 = new Uint32Array(pixels.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
a: pixels[i*4 + 3] / 255
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillStyle = `rgba(0,128,0,${particle.alpha})`;
_ctx.fillRect(x, y, 1, 1);
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
this.alpha = pos.a;
}
}
<canvas id="textCanvas"></canvas>
I'm trying to show multiple pipes in the canvas of same height but even after using a for loop it is showing a single pipe and not a lot of it
<script>
var tryCanvas = document.getElementById("myCanvas");
var c = tryCanvas.getContext("2d");
var myCall = [];
function Squares() {
for(var i =0; i < 10; i++){
this.x = Math.random()* tryCanvas.clientWidth;
this.y = 0;
this.w = 20;
this.h = 60;
this.counter = 0;
this.draw = function() {
c.beginPath();
c.rect(this.x, this.y, this.w, this.h)
c.fill();
}
}
this.update = function() {
if(this.x < 0){
this.x = 0;
}
this.x -= 1;
this.draw();
}
}
var holder = new Squares;
setInterval(callFun, 10);
function callFun() {
c.clearRect(0,0,tryCanvas.clientWidth, tryCanvas.clientHeight);
holder.update();
}
</script>
If I push the constructor function in an array it's not showing anything in the canvas and in the console it's giving undefined or NaN.
But if I do it without "this" its generating the number of rects.
What am I doing wrong here?
Updated to move the bars along the screen
See this working example:
https://codepen.io/bkfarns/pen/braWQB?editors=1010
This.draw will only get created with the values from the last iteration of the for loop.
Also as a side node, usually instead of new Squares you call the constructor like new Squares(). When you call the constructor you are calling a method.
But I think the code below fixes your issues. Try it out:
<body>
<canvas id="myCanvas"/>
</body>
<script>
var tryCanvas = document.getElementById("myCanvas");
var c = tryCanvas.getContext("2d");
var myCall = [];
function Squares() {
this.draw = function(xOffset) {
for(var i =0; i < 10; i++){
this.x = (i * xOffset) + (5*i)//Math.random()* tryCanvas.clientWidth;
this.y = 0;
this.w = 20;
this.h = 60;
c.beginPath();
c.rect(this.x, this.y, this.w, this.h)
c.fill();
}
}
}
var holder = new Squares();
var xOffset = 20;
setInterval(function() {
c.clearRect(0,0,tryCanvas.clientWidth, tryCanvas.clientHeight);
holder.draw(xOffset)
xOffset--;
}, 1000)
</script>
I made this (run snippet below)
var Canvas = document.getElementById('c');
var ctx = Canvas.getContext('2d');
var resize = function() {
Canvas.width = Canvas.clientWidth;
Canvas.height = Canvas.clientHeight;
};
window.addEventListener('resize', resize);
resize();
var elements = [];
var presets = {};
presets.shard = function (x, y, s, random, color) {
return {
x: x,
y: y,
draw: function(ctx, t) {
this.x += 0;
this.y += 0;
var posX = this.x + + Math.sin((50 + x + (t / 10)) / 100) * 5;
var posy = this.y + + Math.sin((55 + x + (t / 10)) / 100) * 7;
ctx.beginPath();
ctx.fillStyle = color;
ctx.moveTo(posX, posy);
ctx.lineTo(posX+random,posy+random);
ctx.lineTo(posX+random,posy+random);
ctx.lineTo(posX+0,posy+50);
ctx.closePath();
ctx.fill();
}
}
};
for(var x = 0; x < Canvas.width; x++) {
for(var y = 0; y < Canvas.height; y++) {
if(Math.round(Math.random() * 60000) == 1) {
var s = ((Math.random() * 5) + 1) / 10;
if(Math.round(Math.random()) == 1){
var random = Math.floor(Math.random() * 100) + 10;
var colorRanges = ['#8c8886', '#9c9995'];
var color = colorRanges[Math.floor(Math.random() * colorRanges.length)];
elements.push(presets.shard(x, y, s, random, color));
}
}
}
}
setInterval(function() {
ctx.clearRect(0, 0, Canvas.width, Canvas.height);
var time = new Date().getTime();
for (var e in elements)
elements[e].draw(ctx, time);
}, 10);
<canvas id="c" width="1000" height="1000"\>
I just need to add one feature to be able to use it on the site I'm building it for. Some of the floating shards need to be blurred to give a sense of depth.
Can Canvas do this, and if so, how?
context.filter = 'blur(10px)';
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter
I used this few months ago, maybe it could work for you as well :
var canvas = document.getElementById("heroCanvas");
var canvasContext = canvas.getContext("2d");
var canvasBackground = new Image();
canvasBackground.src = "image.jpg";
var drawBlur = function() {
// Store the width and height of the canvas for below
var w = canvas.width;
var h = canvas.height;
// This draws the image we just loaded to our canvas
canvasContext.drawImage(canvasBackground, 0, 0, w, h);
// This blurs the contents of the entire canvas
stackBlurCanvasRGBA("heroCanvas", 0, 0, w, h, 100);
}
canvasBackground.onload = function() {
drawBlur();
}
Here the source : http://zurb.com/playground/image-blur-texture
I'm trying to animate a sine wave in JS but it's not acting as expected. I'm using a <canvas> element along with window.requestAnimationFrame() method but it's a CPU hog and as i change frequency with the slider it just break and show random waveforms. I also don't know if drawing adjacent lines is the best way to represent a sine wave. Please note that i'll use vanilla JS and that the sine's frequency and amplitude are variables set by sliders. Thanks in advance.
This is what i got so far: http://cssdeck.com/labs/8cq5vclp
UPDATE: i worked on it and this is the new version: http://cssdeck.com/labs/sbfynjkr
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
cHeight = canvas.height,
cWidth = canvas.width,
frequency = document.querySelector("#f").value,
amplitude = 80,
x = 0,
y = cHeight / 2,
point_y = 0;
window.onload = init;
function init() {
document.querySelector("#f").addEventListener("input", function() {
frequency = this.value;
document.querySelector("#output_f").value = frequency;
}, false);
drawSine();
}
function drawSine() {
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.beginPath();
ctx.moveTo(0, y);
ctx.strokeStyle = "red";
ctx.lineTo(cWidth, y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "black";
for (x = 0; x < 600; x++) {
point_y = amplitude * -Math.sin((frequency / 95.33) * x) + y;
ctx.lineTo(x, point_y);
}
ctx.stroke();
ctx.closePath();
requestAnimationFrame(drawSine);
}
canvas {
border: 1px solid red;
margin: 10px;
}
<input id="f" type="range" min="0" max="20000" value="20" step="1">
<output for="f" id="output_f">20</output>
<canvas width="600px" height="200px"></canvas>
I've messed around with sine waves quite a bit, because I'm working on a little project that involves animated sine waves. I've got some code you might be interested in taking a look at. Like mentioned earlier, you need to make sure you are using the right increment in your loop so the lines do not look jagged.
https://jsfiddle.net/uawLvymc/
window.requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(f) {
return setTimeout(f, 1000 / 60)
};
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var startTime = new Date().getTime();
function getPath(height) {
var width = canvas.width;
var spacing = 0.08;
var loopNum = 0;
var pointList = [];
var i = 0;
for (i = 0; i < width / 2; i++) {
pointList[loopNum] = [loopNum, Math.sin(loopNum * spacing) * (i * height) + 100];
loopNum++;
}
for (i = width / 2; i > 0; i--) {
pointList[loopNum] = [loopNum, Math.sin(loopNum * spacing) * (i * height) + 100];
loopNum++;
}
return pointList;
}
function draw() {
var currentTime = new Date().getTime();
var runTime = currentTime - startTime;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "rgb(80, 100, 230)";
var height = Math.sin(runTime * 0.008) * 0.2;
var pointList = getPath(height);
for (var i = 0; i < 500; i++) {
if (i === 0) {
ctx.moveTo(pointList[0][0], pointList[0][1]);
} else {
ctx.lineTo(pointList[i][0], pointList[i][1]);
}
}
ctx.stroke();
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
Sorry I didn't really edit down the code, it's just a direct copy from what I was working on. Hope it helps though.
See if this example could help you a little
Sine Wave Example canvas
function init()
{
setInterval(OnDraw, 200);
}
var time = 0;
var color = "#ff0000";
function OnDraw()
{
time = time + 0.2;
var canvas = document.getElementById("mycanvas");
var dataLine = canvas.getContext("2d");
var value = document.getElementById("lineWidth");
dataLine.clearRect(0, 0, canvas.width, canvas.height);
dataLine.beginPath();
for(cnt = -1; cnt <= canvas.width; cnt++)
{
dataLine.lineTo(cnt, canvas.height * 0.5 - (Math.random() * 2 + Math.cos(time + cnt * 0.05) * 20 ));
}
dataLine.lineWidth = value.value * 0.1;
dataLine.strokeStyle = color;
dataLine.stroke();
}
I wrote the following 1000 bounding squares demo as a test of the capabilities of HTML5 canvas. It runs fine at first but then a noticeable drop in fps after a few seconds. I am not sure why. Any pointers would be appreciated.
var c = document.getElementById("canvas");
var context = c.getContext("2d");
var WIDTH = 600;
var HEIGHT = 800;
c.width = WIDTH;
c.height = HEIGHT;
image = loadImage("square.png");
function loadImage(imageName){
var i = new Image();
i.src = imageName;
return i;
}
function clear(){
context.fillStyle = "#d0e7f9";
context.rect(0,0,WIDTH,HEIGHT);
context.fill();
}
clear();
var SpriteList = [];
var Sprite = (function() { //javascript class(?)... shredders
function Sprite(){ //constructor
this.x = Math.random()*WIDTH;
this.y = Math.random()*HEIGHT;
this.vx = Math.random()*10;
this.vy = Math.random()*10;
SpriteList.push(this);
}
Sprite.prototype.update = function(){
this.x += this.vx;
this.y += this.vy;
if (this.x<0 || this.x>WIDTH){
this.vx *= -1;
}
if (this.y<0 || this.y>HEIGHT){
this.vy *= -1;
}
};
return Sprite;
})();
for (var i = 0;i<1000;i++){
new Sprite();
}
function draw(){
clear();
for (i in SpriteList)
{
var s = SpriteList[i];
s.update();
context.drawImage(image, s.x, s.y);
}
}
setInterval(draw,1000/60);
There are a few issues with the code but the main reason for this to happen is this code:
This code will require you to use beginPath():
function clear(){
context.fillStyle = "#d0e7f9";
context.beginPath();
context.rect(0,0,WIDTH,HEIGHT); /// this will require beginPath();
context.fill();
}
or to avoid it, you can simply modify the code to do this:
function clear(){
context.fillStyle = "#d0e7f9";
context.fillRect(0,0,WIDTH,HEIGHT); /// this does not require beginPath();
}
Live fiddle here
/// use a var here
var image = loadImage("square.png");
/// your image loader is missing - image may not show up
function loadImage(imageName){
var i = new Image();
i.onload = nextStep; /// something like this
i.src = imageName;
return i;
}
var SpriteList = [];
/// create this as an object
function Sprite(){ //constructor
this.x = Math.random()*WIDTH;
this.y = Math.random()*HEIGHT;
this.vx = Math.random()*10;
this.vy = Math.random()*10;
return this;
}
Sprite.prototype.update = function(){
this.x += this.vx;
this.y += this.vy;
if (this.x<0 || this.x>WIDTH){
this.vx *= -1;
}
if (this.y<0 || this.y>HEIGHT){
this.vy *= -1;
}
};
/// separate pushing of the instances
for (var i = 0;i<1000;i++){
SpriteList.push(new Sprite());
}
var oldTime = 0;
function draw(timeElapsed){ /// in milliseconds
clear();
var diffTime = timeElapsed - oldTime;
/// use vars here too
for (var i = 0, s; s = SpriteList[i]; i++ )
{
s.update();
context.drawImage(image, s.x, s.y);
}
oldTime = timeElapsed;
/// use rAF here
requestAnimationFrame(draw);
}
draw(0); /// start
The setInterval may cause the whole thing to stack calls if the browser is not fast enough processing the sprites within the time budget you give,.
By using rAF the browser will only request a frame when it can even if that means lower frame rates - you will at least not lock up/slow down the browser.
(as you didn't provide a link to the image you're using I substituted it with a temp canvas - you will still need to consider a onload event handler for the actual image).
A few suggestions:
Use image.onload to be sure your square.png is fully loaded before it's used.
Put the image loading at the bottom of your code after you create your 1000 sprites.
var image=new Image();
image.onload=function(){
draw();
}
image.src="square.png";
Don't iterate using for(i in SpriteList). Do this instead:
for(var i=0;i<SpriteList.length;i++)
Your draw functions are probably stacking--the current draw() isn't being completed before setInterval is requesting another draw().
Replace setInterval with requestAnimationFrame to stop your stacking problems.
function draw(){
// request another animation frame
requestAnimationFrame(draw);
// draw the current frame
clear();
for(var i=0;i<SpriteList.length;i++)
{
var s = SpriteList[i];
s.update();
context.drawImage(image, s.x, s.y);
}
}