Ok, so, I'm working on a project in HTML5 and JavaScript. I'm trying to resize a Canvas, but it won't work. I don't think it's my browser, though, because I am using the latest version of FireFox. I've also researched this issue for a while, and I am confident I'm doing this correctly. So, I don't know why it won't work.
Here's my Code:
var level = 1;
var levelImg = undefined;
var width = 0;
var height = 0;
var cnvs = document.getElementById("cnvs").getContext("2d");
width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
height = window.innerHeight
|| document.documentElement.cleintHeight
|| document.body.cleintHeight;
cnvs.width = width;
cnvs.height = height;
window.onload = function Init(){
levelImg = document.getElementById("level" + level);
setInterval("Draw()", 3);
}
function Draw(){
//Clear the Screen
cnvs.clearRect(0, 0, width, height);
//Draw stuff
DrawLevel();
}
function DrawLevel(){
cnvs.fillRect(0, 0, 10, 10);
}
Any help is appreciated. Thanks.
First correct all the typos in you code.
Use the browser console to detect errors.
Difference between canvas and the context
var cnvs = document.getElementById("cnvs").getContext("2d");
cnvs variable is not a canvas but the context for the canvas.
Canvas is the element and context is the object used to write in the canvas.
To access the canvas you need to do this:
var canvas = document.getElementById("cnvs");
var cnvs = canvas.getContext('2d'); //context
Now when you are trying to change the canvas with, you use canvas, not cnvs.
canvas.width = width;
canvas.height = height;
SetInterval expects a function and a number value that represents milliseconds.
"Draw()" is a string, not a function, and 3 is a really small number between each time the browser draws on canvas. It works, but it's very inefficient.
Other point about setInterval. Avoid it by using requestAnimationFrame() instead.
Take a look here: setTimeout or setInterval or requestAnimationFrame
Defining var levelImg = undefined has no utility. It can be replaced by var levelImg;
Related
I have a Javascript function setup to dynamically generate thumbnails from a given embedded HTML5 video once it loads. This works fine on other browsers. The problem arises with IE11. For some reason, it's just not outputting anything despite working perfectly on Firefox and Chrome.
I have a section of the site which has several HTML5 videos and they need to generate their respective thumbnails based on the first frame. I've done a good bit of researching around and I can't seem to find any IE11 compatibility issues with my code.
Here is the code I have now:
var vids = document.querySelectorAll('[id^=video-]');
for(var i = 0; i < vids.length; i++){
if(vids[i].tagName == 'VIDEO'){
$("#video-" + (i+1)).on("loadeddata", generate_handler(i+1));
}
...
}
generate_handler calls the shoot function (had to do it this way because of scoping issues in the loop),
function generate_handler(j) {
return function(event) {
shoot(j);
};
}
and the shoot function goes as follows:
function shoot(num){
var video = document.getElementById('video-' + num);
var output = document.getElementById('videothumbnail-' + num);
if(output.childNodes.length == 1){
var canvas = capture(video);
output.appendChild(canvas);
}
}
and finally, the capture function is as follows:
function capture(video) {
var canvas = document.createElement('canvas');
var w = 48;
var h = 40;
canvas.width = w;
canvas.height = h;
canvas.style.marginTop = "4px";
canvas.style.width = w + "px";
canvas.style.height = h + "px";
canvas.style.zIndex = "-999";
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, w, h);
return canvas;
}
Essentially, as a TL;DR: When the data of the video is loaded, it calls this function to shoot a screengrab and add it to the thumbnail div. In order to do that, it draws the video to a canvas. This is the order the functions are called:
on('loadeddata', generate_handler()) -> shoot() -> capture()
What's strange is that after some simple tests with console.log, it actually is reaching the inside of capture(), which inclines me to believe it's a compatibility issue with something in there, or with appendChild().
What this should do (and does do on Firefox/Chrome) is draw a 48x44px thumbnail of the HTML5 video being loaded on the page. Instead, on IE11 it displays nothing.
I want to resize a canvas, but when I do, the context gets reset, losing the current fillStyle, transformation matrix, etc. The ctx.save() and ctx.restore() functions didn’t work as I initially expected:
function resizeCanvas(newWidth, newHeight) {
ctx.save();
canvas.width = newWidth; // Resets context, including the save state
canvas.height = newHeight;
ctx.restore(); // context save stack is empty, this does nothing
}
After researching for a while, I can’t seem to find a good way to save and restore the canvas context after resizing. The only way I can think of is manually saving each property, which seems both messy and slow. ctx.save() doesn’t return the saved state either, and I don’t know of a way to access the context’s stack.
Is there a better way, or am I doomed to use something like this:
function resizeCanvas(newWidth, newHeight) {
let fillStyle = ctx.fillStyle;
let strokeStyle = ctx.strokeStyle;
let globalAlpha= ctx.globalAlpha;
let lineWidth = ctx.lineWidth;
// ...
canvas.width = newWidth;
canvas.height = newHeight;
ctx.fillStyle = fillStyle;
ctx.strokeStyle = strokeStyle;
ctx.globalAlpha= globalAlpha;
ctx.lineWidth = lineWidth;
// ...
}
I also found this somewhat annoying and I'm very interested to see if there are other solutions to this. I'd also like to see a reason as to why the state has to be reset and the state stack cleared when the canvas is resized. Really it's unintuitive behavior that you wouldn't expect and even MDN doesn't mention it, so it's probably pretty easy to make this mistake.
The absolute best way is to restructure your code in such a manner that all draw operations can be redone after the canvas is resized since you're gonna have to redraw everything anyway, you might as well be setting all the ctx states after you resize anyway. (i.e. make and utilize a 'draw' function that can be called after you resize the canvas)
If you really want to keep the state then I'd recommend making your own save/restore state stack and perhaps a resize function while you're at it. I'm not gonna judge you and say it's a bad idea...
Let's say I wanted to resize the canvas to the exact width and height of some text before drawing the text.
Normally I would have to set the font for the text first, then measure the text, then resize the canvas, but since resizing the canvas resets the state, I'd then have to set the font again like so:
ctx.font = "48px serif"
width = ctx.measureText('Hello World').width
canvas.width = width
ctx.font = "48px serif"
As an (admittedly over-complicated) workaround, I'd save and restore the state using my custom save-restore functions before and after resizing, respectively.
And yes, I do see the irony in replacing one extra line of code with around 30 or so extra lines of code in this particular example.
let canvas = document.querySelector('canvas')
, ctx = canvas.getContext('2d')
, stack = []
function save(ctx){
let state = {}
for(let property in ctx){
if(property == 'canvas')
continue
if(typeof ctx[property] == 'function')
continue
state[property] = ctx[property]
}
stack.push(state)
}
function restore(ctx){
let state = stack.pop() || {}
for(let property in state){
ctx[property] = state[property]
}
}
function resize(ctx, width, height){
save(ctx)
ctx.canvas.width = width || canvas.width;
ctx.canvas.height = height || canvas.height;
restore(ctx)
}
////////////// EXAMPLE ////////////////
let index = 0
, words = ["Our", "Words", "Are", "Dynamic"];
(function change(){
let font_size = ~~(Math.random() * 150 + 16)
let word = words[index]
index = (index + 1) % words.length
ctx.font = font_size+"px serif"
ctx.textBaseline = "hanging"
ctx.fillStyle = "white"
resize(ctx, ctx.measureText(word).width, font_size)
ctx.fillText(word, 0, 0)
setTimeout(change, 750)
})()
canvas{
background-color : orange
}
<canvas></canvas>
The accepted answer no longer works because some properties are deprecated and hence give runtime error when trying to set deprecated properties.
Here's a fix for Khauri MacClain's answer.
function save(ctx){
let props = ['strokeStyle', 'fillStyle', 'globalAlpha', 'lineWidth',
'lineCap', 'lineJoin', 'miterLimit', 'lineDashOffset', 'shadowOffsetX',
'shadowOffsetY', 'shadowBlur', 'shadowColor', 'globalCompositeOperation',
'font', 'textAlign', 'textBaseline', 'direction', 'imageSmoothingEnabled'];
let state = {}
for(let prop of props){
state[prop] = ctx[prop];
}
return state;
}
function restore(ctx, state){
for(let prop in state){
ctx[prop] = state[prop];
}
}
function resize(ctx, width, height){
let state = save(ctx);
ctx.canvas.width = width || canvas.width;
ctx.canvas.height = height || canvas.height;
restore(ctx, state);
}
I was trying to take a partially-drawn canvas -- generated by a library, and so not really possible to resize before all the draw commands were issued -- and expand it to add some text next to it. There's a deleted answer on this question by #markE that helped me out. See, you can make a new canvas element of arbitrary size, and "draw" the old (smaller) canvas onto it:
const libCanvas = myLib.getCanvas();
const newCanvas = document.createElement("canvas");
const ctx = newCanvas.getContext("2d");
ctx.font = MY_FONT;
newCanvas.width = libCanvas.width + ctx.measureText(myStr).width;
ctx.drawImage(libImage, 0, 0);
ctx.font = MY_FONT; // Changing width resets the context font
ctx.fillText(myStr, libCanvas.width, libCanvas.height / 2);
Now newCanvas is as wide as the old canvas, plus the width of the text I wanted to draw in, and has the contents of the library-generated canvas on the left side, and my label text on the right side.
I have a canvas that you can draw things with mouse.. When I click the button It has to capture the drawing and add it right under the canvas, and clear the previous one to draw something new..So first canvas has to be static and the other ones has to be created dynamically with the drawing that I draw .. What should I do can anybody help
here is jsfiddle
http://jsfiddle.net/dQppK/378/
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0;
You can create a new canvas the same way you’d create any element:
var newCanvas = document.createElement('canvas');
Then you can copy over your old canvas:
newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height;
oldCanvas.parentNode.replaceChild(newCanvas, oldCanvas);
ctx = newCanvas.getContext('2d');
But if you’re just looking to clear your drawing surface, what’s wrong with clearRect?
ctx.clearRect(0, 0, canvas.width, canvas.height);
Or, in your case, another fillRect. Updated demo
here's the function i use for this, it is part of a library i made and use to ease a few things about canvas.
I just put it on github in case other function might be be of use, i'll have to make a readme later...
https://github.com/gamealchemist/CanvasLib
with namespaceing removed, the code is as follow to insert a canvas :
// insert a canvas on top of the current document.
// If width, height are not provided, use all document width / height
// width / height unit is Css pixel.
// returns the canvas.
insertMainCanvas = function insertMainCanvas (_w,_h) {
if (_w==undefined) { _w = document.documentElement.clientWidth & (~3) ; }
if (_h==undefined) { _h = document.documentElement.clientHeight & (~3) ; }
var mainCanvas = ga.CanvasLib.createCanvas(_w,_h);
if ( !document.body ) {
var aNewBodyElement = document.createElement("body");
document.body = aNewBodyElement;
};
document.body.appendChild(mainCanvas);
return mainCanvas;
}
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.
I'll post a link since theres to much to post here:
http://hem.bredband.net/noor/canvas.htm
My goal is to make the picture fit inside the window with the width of the image being the same as the window, even after resize. If the pictures height becomes to big for the window then the picture should resize itself according to the height and not the width.
Somewhere along my code there is something wrong.. forgive me if this is stupid, i am still learning..
Thanks!
I think this is the behavior you are trying to achieve. I refactored it so you only need to create one Image object (and changed the variable names to English so I could follow the code easier). Hopefully it's clear how the code works. Feel free to ask in the comments if you have any questions about it. The body onload property will need to be changed to "setupBackground();" instead of "draw();" to work with this code.
function setupBackground() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function draw() {
canvas.width = 0;
canvas.height = 0;
var divHeight = div.scrollHeight;
var divWidth = div.scrollWidth;
var yScale = divHeight / img.height;
var xScale = divWidth / img.width;
var newImgHeight = img.height * xScale;
var newImgWidth = divWidth;
if (divHeight >= newImgHeight) {
newImgHeight = divHeight;
newImgWidth = img.width * yScale;
}
canvas.width = divWidth;
canvas.height = divHeight;
ctx.drawImage(img,0,0,newImgWidth,newImgHeight);
}
var img = new Image();
img.onload = function() {
window.onresize = draw;
draw();
}
img.src = 'ad.jpg';
};
As far as I know. You only want to make one call to getContext(). I didn't really analyze the code very much past this point though.