JsFiddle (note: It doesn't show anything, it's merely a way for me to show my code in a neater format) http://jsfiddle.net/h6tVR/
I am new to HTML5 canvas and have decided to play about and see what I can do with it. So far I've been able to draw a locally hosted image onto the canvas and even do a bit of basic tiling:
window.onload = function(){
var GameClosure = function() {
var canv = document.getElementById("canv");
var canvContext = canv.getContext("2d");
var sprite = new Image();
sprite.src = "sprite.png"
var tile = new Image();
tile.src = "tile.png"
function loadSprite(){
sprite.onload = function(){
canvContext.drawImage(sprite, 50, 50);
};
}
function loadTiles(){
tile.onload = function(){
for(var i = 0; i < 800; i += 16){
for(var r = 0; r < 608; r += 16){
canvContext.drawImage(tile, i, r);
}
}
};
}
return{
loadTiles: loadTiles,
loadSprite: loadSprite
};
}();
GameClosure.loadTiles();
GameClosure.loadSprite();
}
I am getting an odd problem with this. When I load it up, the majority of the time, only the tiles will load up. I've tried a couple of things so far, I've switched the GameClosure.loadTiles() and GameClosure.loadSprite(); calls to see if the load order made any difference. It doesn't. I even tried creating a second context and assigning the tiles to one and the sprite to another, but this made no difference. Commenting out the tile call produces the sprite correctly.
It gets even odder. I was refreshing the page rapidly and I noticed that occasionally (with no pattern to it, sometime it could happen 3 times in a row, other time once in 20) the tiles would load AND the sprite would load on top as I would expect it to.
Can this be fixed? My only guess is that my code is running somewhat asyncronously and the for loops creating the tiles are completing after the sprite has been loaded, but looking at my code I don't see where this could be happening.
Separate the concerns. Wait for all resources to be loaded (and the document), then launch your game. Always be sure to hook event handler before assigning src to avoid 'random' (cache-related, in fact) behaviors.
When you set an onload handler, your javascript will continue while the resource loads in the background. The handler will be executed when the resource has loaded. You have no way to tell when that will happen and in what order.
When you have multiple resources and want to call your draw-function the moment the last one has loaded, you could have a global preloader-object. Each onload-handler should call a function on the preloader to inform it that the resource has loaded. That function should check if all resources have reported in, and when that's the case execute the draw-function.
Also, when you set an onload-handler and the resource is already loaded. When you set .src and the resource is in the browsers cache, it will get loaded instantly. So you always need to first set .onload and then set .src.
Related
I am trying to get the pixel RGBA data from a canvas for further processing. I think the canvas is actually a Unity game if that makes a difference.
I am trying to do this with the canvas of the game Shakes and Fidget. I use the readPixels method from the context.
This is what I tried:
var example = document.getElementById('#canvas');
var context = example.getContext('webgl2'); // Also doesn't work with: ', {preserveDrawingBuffer: true}'
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
But all pixels are black apparently (which is not true obviously).
Edit: Also, I want to read the pixels multiple times.
Thanks everyone for your answers. The answer provided by #Kaiido worked perfectly for me :)
You can require a Canvas context only once. All the following requests will either return null, or the same context that has been created before if you passed the same options to getContext().
Now, the one page you linked to didn't pass the preserveDrawingBuffer option when creating their context, which means that to be able to grab the pixels info from there, you will have to hook up in the same event loop as the one the game loop occur.
Luckily, this exact game does use a simple requestAnimationFrame loop, so to hook up to the same event loop, all we need to do is to also wrap our code in a requestAnimationFrame call.
Since callbacks are stacked, and that they do require the next frame from one such callback to create a loop, we can be sure our call will get stacked after their.
I now realize it might not be obvious, so I'll try to explain further what requestAnimationFrame does, and how we can be sure our callback will get called after Unity's one.
requestAnimationFrame(fn) pushes fn callback into a stack of callbacks that will all get called at the same time in First-In-First-Out order, just before the browser will perform its paint to screen operations. This happens once in a while (generally 60 times per second), at the end of the closest event loop.
It can be understood as a kind of setTimeout(fn , time_remaining_until_next_paint), with the main difference that it is guaranteed that requestAnimationFrame callback executor will get called at the end of the event loop, and thus after other js execution of this event loop.
So if we were to call requestAnimationFrame(fn) in the same event loop that the one where the callbacks will get called, our fake time_remaining_until_next_paint would be 0, and fn will get pushed at the bottom of our stack (last in, last out).
And when calling requestAnimationFrame(fn) from inside the callbacks executor itself, time_remaining_until_next_paint would be something around 16, and fn will get called among the first ones at the next frame.
So any calls to requestAnimationFrame(fn) made from outside of the requestAnimationFrame's callbacks executor is guaranteed to be called in the same event loop than a requestAnimationFrame powered loop, and to be called after.
So all we need to grab these pixels, is to wrap the call to readPixels in a requestAnimationFrame call, and to call it after Unity's loop started.
var example = document.getElementById('#canvas');
var context = example.getContext('webgl2') || example.getContext('webgl');
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
requestAnimationFrame(() => {
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
// here `pixels` has the correct data
});
Likely you either need to read the pixels in the same event as they are rendered, or you need to force the canvas to use preserveDrawingBuffer: true so you can read the canvas at any time.
To do the second override getContext
HTMLCanvasElement.prototype.getContext = function(origFn) {
const typesWeCareAbout = {
"webgl": true,
"webgl2": true,
"experimental-webgl": true,
};
return function(type, attributes = {}) {
if (typesWeCareAbout[type]) {
attributes.preserveDrawingBuffer = true;
}
return origFn.call(this, type, attributes);
};
}(HTMLCanvasElement.prototype.getContext);
Put that at the top of the file before the Unity game OR put it in a separate script file and include it before the Unity game.
You should now be able to get a context on whatever canvas Unity made and call gl.readPixels anytime you want.
For the other method, getting pixels in the same event, you would instead wrap requestAnimationFrame so that you can insert your gl.readPixels after Unity's use of requestAnimationFrame
window.requestAnimationFrame = function(origFn) {
return function(callback) {
return origFn(this, function(time) {
callback(time);
gl.readPixels(...);
};
};
}(window.requestAnimationFrame);
Another solution would be to use a virtual webgl context. This library shows an example of implementing a virtual webgl context and shows an example of post processing the unity output
note that at some point Unity will likely switch to using an OffscreenCanvas. At that point it will likely require other solutions than those above.
Alternatively, you can stream the content of the canvas to a video element, draw the video's content to another canvas and read the pixels there.
This should be independent of the frame being painted by a requestAnimationFrame, but is asynchronous.
We need a video, another canvas and a stream:
var example = document.getElementById('#canvas');
var stream=example.captureStream(0);//0 fps
var vid=document.createElement("video");
vid.width=example.width;
vid.height=example.height;
vid.style="display:none;";
document.body.appendChild(vid);
var canvas2=document.createElement("canvas");
canvas2.width=example.width;
canvas2.height=example.height;
canvas2.style="display:none;";
var width=example.width;
var height=example.height;
body.appendChild(canvas2);
var ctx = canvas2.getContext('2d');
Now you can read the game canvas by requesting a frame from the stream, pushing it into the video and painting the video onto our canvas:
stream.requestFrame();
//wait for the game to draw a frame
vid.srcObject=stream;
//wait
ctx.drawImage(vid, 0, 0, width, height, 0, 0, width, height);
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
ctx.readPixels(0, 0, ctx.drawingBufferWidth, ctx.drawingBufferHeight, ctx.RGBA, ctx.UNSIGNED_BYTE, pixels);
I have some code that renders specific DOM elements to canvas, sort of like taking a screenshot. (It's custom code built for a very particular DOM structure as part of a graphics editing game, not a general library like rasterHTML.js)
The code flow is pretty procedural:
get some DOM elements of class A and draw them to canvas
get some DOM elements of class B and draw them to canvas
The trouble is that step 1 is very intensive compared to step 2, and doesn't finish drawing before step 2, screwing up the layers (in reality I have several canvases doing several things at once, and a canvas is unfortunately resized before all the drawing is completed). I've tried to replicate this in this fiddle: https://jsfiddle.net/1hucuLg9/
In pseudocode:
context.drawComplexSVG(); // slow
context.drawSimpleImage(); //fast
//canvas now has an SVG drawn on top of an image, not underneath.
I've seen a lot of setTimeout examples in an attempt to get one function to execute after another, but to me this seems to be a bit of a hack ... ideally I don't want to delay execution, just execute everything in strict order. I've also seen the idea of postMessage floated to achieve this but I've no idea how you pass messages to yourself. What's the correct way to ensure a function/line is fully executed (or in my case, the canvas is fully updated - is it the same thing?) before proceeding?
"getting some DOM elements" should be synchronous and do not require any complex code to handle sequencing draw operations.
The problem you are facing in your fiddle is that you are dynamically loading some images to draw - and for those, you need to wait, which makes the operation asynchronous.
Promises are here for your rescue, but you'll have to use them correctly. Just calling resolve right away like you did in your own answer will ensure some asynchrony, but that's not less fragile than a setTimeout approach. Instead, you should always create the promise at the heart of the asynchrony, in your case the image loading:
function loadImage(src) {
return new Promise(resolve, reject) {
var img = new Image();
img.onload = function(){ resolve(img); };
img.onerror = reject;
img.src = src;
});
}
so that you can use it in your canvas drawing code:
function drawSwatches(currentSwatch) {
var data = …;
var url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(data);
return loadImage(url).then(function(img) {
ctx.drawImage(img, 0, 0, vw, vh);
});
}
and then chain these properly:
var swatches = Array.from(document.getElementsByClassName("swatch"));
swatches.reduce(function(promise, swatch) {
return promise.then(function() {
return drawSwatches(swatch);
});
}, Promise.resolve()).then(function() {
var otherObjects = document.getElementsByClassName("otherObjects");
for (var i=0; i<otherObjects.length; i++) {
drawOtherObjects(otherObjects[i], 0, 0, 100, 100);
}
});
Well, it seems that this will get the job done:
var promise = new Promise(function(resolve){
context.drawComplexSVG();
resolve();
}
promise.then(function(){
context.drawSimpleImage();
}
This is a very simple use case. Show an element (a loader), run some heavy calculations that eat up the thread and hide the loader when done. I am unable to get the loader to actually show up prior to starting the long running process. It ends up showing and hiding after the long running process. Is adding css classes an async process?
See my jsbin here:
http://jsbin.com/voreximapewo/12/edit?html,css,js,output
To explain what a few others have pointed out: This is due to how the browser queues the things that it needs to do (i.e. run JS, respond to UI events, update/repaint how the page looks etc.). When a JS function runs, it prevents all those other things from happening until the function returns.
Take for example:
function work() {
var arr = [];
for (var i = 0; i < 10000; i++) {
arr.push(i);
arr.join(',');
}
document.getElementsByTagName('div')[0].innerHTML = "done";
}
document.getElementsByTagName('button')[0].onclick = function() {
document.getElementsByTagName('div')[0].innerHTML = "thinking...";
work();
};
(http://jsfiddle.net/7bpzuLmp/)
Clicking the button here will change the innerHTML of the div, and then call work, which should take a second or two. And although the div's innerHTML has changed, the browser doesn't have chance to update how the actual page looks until the event handler has returned, which means waiting for work to finish. But by that time, the div's innerHTML has changed again, so that when the browser does get chance to repaint the page, it simply displays 'done' without displaying 'thinking...' at all.
We can, however, do this:
document.getElementsByTagName('button')[0].onclick = function() {
document.getElementsByTagName('div')[0].innerHTML = "thinking...";
setTimeout(work, 1);
};
(http://jsfiddle.net/7bpzuLmp/1/)
setTimeout works by putting a call to a given function at the back of the browser's queue after the given time has elapsed. The fact that it's placed at the back of the queue means that it'll be called after the browser has repainted the page (since the previous HTML changing statement would've queued up a repaint before setTimeout added work to the queue), and therefore the browser has had chance to display 'thinking...' before starting the time consuming work.
So, basically, use setTimeout.
let the current frame render and start the process after setTimeout(1).
alternatively you could query a property and force a repaint like this: element.clientWidth.
More as a what is possible answer you can make your calculations on a new thread using HTML5 Web Workers
This will not only make your loading icon appear but also keep it loading.
More info about web workers : http://www.html5rocks.com/en/tutorials/workers/basics/
I've got a deceptively simple blog project in the works, where I'm trying to bring together Isotope Jquery (for layout/filtering/sorting), Infinite Scroll, and dynamic loading of all blog excerpts via Ajax (so filtering and sorting is applied to all excerpts before the user scrolls down the page (after which time they're loaded into the dom and then accessible)).
This question primarily deals with getting the blog post excerpt data via Ajax, to then be passed into Isotope filtering code. I'm not sure of the best way to do this, but am currently trying to loop through each page (of blog posts excerpts) with an ajax request and then access the data as one whole.
I've come across two different methods to loop through the ajax requests, each using then when jquery statements. The first is using the method give in this SO answer, the other is simply putting the entire then when statement inside of an $.each statement.
Method 1:
var pageCount = 15;
var pageCountArray = [];
for (var i = 1; i != pageCount; ++i) pageCountArray.push(i);
var deferreds = [];
$(pageCountArray).each(function() {
var pageNumber = this;
deferreds.push(
$.get('/page/' + pageNumber)
)
$.when.apply($, deferreds)
.then(function(data){
console.log(data);
// this outputs data as a string from the first page, then a list of objects
console.log(typeof(data));
// string
// 13 - object
});
});
Slight aside: Any ideas as to why this is outputting one string and then objects?
Method 2:
var pageCount = 15;
var pageCountArray = [];
for (var i = 1; i != pageCount; ++i) pageCountArray.push(i);
$(pageCountArray).each(function(data) {
var pageNumber = this;
$.when(
$.get('/page/' + pageNumber)
).then(function() {
console.log(data);
// this outputs 14 strings of data
console.log(typeof(data));
// 14 - string
})
});
I haven't yet figured out how to incorporate the Ajaxed data into my Isotope filter function, but I think I'll need to parse this into HTML first. Still getting my footing with javascript... in this case is one of these data types (objects vs strings) easier to parse into HTML? I suppose that's the key to my answer?
Much obliged for insights.
PS: Bonus points for anyone who might know of a better way to achieve this in a different way that somehow dovetails into Isotope/Infinite Scroll nicely (perhaps in a way that's more intended to play nice with these plugins... I've been unsuccessful in my searching).
PPS: The second method feels much cleaner... anyone know of a reason that it's not a good approach (using when then inside of an .each loop)?
Wow, this is a largely scoped question no wonder there aren't any responses. This is a massive question so I will do my very best to help. I have created many sites that include the sort/filtering of Isotope while using AJAX preload's with infinite scrolling so here is one of the simplest examples I have already written out...
First I must mention that this whole thing works much better with David DeSandro's ImagesLoaded plugin. This is mostly because it allows you to place a callback function (function to be executed once an event occurs) attached to the loading event of the final image in a given container. Wow that was wordy. How to put that better... It basically asks the container, are you done loading yet? No? How about now? You're loaded? Ok please do this function now then...
With that being implemented I would start with this code in my onLoad event like so...
$(function() {
extendJQ_PreLoad(); //I Will Get To This Function In A Min
//Use ImagesLoaded Plugin To Control Load Time Sync
$(container).imagesLoaded(function() {
cont.isotope({
itemSelector: ".box", //This is the class I use on all my images to sort
layoutMode: "masonry",
isOriginLeft: true,
isFitWidth: true,
filter: "*",
masonry: {
columnWidth: ".box"
}
});
preLoadNextImgSet(); //I Will Get To This Function In A Min
});
});
Ok so let's break this down. The ImagesLoaded plugin stops the Isotope plugin instantiation from happening before there are images present to sort/filter/load and/or handle. This is step 1. Step 2 would be to then start looking at the actual isotope plugin instantiation. I am telling it to use Masonry plugin as its layout style and then I am passing in an object literal with options under the array key 'masonry'. The array key here that is named masonry is the same as any instantiation you would have normally done in the past with the stand alone Masonry plugin (non-isotope or isotope-2).
Step 3 to look at here would be my beginning call to extendJQ_PreLoad();. This function is the function I wrote to let JQuery know that I need to extend it's core functionality in order to capacitate preloading any images I give it, as an array. Like so...
function extendJQ_PreLoad() {
$.preloadImages = function(args) {
for (var i = 0; i < args.length; i++) {
$("<img />").attr("src", args[i]);
}
}
} //end function
This is just a simple iterator and nothing fancy, it allows the images to be preloaded by using a neat trick associated with the DOM. If you load images in this way it loads then into memory but not into the DOM meaning it is loaded and hidden. Once you then insert this image anywhere it will insert very quickly as it is now loaded in cache and awaiting placement. You can view more about this here.
Finally the last to look at is my call to my preload function. This is a very simple call to a php file that simply goes and looks for the next set of images in order, if there is any to find. If it gets some images then it begins adding it to a temporary div in memory (again not on the DOM to be seen) and is now setup for simple DOM traversal. Let's view the function to dissect its functionality...
function preLoadNextImgSet() {
$.post('AjaxController/ajaxPreload_Gallery.php', {currStart: start, currSize: loadSize}, function(data) {
if(data!="") {
var y = $(document.createElement("div")).append(data).find("a"),
found = [];
y.each(function() {
found[found.length] = "img/gallery/" + $(this).text();
});
$.preloadImages(found);
}
});
} //end function
In this example I have two global variables living in my browser window from JavaScript that I would have declared. A start and a loadSize variable. The start variable represents the current place in our list of images that we currently are at and the loadSize variable sets a limit on how many images to preload each time.
Now that the variables are set and sent in to the PHP file via the $.post function, we can use the PHP file to find the appropriate images in order and have them loaded into memory awaiting usage. Whatever is returned here to the y variable gets iterated over by the each function and then preloaded. Once this functions scope is exited the imaginary div will be deleted and sent to garbage as it is not used simple iterated over.
Ok, now. Its been a journey but we are almost ready to begin the final method here. Let's first go back and look at what the first imagesLoaded call was doing now that we know the new functionality added in these functions. The imagesLoaded call in the DOM-Ready event has a call in its very bottom piece that preloads the images.... why? This is because once the page loads and the initial images are loaded into the isotope container, we need the page to now use this idle time to begin already loading the next set. So in other words once the images are placed and sorted and happy to just sit there, the next loadSize amount of images will be loaded and waiting for you to place them.
Now for the final function. This function is a generic function thats sole purpose is to load in the current preloaded images into the DOM officially and then to ask for the next set to be loaded. However what on earth would be calling this function? This is where the lazyloading or infinitescroll becomes useful to us. Somewhere in your page you need to add this function in...
$(window).scroll(function(){
scrollTop = $(window).scrollTop(),
windowHeight = $(window).height(),
docuHeight = $(document).height();
//AJAX Data Pull
if(((scrollTop + windowHeight)+35) >= docuHeight){
getNextImages();
}
});
This function is the magic function that allows the infinitescroll effect to occur. I have added 35 pixels or so of padding (the +35 randomly in my code) because sometimes you want it to load close to the end of the page but not quite the actual end of the page.
Ok so now that this is setup when we reach the end of the page this function will want to get all of the next images generically like we had mentioned. The function of mine looks like this...
function getNextImages() {
cont = $(container);
$.post('AjaxController/ajaxPortfolio_Gallery.php', {currStart: start, currSize: loadSize}, function(data) {
if(data!="") {
//Append New Photos Inside <a> Element Tag
var y = $(document.createElement("div")).append(data).find("a");
cont.append(y);
//Fix Image Layouts
cont.imagesLoaded(function() {
//Feed Isotope Layout The New Items
cont.isotope("appended", y);
cont.find("a").css({"opacity":"1"});
});
} else { unFilled = false; }
});
}
I have included the unFilled variable simply so that there is a flag that can be set when you have reached the end of the images. You don't want it to keep trying to load forever if there are no images left to show.
Ok, so. This is a lot of information so I will try to keep answering as much as possible.
I want to log 'tapped' and execute the HUD asap, but oauth_upload_photo is causing it to lag (apparently because of hoisting). How can I snap the HUD instantly??
var submit_post = function submit_post(){
console.log('tapped');
// Show HUD
plugins.navigationBar.hideRightButton();
var hud = document.getElementById("hud");
hud.style.display = 'block';
// Get the image
var image = document.getElementById('myImage');
var imageURI = image.src;
// Get the caption from the textarea
var cap = document.getElementById('tar');
var caption = cap.value;
// Call upload photo
oauth_upload_photo(imageURI,caption);
};
Your issue (which needs a lot more explanation before we could understand what you're actually asking about) has nothing to do with javascript variable hoisting. All hoisting does is cause variables to be defined at the top of the function,regardless of where their initial declaration is located in the function. It doesn't change the execution order of any statements.
Also, in some browsers console.log() is not guaranteed to be completely synchronous and the display of the data in the log window is not necessarily immediate either. There is sometimes a delay before it actually logs. I don't know if this is caused by marshalling data across process boundaries, general repaint logic or some other internal implementation issue.
You may also want to change this:
var submit_post = function submit_post(){
to this:
var submit_post = function (){
or even this:
function submit_post() {
so you aren't double defining the same symbol.