i've made a little script where a canvas is drawn dynamically according to user input(type:number), it needs work but the issue is that on android devices, when the user hide the soft keyboard, the canvas is erased. is it a way to prevent that ? Thx for any help
here is the code
var form = document.querySelector("form"),
canvas = document.getElementById("canvas"),
positions = [];
if (!canvas) {
alert("Impossible de récupérer le canvas");
}
window.addEventListener("resize", function () {
if (window.innerWidth < 500) {
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerWidth * 0.8;
} else {
canvas.width = 600;
canvas.height = 600;
}
});
var context = canvas.getContext("2d");
if (!context) {
alert("Impossible de récupérer le contexte");
}
form.addEventListener("input", function () {
var table = form.table.value,
modulo = form.modulo.value,
centerX = canvas.width / 2,
centerY = canvas.height / 2,
rayon = (canvas.width - 5) / 2;
context.lineWidth = 2;
context.strokeStyle = "#21A8A3";
context.clearRect(0, 0, canvas.width, canvas.width);
for (var i = modulo; i >= 0; i -= 1) {
var angle = (2 * Math.PI / modulo) * i - (Math.PI / 2);
positions[i] = {
x: centerX + rayon * Math.cos(angle),
y: centerY + rayon * Math.sin(angle)
};
}
context.beginPath();
context.arc(centerX, centerY, rayon, 0, Math.PI * 2);
var j = positions.length - 1;
for (j; j >= 0; j -= 1) {
var next = j * table;
context.moveTo(positions[j].x, positions[j].y);
context.lineTo(positions[next % modulo].x, positions[next % modulo].y);
}
context.stroke();
});
and the url :http://multiplier.hyperion-web.com/
I didn't tried on an Android device, but your problem is more likely due to your bad resize event listener.
This handler do resize your canvas at each resize event, but it doesn't call again your drawing functions, which leaves you with an empty canvas, since modifying the width and height of a canvas resets its context (it does clear it, but also resets any properties like fillStyle, transformation matrix, clipping area etc).
Calling and hiding the soft keyboard will fire a resize event.
So the solution for you is to fix your event handler so that it does redraw everything after it has set the new dimensions of your canvas.
For this you'll have to store your drawing function in a separate variable, then attach it to your input event, and make a call to it in your resize event listener.
Ps : even setting the width or height attributes to the same value they were will reset the whole context
Related
I am trying to reset my progress ring, which is drawn with canvas on resize.
Currently, when I resize my window the function is run again as expected, but instead of the canvas being reset another progress ring is being drawn within the same canvas, please see screenshot below:
I have found clearRect() found in an answer here: How to clear the canvas for redrawing and similar solutions but it doesn't seem to resolve my issue.
Please find my codepen with the code: https://codepen.io/MayhemBliz/pen/OJQyLbN
Javascript:
// Progress ring
function progressRing() {
const percentageRings = document.querySelectorAll('.percentage-ring');
for (const percentageRing of Array.from(percentageRings)) {
console.log(percentageRing.offsetWidth)
var percentageRingOptions = {
percent: percentageRing.dataset.percent,
size: percentageRing.offsetWidth,
lineWidth: 30,
rotate: 0
}
var canvas = document.querySelector('.percentage-ring canvas');
var span = document.querySelector('.percentage-ring span');
span.textContent = percentageRingOptions.percent + '%';
if (typeof (G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
//clear canvas for resize
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
// end clear canvas
canvas.width = canvas.height = percentageRingOptions.size;
percentageRing.appendChild(span);
percentageRing.appendChild(canvas);
ctx.translate(percentageRingOptions.size / 2, percentageRingOptions.size / 2); // change center
ctx.rotate((-1 / 2 + percentageRingOptions.rotate / 180) * Math.PI); // rotate -90 deg
//imd = ctx.getImageData(0, 0, 240, 240);
var radius = (percentageRingOptions.size - percentageRingOptions.lineWidth) / 2;
var drawCircle = function (color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
//ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};
drawCircle('#efefef', percentageRingOptions.lineWidth, 100 / 100);
var i = 0; var int = setInterval(function () {
i++;
drawCircle('#555555', percentageRingOptions.lineWidth, i / 100);
span.textContent = i + "%";
if (i >= percentageRingOptions.percent) {
clearInterval(int);
}
}, 50);
}
}
window.addEventListener('load', progressRing);
window.addEventListener('resize', progressRing);
HTML:
<div class="percentage-ring" data-percent="88">
<span>88%</span>
<canvas></canvas>
</div>
Your help would be greatly appreciated.
Thanks in advance
1. Clear size problem
var canvas = document.querySelector('.percentage-ring canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
You have to specify canvas size with (canvas.width, canvas.height) instead of ctx.canvas.width, ctx.canvas.height
ctx.clearRect(0, 0, canvas.width, canvas.height);
2. Interval timer usage problem
If progressRing() is called again after setInterval() without clearInterval(), e.g., due to resizing, it will continue to run with the previous interval execution surviving. This will cause the ring to be drawn twice, thrice and more.
Place var int = 0; outside functions. This initializes the int to 0 first.
And modify var int = setInterval( to int = setInterval(
Then place the following code at the beginning of progressRing()
if (int) {
clearInterval(int);
int = 0;
}
And also place int = 0; immediately after the clearInterval() call that was already there.
I want to animate a rect on a canvas. It technically works, but the canvas is not clearing before each frame and it leaves a mark on the ground (sort of like a snail). I have done research and everything seems to point to the use of ctx.beginPath() as the solution to my problem, but when I try to use it here, it doesn't work. What am I doing wrong?
Here is the raw javascript:
// create a canvas element
var canvas = document.createElement("canvas");
// attach element to DOM
document.getElementsByTagName("body")[0].appendChild(canvas);
// get the canvas context (this is the part we draw to)
var ctx = canvas.getContext("2d");
var bug = new Bug(0,0);
function setup() {
// setup the canvas size to match the window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// set the 0,0 point to the middle of the canvas
ctx.translate(canvas.width / 2, canvas.height / 2);
}
function draw() { //Do the drawing
ctx.beginPath();
bug.update();
bug.draw();
window.requestAnimationFrame(function(){draw()});
}
// start enterFrame loop
window.requestAnimationFrame(draw);
// force running setup
setup();
// re-setup canvas when the size of the window changes
window.addEventListener("resize", setup);
// sin(pi) == 0
// cos(pi) == -1
// sin(2pi) == 0
// cos(2pi) == 1
// degrees to radians: {{ deg * (pi/180) = rad }}
function randomRange(max, min) {
return Math.floor(Math.random() * (max - min) + min);
}
function Bug(x, y) {
this.x = x;
this.y = y;
this.jitter = 10;
this.speed = 1;
this.deg = 0;
this.rad = 0;
this.update = function() {
//update degrees
this.deg += randomRange(this.jitter, -this.jitter);
//convert degrees into radians
this.rad = this.deg * (Math.PI/180);
//update coordinates
this.x += this.speed * (Math.cos(this.rad));
this.y += this.speed * (Math.sin(this.rad));
};
this.draw = function() {
ctx.beginPath();
ctx.rect(this.x, this.y, 50, 50);
ctx.fill();
};
}
The beginPath function doesn't clear the screen it starts a path to draw, to clear the screen you should use clearRect instead and in your specific situation you should use:
ctx.clearRect(-canvas.width, -canvas.height, canvas.width*2, canvas.height*2);
I am trying to divide my whole canvas in 20 equal columns and animate them individually on the y-axis. The goal is to create a similar wave scroll animation like here. First I tried to create the wave scroll effect with php generated images with the text on it and tried to animate them in single divs with the images as background-images. It technically worked but the performance and page load time was extremely bad. Now I want to create it with canvas: I already have the content with all the images and text in it and tried to animate it. I tried to save the whole content in columns (rectangles) with getImageData() then I created created rectangles with the ImageData and re-draw them in a loop but again the performance was terrible, especially on mobile devices. The animation loop looked as followed:
var animate = function(index, y) {
// The calculations required for the step function
var start = new Date().getTime();
var end = start + duration;
var current = rectangles[index].y;
var distance = y - current;
var step = function() {
// get our current progress
var timestamp = new Date().getTime();
var progress = Math.min((duration - (end - timestamp)) / duration, 1);
context.clearRect(rectangles[index].x, rectangles[index].y, columnWidth, canvas.height);
// update the rectangles y property
rectangles[index].y = current + (distance * progress);
context.putImageData(rectangles[index].imgData, rectangles[index].x, rectangles[index].y);
// if animation hasn't finished, repeat the step.
if (progress < 1)
requestAnimationFrame(step);
};
// start the animation
return step();
};
Now the question: How can I divide the whole canvas in equal columns and animate them on the y-axis with a good performance? Any suggestions? Maybe with the help of Pixi.js / Greensock? Thanks in advance!
Do not use getImageData and setImageData to do animation. The canvas is an image and can be rendered just like any image.
To do what you want
Create a second copy of the canvas and use that canvas a source for the strips you want to render.
Example.
const slices = 20;
var widthStep;
var canvas = document.createElement("canvas");
var canvas1 = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.top = canvas.style.left = "0px";
var ctx = canvas.getContext("2d");
var ctx1 = canvas1.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function resize(){
canvas.width = canvas1.width = innerWidth;
canvas.height = canvas1.height = innerHeight;
w = canvas.width;
h = canvas.height;
ctx1.font = "64px arial black";
ctx1.textAlign = "center"
ctx1.textBaseLine = "middle";
ctx1.fillStyle = "blue";
ctx1.fillRect(0,0,w,h);
ctx1.fillStyle = "red";
ctx1.fillRect(50,50,w-100,h-100);
ctx1.fillStyle = "black";
ctx1.strokeStyle = "white";
ctx1.lineWidth = 5;
ctx1.lineJoin = "round";
ctx1.strokeText("Waves and canvas",w / 2, h / 2);
ctx1.fillText("Waves and canvas",w / 2, h / 2);
widthStep = Math.ceil(w / slices);
}
resize();
window.addEventListener("resize",resize);
document.body.appendChild(canvas);
function update(time){
var y;
var x = 0;
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i = 0; i < slices; i ++){
y = Math.sin(time / 500 + i / 5) * (w / 8);
y += Math.sin(time / 700 + i / 7) * (w / 13);
y += Math.sin(time / 300 + i / 3) * (w / 17);
ctx.drawImage(canvas1,x,0,widthStep,h,x,y,widthStep,h);
x += widthStep;
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
$(function() {
var centerX = 400,
centerY = 400,
radius = 300;
function make_base(ctx, angle) {
base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
base_image.onload = function(){
ctx.save();
var x = centerX + Math.sin(angle) * radius;
var y = centerY + Math.cos(angle) * radius;
ctx.translate(x, y);
ctx.drawImage(base_image, 0, 0);
ctx.rotate(angle);
ctx.restore();
}
}
function draw(step) {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var angle;
for (var i = 0; i < 180; i += 10) {
angle = step + (i * Math.PI / 90);
make_base(ctx, angle);
}
}
var step = 0;
draw(step);
setInterval(function() {
draw(step);
++step;
}, 20);
});
The issue is when i try to loop the image to create a ring shape and animate it by using setInterval, clearing and redrawing the images on the canvas results to flicker.
Is there a way to smoothen the orbit animation? or a better way to create an orbit animation?
Codepen
Canvas animations.
When animating on a browser always use requestAnimationFrame, it gives far better results than setInterval and setTimeout by only moving new pixel data to the display in sync with the refresh time.
Why the flicker
It's complicated...
The basic reason is that you were loading the image each time you wanted to use it. The onload will not fire until after your code has stopped running. When your code stops running the canvas content will be presented to the display and it will be empty because you just cleared it (no event will fire until you exit the draw function). Then the onload events will start to fire, each drawing the image on the canvas. When the event exits the browser thinks "ok you want to put that to the display!" and the whole canvas is again presented to the display. Because this happens much faster than the screen refresh rate you get you weird shearing, flickering, jumping effect.
Another reason is the use of setInterval The Answer from DominicValenciana still uses setInterval and if you look carefully you will see that it is not a smooth as using requestAnimationFrame. Good animation should be smooth as silk (well as close a possible)
Some tips below the demo.
This answer will also help explain image loading and drawing.
(function () {
const oneDeg = Math.PI / 180;
const D1 = Math.PI / 180; // again for lazy programmers
const centerX = 400;
const centerY = 400;
const radius = 300;
// create and load image
const base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
// create size and add to DOM a canvas
const c = document.createElement("canvas");
c.width = 800;
c.height = 800;
document.body.appendChild(c);
const ctx = c.getContext("2d"); // get context
var step = 0; // the animation step
function draw() { // the main draw function
var x,y,i,angle;
if(base_image.complete){ // has the image loaded
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.clearRect(0, 0, c.width, c.height);
for (i = 0; i < Math.PI * 2; i += D1 * 10) { // 360 in steps of 10
angle = step + i; // angle in radians
x = centerX + Math.sin(angle) * radius;
y = centerY + Math.cos(angle) * radius;
ctx.setTransform(1, 0, 0, 1, x, y); // set translate and scale no need for save restore
ctx.rotate(angle); // set angle
ctx.drawImage(base_image, 0, 0); //draw image
}
step += 1* D1; // step 1 deg per frame
}
requestAnimationFrame(draw);
}
draw();
})();
Angles, PI, and Trig
Your calculation for the position of the sprite
x = Math.sin(angle);
y = Math.cos(angle);
has zero (angle = 0) at the top of the display (the 12 oclock position). To match the same angle as used by the canvas rotate function use
x = Math.cos(angle);
y = Math.sin(angle);
this has zero at the right of the screen (the 3oclock position)
If you want to keep creating animations and computer graphics. Forget about 360 deg. Circles are 2PI around not 360, PI is half a turn 180, PI/2 is a quarter turn 90, PI/30 is a minute hands movement per minute, the distance around a circle is 2*PI*radius, around half a circle PI * radius. Every trig function in JS uses radians (not to be annoying, or obtuse, but because it is far simpler to use), You will always need to use PI at some stage if you work in deg, so just drop the unnecessary overhead.
Constants and variables
Only initialize variables once if you never intend to change it. Better yet if a variable reference never needs to be changed make it a constant const myVar = 10; if you try and change it in any way it will throw an error myVar = 10; ERROR!!!!!
Animation where performance is everything
When animating it is important to keep performance at maximum so you can invest more into the quality of the animation. When setting the transform it is far easier to use ctx.setTransform(scale,0,0,scale,x,y); ctx.rotate(angle); as you do not need to save and restore the full canvas state just for transforms. If you wish to get the default transform use ctx.setTransform(1,0,0,1,0,0);
Don't support the dead
No need for the moz,webkit prefixed requestAnimationFrame is everywhere. and as far as I know if a browser does not support requestAnimationFrame it does not support canvas, and we should not support as browser that does. Also requestAnimationFrame is not just for canvas, it should be used for any DOM javascript based animations you create.
The problem is that a new instance of the image is being reloaded each time they are rendered in the circle. The network delay causes this flashing that you see. By loading the image first and then reusing the data I have removed the flashing.
Here is a working version:
http://codepen.io/anon/pen/JRVJRJ
I have moved the loading of base_image out side of the make_base function and made it a global variable for reuse in the make_base function. The causes the image to be loaded once and reapplied multiple times.
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
$(function() {
var centerX = 400,
centerY = 400,
radius = 300;
function make_base(ctx, angle) {
ctx.save();
var x = centerX + Math.sin(angle) * radius;
var y = centerY + Math.cos(angle) * radius;
ctx.translate(x, y);
ctx.drawImage(base_image, 0, 0);
ctx.rotate(angle);
ctx.restore();
}
function draw(step) {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var angle;
for (var i = 0; i < 180; i += 10) {
angle = step + (i * Math.PI / 90);
make_base(ctx, angle);
}
}
base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
base_image.onload = function(){
var step = 0;
draw(step);
setInterval(function() {
draw(step);
++step;
}, 20);
}
});
http://jsfiddle.net/27WVW/1/
I have a canvas element that uses SetInterval to animate the entrance of a background.
var c=document.getElementById("theCircle")
function drawCircle(end=2*Math.PI){
height = window.innerHeight
width = window.innerWidth
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.arc(width/2,height/2,radius,0,end,true);
ctx.lineWidth=end/5;
ctx.closePath();
ctx.stroke();
}
function counter(start,finish,speed){
var i = start
var animation = setInterval(function(){
i += speed
if(i>=finish){clearInterval(animation)}
drawCircle(i);
}, 20)
}
counter(0.0,2*Math.PI,0.05)
On window resize the canvas element is resized to fit the window so that it always fills the page.
window.addEventListener('resize',posFix,false);
function posFix(){
c.height = window.innerHeight
c.width = window.innerWidth
drawCircle();
}
When the canvas element is resized the drawing inside is deleted. As you can see, I am calling a redraw on resize but it doesn't seem to fire properly.
What am I missing?
As discussed within the comments, the solution is to clear the animation interval within the resize event and then restart the animation.
var c = document.getElementById("theCircle"),
radius = 800;
posFix();
window.addEventListener('resize', posFix, false);
function posFix() {
c.height = window.innerHeight
c.width = window.innerWidth
clearInterval(animation);
counter(0.0, 2 * Math.PI, 0.05)
}
function drawCircle(end = 2 * Math.PI) {
height = window.innerHeight
width = window.innerWidth
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(width / 2, height / 2, radius, 0, end, true);
ctx.lineWidth = end / 5;
ctx.closePath();
ctx.stroke();
}
var animation;
function counter(start, finish, speed) {
var i = start
animation = setInterval(function () {
i += speed
if (i >= finish) {
clearInterval(animation);
}
drawCircle(i);
}, 20);
}
jsfiddle: http://jsfiddle.net/27WVW/4/