Displaying a loading spinner when drawing HTML5 canvas. Event? - html

I have spent many hours searching for this, but either it's not a very common problem or my Google-fu is lacking.
The short version is I have site with an HTML5 canvas element that is displaying large complex drawing, which means it usually takes up to 10 seconds to complete.
How is it possible to hook into the drawing event? I can't find anything of the sorts, but it would really help user experience if a loading spinner was displayed as the canvas was being drawed. I have seen it done before, but not seen the "how".
Details for the experienced:
I am displaying a PDF with PDFJS, and have my own viewer instead of the supplied one. It works, but sometimes it draws the PDF very slow, hence the need of a spinner.

Here is a really simple and nasty example this now uses an event which you can fire off :).
jsFiddle : https://jsfiddle.net/CanvasCode/jwzLr4wh/1/
javascript
image = new Image();
image.src = "https://upload.wikimedia.org/wikipedia/commons/1/1e/Large_Siamese_cat_tosses_a_mouse.jpg";
var canvas = document.getElementById('myCanvas-1');
var ctx = document.getElementById('myCanvas-1').getContext('2d');
var loading = true;
var loadingText = "Loading";
var event = new Event('loading');
// Listen for the event.
canvas.addEventListener('loading', function (e) {
// Quick and nasty
setInterval(function () {
if (loading) {
console.log("update");
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, 400, 400);
ctx.font = '15pt Arial ';
ctx.fillStyle = "#000";
ctx.fillText(loadingText, 200, 200);
if (loadingText == "Loading...") {
loadingText = "Loading";
}
loadingText = loadingText + "."
}
}, 1);
}, false);
canvas.dispatchEvent(event);
setTimeout(function () {
ctx.drawImage(image, 0, 0);
ctx.font = '15pt Arial ';
ctx.fillStyle = "#000";
loading = false;
}, 5000);
This now uses an event which you can fire off manually which will set the canvas to loading.

Related

Play video on canvas and preserve the last frame/image on canvas

I'm using the following script code to draw a video on canvas:
$("#vPlayer").on('play', function (e) {
var canvas = $('canvas')[0];
var ctx = canvas.getContext('2d');
var $this = this;
canvas.width = 640;
canvas.height = 480;
(function loop() {
if (!$this.paused && !$this.ended) {
ctx.drawImage($this, 0, 0, 640, 480);
setTimeout(loop, 1000 / 30); // drawing at 30fps
}
})();
});
The code works well, but in my case, I want to change the video source (src attribute) of the video tag every 2 mins. When I set the src attr for the video and during the loading time for the video, the canvas displays white screen. How can I preserve the last image of video and do not clear the canvas?
It is a little bit weird because, when I don't set the width and height for the canvas, the last frame is preserved, but I need to set the size.
Every time you set the size of the canvas it will be cleared.
To avoid this you need to set the size at the "beginning", before you start drawing to it. In this case I would recommend you set it outside your event handler as well as the initializing of the canvas and context variable:
var canvas = $('canvas')[0];
var ctx = canvas.getContext('2d');
canvas.width = 640;
canvas.height = 480;
$("#vPlayer").on('play', function (e) {
var $this = this;
(function loop() {
if (!$this.paused && !$this.ended) {
ctx.drawImage($this, 0, 0, 640, 480);
setTimeout(loop, 1000 / 30); // drawing at 30fps
}
})();
});

html5 canvas in durandal

i am trying to use the html5 canvas element to draw in durandal without success
the markup
<body>
<canvas id="canvas"></canvas>
</body>
the js
define(function() {
function viewAttached() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var buffer = document.createElement('canvas');
buffer.width = buffer.height = 60;
var bctx = buffer.getContext('2d');
bctx.translate(30, 30);
bctx.rotate(0.5);
bctx.fillStyle = 'rgb(255, 0, 0)';
bctx.fillRect(-15, -15, 30, 30);
ctx.fillStyle = 'rgb(0, 255, 0)';
ctx.fillRect(30, 30, 30, 30);
ctx.drawImage(buffer, 50, 50);
}
var ctor = function () {
this.viewAttached = viewAttached();
return ctor;
};
});
the problem is that canvas doesn't load so all what i can see is an empty canvas. the same code is working well in jsfiddle i mean only jquery and canvas but when i transfer it to durandal it doesnt work anymore can anybody help?
In the comments you mentioned it is working with viewAttached(), but in fact in your question you mention that it is indeed not working. My best bet is that A: nemesv is right and you may be firing that function, but not in the way you intend and B: Your view isn't actually attached when you call viewAttached.
This works in Durandal 1.2 -
define(function() {
function viewAttached() {
// Do stuff
}
var viewModel = {
viewAttached: viewAttached;
};
return viewModel;
});
If you are using Durandal 2.0, do it like this -
define(function() {
function attached() {
// Do stuff
}
var viewModel = {
attached: attached;
};
return viewModel;
});
That assumes you are using the router. You don't need to fire the constructor, Durandal handles that for you. You also don't need to call viewAttached, because Durandal handles that at the proper point in the loading cycle (after the DOM is ready) http://durandaljs.com/documentation/Using-The-Router/

why is my canvas zoom/pan slowing down.

I'm a new programmer just trying to learn how to make webpages. I found a code to zoom and pan canvas elements but when I implemented it into an extJS window It started becoming sluggish. It doesn't become sluggish if the image I render is just a shape, only if It's from a file image. I thought at first I was creating instances of objects over and over but I tried deleting the objects after use and it didn't change anything. Why is my zooming slowing down?
Ext.onReady(function(){
Ext.define("w",{
width: 1000,
height: 750,
extend: "Ext.Window",
html: '<canvas id="myCanvas" width="1000" height="750">'
+ 'alternate content'
+ '</canvas>'
,afterRender: function() {
this.callParent(arguments);
var canvas= document.getElementById("myCanvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var stage = new createjs.Stage("myCanvas");
/*function addCircle(r,x,y){
var g=new createjs.Graphics().beginFill("#ff0000").drawCircle(0,0,r);
var s=new createjs.Shape(g)
s.x=x;
s.y=y;
stage.addChild(s);
stage.update();
}*///// If I use this function instead of loading an img there's no slowdown.
function setBG(){
var myImage = new Image();
myImage.src = "dbz.jpg";
myImage.onload = setBG;
var bgrd = new createjs.Bitmap(myImage);
stage.addChild(bgrd);
stage.update();
delete myImage;
delete bgrd;
};
setBG();
//addCircle(40,200,100);
//addCircle(50,400,400);
canvas.addEventListener("mousewheel", MouseWheelHandler, false);
canvas.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
var zoom;
function MouseWheelHandler(e) {
if(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)))>0)
zoom=1.1;
else
zoom=1/1.1;
stage.regX=stage.mouseX;
stage.regY=stage.mouseY;
stage.x=stage.mouseX;
stage.y=stage.mouseY;
stage.scaleX=stage.scaleY*=zoom;
stage.update();
delete zoom;
}
stage.addEventListener("stagemousedown", function(e) {
var offset={x:stage.x-e.stageX,y:stage.y-e.stageY};
stage.addEventListener("stagemousemove",function(ev) {
stage.x = ev.stageX+offset.x;
stage.y = ev.stageY+offset.y;
stage.update();
delete offset;
});
stage.addEventListener("stagemouseup", function(){
stage.removeAllEventListeners("stagemousemove");
});
});
} //end aferrender
}); //end define
Ext.create("w", {
autoShow: true });
}); //end onready
It looks like you are infinitely re-loading the BG image. After your BG image finishes loading, your onload function callback just makes it call getBG again which will just repeat the same process forever.
function setBG() {
...
myImage.onload = setBG;
...
}
I'm not sure exactly what you expect by doing this.
You really shouldn't need to delete the image. Off the top of my head, this is how I would generally load an image for use in canvas, (based on how your train of thought is working).
function setBG(){
var myImage = new Image();
myImage.src = "dbz.jpg";
myImage.onload = function(){
var bgrd = new createjs.Bitmap(this);
stage.addChild(bgrd);
stage.update();
}
};
setBG();

Three.js: tiling textures, buffering and browser compatibility

I am working on a project in html 5 canvas. For this I use the three.js library to draw some simple cubes in the shape of a simple cupboard. I have already added ambient lighting, anti-aliasing and textures. If all goes well it renders the cupboard and you can use your mouse to move it around.
Current issues:
var textureMap = map: THREE.ImageUtils.loadTexture("wood1.jpg")
textureMap.wrapS = THREE.RepeatWrapping;
textureMap.wrapT = THREE.RepeatWrapping;
textureMap.repeat.x = 100;
textureMap.repeat.y = 100;
material = new THREE.MeshLambertMaterial(textureMap);
The texture is currently stretched over each surface. I prefer tiling but can't seem to get it working. The code above was the best guess I came up with based on what I found on a site (sorry, can't share more links with my current reputation). It is commented out because it stops the script from running at all.
I used to have some lag issues. After a quick search I found a site (sorry, can't share more links with my current reputation) that told me I had to put a timeout in the main loop and that I should use a second canvas for buffering. Adding the timeout worked like a charm but I'm still curious about the buffering canvas. I'm unsure how to go about this since I'm dealing with a renderer and a canvas. I don't even know if I should still bother with a buffer, since it seems to work just fine now, although that may change in the future when I try to render more meshes at a time.
My code currently only runs in firefox for me. Chrome and Internet explorer both just show a blank screen. Any ideas what I need to change in order to fix this?
When I run the code in firefox the cupboard is completely black at first. When I move it (at all) it immediately changes to the texture. Any ideas? I could come up with a dirty fix that moves the camera up and down 1 pixel in the setup but I'd rather not.
I tried uploading the code to jsfiddle (importing the texture from tinypic) but that doesn't go so well. Either I'm importing the texture wrong or jsfiddle just doesn't like it when I use external pictures. However, if you download the texture and put it in the same folder as the code you should be able to open it in firefox (only works in firefox (see issue 4)). So if you want to see what's going on: copy the code into a .html file and download the texture.
<!DOCTYPE HTML>
<html>
<head>
<title>Canvas demo</title>
<script type="text/javascript" src="http://www.html5canvastutorials.com/libraries/Three.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
//three.js vars
var camera;
var scene;
var renderer;
var material;
var directionalLight;
//input vars
var lastX = 0;
var lastY = 450;
var clickX;
var clickY;
var mousedown;
function rerender(){
//draw
renderer.render(scene, camera);
//redraw after 25 ms (the 25 ms delay reduces lag)
setTimeOut( requestAnimFrame(function(){rerender()}), 25);
}
$(document).ready(function() {
//initialize renderer
renderer = new THREE.WebGLRenderer({antialias : true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.domElement.id = "visiblecanvas";
document.body.appendChild(renderer.domElement);
//camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.y = -450;
camera.position.z = 400;
camera.rotation.x = 45.2;
//scene
scene = new THREE.Scene();
//material
//non-working tiled texture
/*
var textureMap = map: THREE.ImageUtils.loadTexture("wood1.jpg")
textureMap.wrapS = THREE.RepeatWrapping;
textureMap.wrapT = THREE.RepeatWrapping;
textureMap.repeat.x = 100;
textureMap.repeat.y = 100;
*/
//workingg stretched texture
material = new THREE.MeshLambertMaterial({map: THREE.ImageUtils.loadTexture("wood1.jpg")});
//the cupboard
//cube
var cube1 = new THREE.Mesh(new THREE.CubeGeometry(300,50,10), material);
cube1.overdraw = true;
scene.add(cube1);
//cube
var cube2 = new THREE.Mesh(new THREE.CubeGeometry(300,10,300), material);
cube2.overdraw = true;
cube2.position.z += 150;
cube2.position.y += 20;
scene.add(cube2);
//cube
var cube3 = new THREE.Mesh(new THREE.CubeGeometry(10,50,300), material);
cube3.overdraw = true;
cube3.position.z += 150;
cube3.position.x += 145;
scene.add(cube3);
//cube
var cube4 = new THREE.Mesh(new THREE.CubeGeometry(10,50,300), material);
cube4.overdraw = true;
cube4.position.z += 150;
cube4.position.x -= 145;
scene.add(cube4);
//cube
var cube5 = new THREE.Mesh(new THREE.CubeGeometry(300,50,10), material);
cube5.overdraw = true;
cube5.position.z += 300;
scene.add(cube5);
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0x555555);
scene.add(ambientLight);
// add directional light source
directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
//mousedown event
$('#visiblecanvas').mousedown(function(e){
clickX = e.pageX - this.offsetLeft + lastX;
clickY = e.pageY - this.offsetLeft + lastY;
mousedown = true;
});
//mousemove event, act if mousedown
$('#visiblecanvas').mousemove(function(e){
if(mousedown) {
var xDiff = e.pageX - this.offsetLeft - clickX;
var yDiff = e.pageY - this.offsetLeft - clickY;
lastX = -xDiff;
lastY = -yDiff;
camera.position.x = lastX;
camera.position.y = -lastY;
rerender();
}
delay(5);
});
//mouseup event
$('#visiblecanvas').mouseup(function(e){
mousedown = false;
});
//mouseleave event (mouse leaves canvas, stop moving cupboard)
$('#visiblecanvas').mouseleave(function(e){
mousedown = false;
});
rerender();
});
//request new frame
window.requestAnimFrame = (function (callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
</script>
</body>
</html>
Thanks for taking a look! hope you can answer any of my questions.
1) Use JavaScript console to debug the code.
2) The jsfiddle is missing jQuery.
3) This line:
var textureMap = map: THREE.ImageUtils.loadTexture("wood1.jpg")
must be:
var textureMap = THREE.ImageUtils.loadTexture("wood1.jpg");
See the extra map: and the missing semicolon.
4) The repeat texture parameters are too big. Default value is 1, to repeat the texture one time. Try to change that values to 2, 3, ... and so.
5) delay function is undefined, remove it
6) setTimeOut function is undefined (setTimeout?, JavaScript is case sensitive)
7) Rewrite the rendererer function to this:
function rerender(){
requestAnimFrame(rerender);
renderer.render(scene, camera);
}
Take a look at this: http://paulirish.com/2011/requestanimationframe-for-smart-animating/
8) Remove the call to the rerender function inside the mousemove event
9) IE and WebGL?
Try:
function rerender(){
requestAnimFrame(rerender);
renderer.render(scene, camera);
}

Frame by frame animation in HTML5 with canvas

I have a flash animation I am trying to convert to HTML5. Now I have taken out all the images. For example in the hand animation, I have taken images of all hand images. I have made the canvas with the base drawing but I don't know how to replace those images frame by frame.
function draw(){
var canvas = document.getElementById('canvas');
if(canvas.getContext){
// canvas animation code here:
var ctx = canvas.getContext('2d');
var lhs = new Image();
lhs.src = "images/left_hnd_1.png";
lhs.onload = function(){
ctx.drawImage(lhs, 293, 137);
}
} else {
// canvas unsupported code here:
document.getElementById('girl').style.display = "block";
}
}
Now I have three more frame for this image. left_hnd_2.png, left_hnd_3.png & left_hnd_4.png. I would've used one image but the difference in frames is way too much for it to be done with one image. How can I animate this with the time differences I want.
Any ideas would be greatly appreciated. Thanks!
Try this:
var imgNumber = 1;
var lastImgNumber = 4;
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function(){
ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height );
ctx.drawImage( img, 0, 0 );
};
var timer = setInterval( function(){
if (imgNumber>lastImgNumber){
clearInterval( timer );
}else{
img.src = "images/left_hnd_"+( imgNumber++ )+".png";
}
}, 1000/15 ); //Draw at 15 frames per second
An alternative, if you only have 4 images, would be to create a single huge image with all four in a 'texture atlas', and then use setTimeout or setInterval to call drawImage() with different parameters to draw different subsets of the image to the canvas.
This worked for me as well! For some reason, it didn't work when I had used the OP's opening code: function draw(){
However when I used: window.onload = function draw() { the animation plays on the canvas. I'm also using about 150 PNG images with an Alpha channel so this is a great way to bring 'video' or create composites to the iPad/iPhone. I confirm that it does work on iPad iOS 4.3.