I think this is a chrome issue as it only seems to Chrome, Edge, and OBS. However, that is a problem because that is most of my intended audience. There is no flicker in Firefox so I believe I just need a work around for Chromium based browsers.
The flickering only seems to last for 1 rotation through the animation, however, it will occasionally come back at random times. It also comes back whenever we change the sprite animation
Prior to the below code running we have already imported the image as a Javascript Object. Each image has multiple states we can cycle between. Each state has 10 frames which are loaded as DataURLs and switched between so the flickering would not be caused by loading the images.
Currently we are testing with very few images so the total object size is only a few Kb.
//Set up Canvas Javascript Elements
const canvas = document.getElementById('mainCanvas');
const bufferCanvas = document.getElementById('bufferCanvas');
//Get Canavas 2d context
const ctx = canvas.getContext('2d');
const bctx = bufferCanvas.getContext('2d');
//Set Canvas Size - Broken up into multiple lines to more easily enable and disable elements during testing
const CANVAS_WIDTH = 364;
const CANVAS_HEIGHT = 444;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
bufferCanvas.width = CANVAS_WIDTH;
bufferCanvas.height = CANVAS_HEIGHT;
//Sets current animation, initial frame, and cycle count
var currentAnimation = 'idle'
var currentFrame = 0;
var cycle = 0;
function animate(){
//Only incrementing frames when the cycle mod 10 is 0 slows it down
if(cycle % 10 == 0){
currentFrame++;
if(currentFrame >= currentImage[currentAnimation].length){
currentFrame = 0;
}
playerImage.src = currentImage[currentAnimation][currentFrame];
}
//Draws the buffer canvas prior to clearing the main canvas
bctx.drawImage(playerImage, 0, 0);
//Clears main Canvas
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
//Draws new image to main Canvas
ctx.drawImage(bufferCanvas, 0, 0);
//Clears buffer canvas once main canvas has been drawn
bctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
//Resets cycle to maintain a manageable number
cycle++;
if(cycle >= 10000){
cycle = 0;
}
//Calls itself to loop
requestAnimationFrame(animate);
}
Related
I'm trying to draw any amount of images onto a canvas while being able to zoom in/out using the mousewheel event, but when adjusting the canvas scale, sometimes the images overlap and the canvas doesn't clear.
Here I get the canvas, create the starting scale, image, and add the eventlistener when it is loaded:
var canvas = document.getElementById("Canvas") // Canvas
var ctx = canvas.getContext("2d") // Context
var scale = 1 // Stored Canvas Scale
var image = new Image() // Random Image
image.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Heart_coraz%C3%B3n.svg/1200px-Heart_coraz%C3%B3n.svg.png"
image.onload = () => {console.log("Running..."); canvas.addEventListener("mousewheel", MouseWheelHandler)}
Next, the reDraw() method clears the canvas and draws any number of images (between 10, or so) onto the canvas, while compensating for the positioning of the images:
function reDraw() { // Draw Image(s)
ctx.clearRect(0, 0, canvas.width, canvas.height) // Clear Canvas
var maxX = parseInt(Math.random()*10) // Set Max Length
var maxY = parseInt(Math.random()*10) // Set Max Height
var size = 32; // Set Image Size
for (var i=0; i<maxX; i++) {
ctx.drawImage(image, canvas.width/2, canvas.height/2-(size*(maxY-i-1)), size, size)
for (var c=0; c<maxY; c++) {
ctx.drawImage(image, canvas.width/2-(size*(maxX-i-1)), canvas.height/2-(size*(maxY-i-1)), size, size)
}
}
}
Finally, the MouseWheelHandler() method gets the mousewheel data and adjusts the canvas scale accordingly:
function MouseWheelHandler(e) {
var e = window.event || e;
var delta = e.wheelDelta || -e.detail
if (scale+(delta/Math.abs(delta*10)) >= 0.1) { // Prevent zooming out further than 10% of view
var zoom = (1+(delta/Math.abs(delta*10))) // Set the zoom
scale += (delta/Math.abs(delta*10)) // Set the scale (No less than 0.1)
ctx.transform(zoom,0,0,zoom,-(zoom-1)*canvas.width/2,-(zoom-1)*canvas.height/2)
reDraw()
}
}
My problem is that if I zoom too far out, the canvas isn't getting cleared and the images appear to kind of echo, or layer on top of one another. I've tried doing ctx.clearRect() with numbers larger and smaller than the canvas size, but it still results in overlapping images.
I just want to be able to zoom in/out and have the canvas only display what was drawn after the MouseWheelHandler runs.
I am quite new to Pixi.js so I'm sorry this is a stupid question.
I understand that if I would like to render pixi.js to an existing canvas I have to specify view.
const app = new PIXI.Application({
view: myExistingCanvas,
});
However, I realized that if I write like this, Pixi application actually overwrites my existing canvas and I end up losing all the contents inside "myExistingCanvas".
Could somebody advise me how I can create a pixi application on top of an existing canvas without overwriting?
Using the view property you can pass to the constrctor of a new PIXI.Application, we can tell it to use an existing Canvas. This canvas though doesn't necessarily have to be added to the DOM - it's enough if it exists 'virtually'.
So ultimately we need three Canvas instances - which all should have equal dimensions.
The first canvas would be the existing canvas you've mentioned in your question and act as an off-screen canvas
The second canvas is another empty off-screen canvas, which captures Pixi's output
The third canvas is actually the on-screen canvas which combines the output of the previous two canvases
Now you might wonder how to do this.
To do this we must intercept Pixi's update loop, which we can do by adding a ticker to PIXI.Ticker.shared.
Inside this update loop we need to do the following things:
Update Pixi's animations and call it's renderer
Clear the third (on-screen) canvas
Draw the contents of the first canvas to the third
Draw the contents of the second canvas to the third
Basically that's it - though it might sound a bit abstract.
Here's an example (Just click on 'Run code snippet'):
let offScreenCanvasA = document.createElement("canvas");
let offScreenCanvasB = document.createElement("canvas");
let onScreenCanvas = document.createElement("canvas");
let width = 400;
let height = 300;
offScreenCanvasA.width = width;
offScreenCanvasB.width = width;
onScreenCanvas.width = width;
offScreenCanvasA.height = height;
offScreenCanvasB.height = height;
onScreenCanvas.height = height;
document.body.appendChild(onScreenCanvas);
const app = new PIXI.Application({
view: offScreenCanvasB,
transparent: true,
width: 400,
height: 300
});
const container = new PIXI.Container();
const renderer = PIXI.autoDetectRenderer();
app.stage.addChild(container);
const texture = PIXI.Texture.from('https://picsum.photos/id/237/26/37');
for (let i = 0; i < 25; i++) {
const bunny = new PIXI.Sprite(texture);
bunny.anchor.set(0.5);
bunny.x = (i % 5) * 40;
bunny.y = Math.floor(i / 5) * 40;
container.addChild(bunny);
}
container.x = app.screen.width / 2;
container.y = app.screen.height / 2;
container.pivot.x = container.width / 2;
container.pivot.y = container.height / 2;
let ticker = PIXI.Ticker.shared
ticker.add(function(delta) {
container.rotation -= 0.01;
renderer.render(container);
onScreenCanvas.getContext("2d").clearRect(0, 0, width, height);
onScreenCanvas.getContext("2d").drawImage(offScreenCanvasA, 0, 0, width, height);
onScreenCanvas.getContext("2d").drawImage(offScreenCanvasB, 0, 0, width, height);
});
let image = new Image();
image.onload = function(e) {
offScreenCanvasA.getContext("2d").drawImage(e.target, 0, 0, width, height);
}
image.src = "https://picsum.photos/id/237/400/300";
<script src="https://d157l7jdn8e5sf.cloudfront.net/dev/pixi-legacy.js"></script>
The dog picture in the background is the from the first canvas, and the rotating grid of dogs Pixi's output to the second canvas.
I have created a basic shape in HTML canvas element which works fine.
The problem occurs when I resize the canvas, all the drawing in the canvas disappears. Is this the normal behavior? or is there a function that can be used to stop this?
One way to fix this could be to call drawing function again on canvas resize however this may not be very efficient if there is huge content to be drawn.
What's the best way?
Here is the link to sample code https://gist.github.com/2983915
You need to redraw the scene when you resize.
setting the width or height of a canvas, even if you are setting it to the same value as before, not only clears the canvas but resets the entire canvas context. Any set properties (fillStyle, lineWidth, the clipping region, etc) will also be reset.
If you do not have the ability to redraw the scene from whatever data structures you might have representing the canvas, you can always save the entire canvas itself by drawing it to an in-memory canvas, setting the original width, and drawing the in-memory canvas back to the original canvas.
Here's a really quick example of saving the canvas bitmap and putting it back after a resize:
http://jsfiddle.net/simonsarris/weMbr/
Everytime you resize the canvas it will reset itself to transparant black, as defined in the spec.
You will either have to:
redraw when you resize the canvas, or,
don't resize the canvas
One another way is to use the debounce if you are concerned with the performance.
It doesnt resize or redraw every position you are dragging. But it will resize only when the it is resized.
// Assume canvas is in scope
addEventListener.("resize", debouncedResize );
// debounce timeout handle
var debounceTimeoutHandle;
// The debounce time in ms (1/1000th second)
const DEBOUNCE_TIME = 100;
// Resize function
function debouncedResize () {
clearTimeout(debounceTimeoutHandle); // Clears any pending debounce events
// Schedule a canvas resize
debounceTimeoutHandle = setTimeout(resizeCanvas, DEBOUNCE_TIME);
}
// canvas resize function
function resizeCanvas () { ... resize and redraw ... }
I had the same problem. Try following code
var wrapper = document.getElementById("signature-pad");
var canvas = wrapper.querySelector("canvas");
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
It keeps the drawing as it is
One thing that worked for me was to use requestAnimationFrame().
let height = window.innerHeight;
let width = window.innerWidth;
function handleWindowResize() {
height = window.innerHeight;
width = window.innerWidth;
}
function render() {
// Draw your fun shapes here
// ...
// Keep this on the bottom
requestAnimationFrame(render);
}
// Canvas being defined at the top of the file.
function init() {
ctx = canvas.getContext("2d");
render();
}
I had the same problem when I had to resize the canvas to adjust it to the screen.
But I solved it with this code:
var c = document.getElementById('canvas');
ctx = c.getContext('2d');
ctx.fillRect(0,0,20,20);
// Save canvas settings
ctx.save();
// Save canvas context
var dataURL = c.toDataURL('image/jpeg');
// Resize canvas
c.width = 50;
c.height = 50;
// Restore canvas context
var img = document.createElement('img');
img.src = dataURL;
img.onload=function(){
ctx.drawImage(img,20,20);
}
// Restote canvas settings
ctx.restore();
<canvas id=canvas width=40 height=40></canvas>
I also met this problem.but after a experiment, I found that Resizing the canvas element will automatically clear all drawings off the canvas!
just try the code below
<canvas id = 'canvas'></canvas>
<script>
var canvas1 = document.getElementById('canvas')
console.log('canvas size',canvas1.width, canvas1.height)
var ctx = canvas1.getContext('2d')
ctx.font = 'Bold 48px Arial'
var f = ctx.font
canvas1.width = 480
var f1 = ctx.font
alert(f === f1) //false
</script>
I have two JS Fiddles, both with 10,000 snow flakes moving around but with two different approaches.
The first fiddle: http://jsfiddle.net/6eypdhjp/
Uses fillRect with a 4 by 4 white square, providing roughly 60 frames per second # 10,000 snow flakes.
So I wondered if I could improve this and found a bit of information on HTML5Rocks' website regarding canvas performance. One such suggestion was to pre-render the snow flakes to canvases and then draw the canvases using drawImage.
The suggestion is here http://www.html5rocks.com/en/tutorials/canvas/performance/, namely under the title Pre-render to an off-screen canvas. Use Ctrl + f to find that section.
So I tried their suggestion with this fiddle: http://jsfiddle.net/r973sr7c/
How ever, I get about 3 frames per second # 10,000 snow flakes. Which is very odd given jsPerf even shows a performance boost here using the same method http://jsperf.com/render-vs-prerender
The code I used for pre-rendering is here:
//snowflake particles
var mp = 10000; //max particles
var particles = [];
for(var i = 0; i < mp; i++) {
var m_canvas = document.createElement('canvas');
m_canvas.width = 4;
m_canvas.height = 4;
var tmp = m_canvas.getContext("2d");
tmp.fillStyle = "rgba(255,255,255,0.8)";
tmp.fillRect(0,0,4,4);
particles.push({
x : Math.random()*canvas.width, //x-coordinate
y : Math.random()*canvas.height, //y-coordinate
r : Math.random()*4+1, //radius
d : Math.random()*mp, //density
img: m_canvas //tiny canvas
})
}
//Lets draw the flakes
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < particles.length; i++) {
var flake = particles[i];
ctx.drawImage(flake.img, flake.x,flake.y);
}
}
So I wondered why am I getting such horrendous frame rate? And is there any better way to get higher particle counts moving on screen whilst maintaining 60 frames per second?
Best frame rates are achieved by drawing pre-rendered images (or pre-rendered canvases).
You could refactor your code to:
Create about 2-3 offscreen (in-memory) canvases each with 1/3 of your particles drawn on them
Assign each canvas a fallrate and a driftrate.
In each animation frame, draw each offscreen canvas (with an offset according to its own fallrate & driftrate) onto the on-screen canvas.
The result should be about 60 frames-per-second.
This technique trades increased memory usage to achieve maximum frame rates.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var mp=10000;
var particles=[];
var panels=[];
var panelCount=2;
var pp=panelCount-.01;
var maxFallrate=2;
var minOffsetX=-parseInt(cw*.25);
var maxOffsetX=0;
// create all particles
for(var i=0;i<mp;i++){
particles.push({
x: Math.random()*cw*1.5, //x-coordinate
y: Math.random()*ch, //y-coordinate
r: 1, //radius
panel: parseInt(Math.random()*pp) // panel==0 thru panelCount
})
}
// create a canvas for each panel
var drift=.25;
for(var p=0;p<panelCount;p++){
var c=document.createElement('canvas');
c.width=cw*1.5;
c.height=ch*2;
var offX=(drift<0)?minOffsetX:maxOffsetX;
panels.push({
canvas:c,
ctx:c.getContext('2d'),
offsetX:offX,
offsetY:-ch,
fallrate:2+Math.random()*(maxFallrate-1),
driftrate:drift
});
// change to opposite drift direction for next panel
drift=-drift;
}
// pre-render all particles
// on the specified panel canvases
for(var i=0;i<particles.length;i++){
var p=particles[i];
var cctx=panels[p.panel].ctx;
cctx.fillStyle='white';
cctx.fillRect(p.x,p.y,1,1);
}
// duplicate the top half of each canvas
// onto the bottom half of the same canvas
for(var p=0;p<panelCount;p++){
panels[p].ctx.drawImage(panels[p].canvas,0,ch);
}
// begin animating
drawStartTime=performance.now();
requestAnimationFrame(animate);
function draw(time){
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<panels.length;i++){
var panel=panels[i];
ctx.drawImage(panel.canvas,panel.offsetX,panel.offsetY);
}
}
function animate(time){
for(var i=0;i<panels.length;i++){
var p=panels[i];
p.offsetX+=p.driftrate;
if(p.offsetX<minOffsetX || p.offsetX>maxOffsetX){
p.driftrate*=-1;
p.offsetX+=p.driftrate;
}
p.offsetY+=p.fallrate;
if(p.offsetY>=0){p.offsetY=-ch;}
draw(time);
}
requestAnimationFrame(animate);
}
body{ background-color:#6b92b9; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
I don't think you want to create a new canvas element every time. Doing so causes a huge performance drain.
When I moved this code out of the for loop, the performance instantly improved. I think doing so will allow you to optimize your code to achieve the intended behavior:
var m_canvas = document.createElement('canvas');
m_canvas.width = 4;
m_canvas.height = 4;
var tmp = m_canvas.getContext("2d");
tmp.fillStyle = "rgba(255,255,255,0.8)";
tmp.fillRect(0, 0, 4, 4);
Check out this revised JSFiddle.
Hope this helped!
You would pre-render elements you paint many times.
Say for example you have a landscape where you paint bushes (of same form) on various locations during a game scroll. The the use of memory canvas would be ok.
For your code you should try to divide your flakes into for example 10 sizes. Thus create 10 memory canvases. Then paint these into random possitions.
In other words you copy 10 canvases 1.000 times. Not 10.000 canvases 10.000 times.
I am working with the 'canvas' element, and trying to do some pixel based manipulations of images with Javascript in FIrefox 4.
The following code leaks memory, and i wondered if anyone could help identify what is leaking.
The images used are preloaded, and this code fragment is called once they are loaded (into the pImages array).
var canvas = document.getElementById('displaycanvas');
if (canvas.getContext){
var canvasContext = canvas.getContext("2d");
var canvasWidth = parseInt(canvas.getAttribute("width"));
var canvasHeight = parseInt(canvas.getAttribute("height"));
// fill the canvas context with white (only at start)
canvasContext.fillStyle = "rgb(255,255,255)";
canvasContext.fillRect(0, 0, canvasWidth, canvasHeight);
// for image choice
var photoIndex;
// all images are the same width and height
var imgWidth = pImages[0].width;
var imgHeight = pImages[0].height;
// destination coords
var destX, destY;
// prep some canvases and contexts
var imageMatrixCanvas = document.createElement("canvas");
var imageMatrixCanvasContext = imageMatrixCanvas.getContext("2d");
// Set the temp canvases to same size - apparently this needs to happen according
// to one comment in an example - possibly to initialise the canvas?
imageMatrixCanvas.width = imgWidth;
imageMatrixCanvas.height = imgHeight;
setInterval(function() {
// pick an image
photoIndex = Math.floor(Math.random() * 5);
// fill contexts with random image
imageMatrixCanvasContext.drawImage(pImages[photoIndex],0,0);
imageMatrixData = imageMatrixCanvasContext.getImageData(0,0, imgWidth, imgHeight);
// do some pixel manipulation
// ...
// ...
// choose random destination coords (inside canvas)
destX = Math.floor(Math.random() * (canvasWidth - imgWidth));
destY = Math.floor(Math.random() * (canvasHeight - imgHeight));
// show the work on the image at the random coords
canvasContext.putImageData(imageMatrixData, destX, destY);
}, 500);
}
Oh.. mistake. The memory lookes OK after few test.
But there is another problem.
The size of used memory by tab process is growing when changing the src property of img elements...
Src property = canvas.getContext('2d').toDataURL('image/png') (changing each time);
I've tried to "delete img.src", remove node...
Changing imageMatrixData = ... to var imageMatrixData = ... might help a bit, but I doubt that is the full story. But as far as i can tell imageMatrixData is a global scope variable that you assign on every interval iteration, and that cannot be healthy especially with a big chunk of data :)
I know that getImageData used to memoryleak in Chrome but that was pre version 7, not sure how it is now, and seeing as you are talking about ff4 then that is probably very irrelevant.