John Smith's Blog

Ramblings (mostly) about technical stuff

gl.enableVertexAttribArray() gotcha

Posted by John Smith on

Another post mainly in the hope that I might save someone else the wasted time and head-scratching I spent in fixing this...

I've been continuing playing with WebGL, and as well as experimenting with new (to me) functionality, in parallel I've started building up a library to tidy up the repetitious boilerplate that has been largely common to all my experiments to date. Until now, this has been a fairly mundane and trouble-free job, but I managed to cause myself a lot of pain and anguish last night, when some of my library code wasn't completely right.

I had a vertex buffer that contained 4 elements per vertex, a three-element (x,y,z) coordinate, and a single-element greyscale value. On initial run-through, the coordinates were rendered correctly, but the greyscale value was not at all how I expected. Rather than coming out in a shade of grey, my pixels were being rendered as white.

As far as I could tell, the code to push the vertex data through to OpenGL was fine, and not really any different to a number of earlier successful experiments: gl.bindBuffer(gl.ARRAY_BUFFER, bottomFace.vertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.attributes["aVertexPosition"], 3, // vec3 - (x, y, z) gl.FLOAT, false, (4*4), // total size of 'vertex-input' aka stride 0); // offset within 'vertex-input' gl.vertexAttribPointer(shaderProgram.attributes["aVertexGreyness"], 1, // float gl.FLOAT, false, (4*4), // total size of 'vertex-input' aka stride 12); // offset within 'vertex-input' gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bottomFace.vertexIndexBuffer); gl.drawElements(gl.TRIANGLES, bottomFace.vertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); I've had problems with this sort of code before, so started fiddling with the arguments to the second gl.vertexAttribPointer() to see if I could provoke it into doing something that would give some insight into what was going wrong, but it steadfastly refused to render anything differently.

One thing that was curious, was swapping the ordering of the two attribute declarations in the vertex shader. As expected, this caused the attribute index values to flip between 0 and 1, but also this seemed to be passed through to the shader, causing my pixels to render as either black or white.

Chrome's WebGL inspector didn't show anything unusual, and indicated that my vertex array had the expected values, so I was at a bit of a loss. Eventually I started hacking around with some older working code, to find out where things were going wrong, and stumbled across the cause.

It transpired that when I was initially getting my attribute index values, I wasn't also enabling them as vertex attribute arrays - or rather, this was happening for the (x,y,z) coordinate attribute (thanks to some legacy code that I thought wasn't getting called), but not for the greyscale attribute. Updating my attribute initialization code fixed the problem: for (var i=0; i<attrNames.length; i++) { attrDict[attrNames[i]] = gl.getAttribLocation(shaderProgram, attrNames[i]); gl.enableVertexAttribArray(attrDict[attrNames[i]]); // THIS LINE ADDED }

No errors are caused by not calling gl.enableVertexAttribArray(), and I don't currently know of any reason why you wouldn't want an attribute enabled, but without this rather boring line, you get mysterious failures as I unfortunately found out :-(

Hassles with array access in WebGL, and a couple of workarounds

Posted by John Smith on

I've been pottering around a bit more with WebGL for a personal project/experiment, and came across a hurdle that I wasn't expecting, involving arrays. Maybe my Google-fu was lacking, but this doesn't seem to be widely documented online - and I get the distinct impression that WebGL implementations might have changed over time, breaking some older example code -so here's my attempt to help anyone else who comes across the same issues. As I'm far from being an expert in WebGL or OpenGL, it's highly likely that some of the information below could be wrong or sub-optimal - contact me via Twitter if you spot anything that should be corrected.

I want to write my own implementation of Voronoi diagrams as a shader, with JavaScript doing little more than setting up some initial data and feeding it into the shader. Specifically, JavaScript would generate an array of random (x,y) coordinates, which the shader would then process and turn into a Voronoi diagram. I was aware that WebGL supported array types, but I hadn't used them at all in my limited experimentation, so rather than jumping straight in, I thought it would be a good idea to write a simple test script to verify I understood how arrays work.

This turned out to be a wise decision, as it turns out that what would be basic array access in pretty much any other language, is not possible in WebGL. (Or OpenGL ES - I'm not sure where exactly the "blame" lies.) Take the following fragment shader code: uniform float uMyArray[16]; ... void main(void) { int myIndex = int(mod(floor(gl_FragCoord.y), 16.0)); float myVal = uMyArray[myIndex] ... The intention is to obtain an arbitrary value from the array - simple enough, right?

However, in both Firefox 12 and Chromium 19, this code will fail at the shader-compilation stage, with the error message '[]': index expression must be constant. Although I found it hard to believe initially, this is more-or-less what it seems - you can't access an element in an array using a regular integer variable as the index.

My uninformed guess is that this is some sort of security/leak prevention mechanism, to stop you reading memory you shouldn't be able to, e.g. by making the index variable negative or bigger than 16 in this case. I haven't seen any tangible confirmation of this though.

Help is at hand though, as it turns out that "constant" isn't quite the same as you might expect from other languages. In particular, a counter in a for loop is considered constant, I guess because it is constant within the scope of the looped clause. (I haven't actually tried to alter the value of the counter within a loop though, so I could well be wrong.)

This led me to my first way of working around this limitation: int myIndex = int(mod(floor(gl_FragCoord.y), 16.0)); float myVal; for (int i=0; i<16; i++) { if (i==myIndex) { myVal = myArray[i]; break; } } This seems to be pretty inefficient, conceptually at least. There's a crude working example here, if you want to see more. Note that it's not possible to optimize the for loop - that statement also suffers from similar constant restrictions.

Whilst Googling for ways to fix this, I also found reference to another limitation, regarding the size of array that a WebGL implementation is guaranteed to support. I don't have a link to hand, but IIRC it was 128 elements - which isn't a problem for this simple test, but could well be a problem for my Voronoi diagram. The page in question suggested that using a texture would be a way to get around this.

As such, I've implemented another variant on this code, using a texture instead of an array. This involves quite a bit more effort, especially on the JavaScript side, but seems like it should be more efficient on the shader side. (NB: I haven't done any profiling, so I could be talking rubbish.)

Some example code can be found here, but the basic principle is to

  1. Create a 2D canvas in JavaScript that is N-pixels wide and 1-pixel deep.
  2. Create an ImageData object, and write your data into the bytes of that object. (This is relatively easy or hard depending on whether you have integer or float values, and how wide those values are.)
  3. Convert the Sampler2D uniform. Note you (probably) won't need any texture coordinates or the usual paraphernalia associated with working with textures.
  4. In your shader, extract the value from the shader by using the texture2D function, with vec2(myIndex/arraySize, 0.0) as the second argument.
  5. As the values in the returned vec4 are floats in the range (0.0, 1.0), you'll probably want to decode them into whatever the original number format was.

To go into more detail, here are the relevant bits of code from the previously linked example>.

Creating our array of data

Firstly, we generate the data into a typed array: function createRandomValues(numVals) { // For now, keep to 4-byte values to match RGBA values in textures var buf = new ArrayBuffer(numVals * 4); var buf32 = new Uint32Array(buf); for (var i=0; i < numVals; i++) { buf32[i] = i * 16; } return buf32; } ... var randVals = createRandomValues(16); This is nothing special, and is really only included for completeness, and for reference for anyone unfamiliar with typed arrays. Observant readers will note that the function name createRandomValues is somewhat of a misnomer ;-)

Creating the canvas

The array of data is then fed into a function which creates a canvas/ImageData object big enough to store it: function calculatePow2Needed(numBytes) { /** Return the length of a n*1 RGBA canvas needed to * store numBytes. Returned value is a power of two */ var numPixels = numBytes / 4; // Suspect this next check is superfluous as affected values // won't be powers of two. if (numPixels != Math.floor(numPixels)) { numPixels = Math.floor(numPixels+1); } var powerOfTwo = Math.log(numPixels) * Math.LOG2E; if (powerOfTwo != Math.floor(powerOfTwo)) { powerOfTwo = Math.floor(powerOfTwo + 1); } return Math.pow(2, powerOfTwo); } function createTexture(typedData) { /** Create a canvas/context contain a representation of the * data in the suppled TypedArray. The canvas will be 1 pixel * deep; it will be a sufficiently large power-of-two wide (although * I think this isn't actually needed). */ var numBytes = typedData.length * typedData.BYTES_PER_ELEMENT; var canvasWidth = calculatePow2Needed(numBytes); var cv = document.createElement("canvas"); cv.width = canvasWidth; cv.height = 1; var c = cv.getContext("2d"); var img = c.createImageData(cv.width, cv.height); var imgd = img.data; ... ... createTexture(randVals); The above code creates a canvas sized to be a power-of-two, as WebGL has restrictions with textures that aren't sized that way. As it happens, those restrictions aren't actually applicable in the context we are using the texture, so this is almost certainly unnecessary.

Storing the array data in the canvas

The middle section of createTexture() is fairly straightforward, although a more real-world use would involve a bit more effort. var offset = 0; // Nasty hack - this currently only supports uint8 values // in a Uint32Array. Should be easy to extend to larger unsigned // ints, floats a bit more painful. (Bear in mind that you'll // need to write a decoder in your shader). for (offset=0; offset<typedData.length; offset++) { imgd[offset*4] = typedData[offset]; imgd[(offset*4)+1] = 0; imgd[(offset*4)+2] = 0; imgd[(offset*4)+3] = 0; } // Fill the rest with zeroes (not strictly necessary, especially // as we could probably get away with a non-power-of-two width for // this type of shader use for (offset=typedData.length*4; offset < canvasWidth; offset++) { imgd[offset] = 0; } I made my life easy by just having 8-bit integer values stored in a 32-bit long, as this maps nicely to the 4-bytes used in an (R,G,B,A) canvas. Storing bigger integer or float values, or packing these 8-bit values, could be done with a few more lines. One potential gotcha could be that the typed array values are represented in whatever the host machine's native architecture dictates, so just simply dumping byte values directly into the ImageData object could have "interesting" effects on alternative hardware platforms.

Convert the canvas into a texture

createTexture() concludes by converting the canvas/ImageData into a texture // Convert to WebGL texture myTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, myTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); /* These params let the data through (seemingly) unmolested - via * http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support */ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.bindTexture(gl.TEXTURE_2D, null); // 'clear' texture status } The main lines to note are the gl.texParameteri() calls, which are to tell WebGL not to do any of the usual texture processing stuff like mipmapping. As we want to use the original fake (R,G,B,A) values unmolested, the last thing we want is for OpenGL to try to be helpful and feeding our shader code some modified version of these values.

EDIT 2012/07/10: I found the example parameter code above wasn't enough for another program I was working on. Adding an addition setting for gl.TEXTURE_MAX_FILTER fixed it: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_FILTER, gl.NEAREST); I suspect gl.NEAREST might be generally better than gl.LINEAR for this sort of thing - however I haven't done thorough tests to properly evaluate this.

Extract the data from the texture

Just for completeness, here's the mundane code to pass the texture from JavaScript to the shader: gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, myTexture); gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0);

Of more interest is the shader code to reference the "array element" from our pseudo-texture: uniform sampler2D uSampler; ... void main(void) { float myIndex = mod(floor(gl_FragCoord.y), 16.0); // code for a 16x1 texture... vec4 fourBytes = texture2D(uSampler, vec2(floatIndex/16.0, 0.0)); ... This now gives us four floats which we can convert into something more usable. Note that as we are (ab)using the RGBA space, remember that these four values are in the range 0.0 to 1.0.

Decode the data

Now, this is where I cheated somewhat in my test code, as I just used the RGBA value pretty much as-was to paint a pixel: gl_FragColor = vec4(fourBytes.r, fourBytes.g, fourBytes.b, 1.0); To turn it back into an integer, I'd need something along the lines of: int myInt8 = int(fourBytes.r * 255.0); int myInt16 = int(fourBytes.r * 65536.0) + int(fourBytes.g * 255.0); The above code is untested, and I suspect 65536 might be the wrong value to multiply by - it could be (255*255) or (256*255). In a similar vein, a more intelligent packing system could have got 4 different 8-bit values into a single texel, pulling out the relevant (R,G,B,A) value as appropriate.

I wouldn't be surprised if some of the details above could be improved, as this seems a very long-winded way of doing something that seems like it should be completely trivial. However, as it stands, it should at least get anyone suffering the same issues as me moving forward.

Now, to get back to the code I originally wanted to write...

Parallax starfield and texture mask effect in WebGL

Posted by John Smith on

I've been pottering around a bit with WebGL lately, really just playing with 2D stuff, and this is probably the most notable thing I've hacked up so far.

Screengrab from a WebGL demo, showing the text Hello World using a starfield effect

It's vaguely inspired by stuff like the Sid Sutton Doctor Who titles from the early '80s and Numb Res (especially the bit with letters forming around 2'30" in) - but it's incredibly crude compared to either of those.

If you care to view source on that page and/or the JavaScript that drives it, it should hopefully be fairly easy to follow, but in summary:

  • There are no true 3D objects or particles, instead the code sets up 6 triangles that make up 3 rectangles, each of which fill the full WebGL canvas area. These form 3 parallax layers, which form the starfield(s) - with only minor tweaks, the number of layers could be increased, which would probably improve the believability of the effect a fair bit.
  • The stars are rendered entirely in the fragment shader, using a pseudo-random algorithm based on the X&Y; pixel position and the frame number. The increments to the frame number are what give the scrolling effect, and a speed value supplied via the vertex shader is what causes each layer to scroll at a different rate.
  • The "Hello world" text is rendered into a hidden 2D canvas object and converted to a WebGL texture at startup. The fragment shader reads the texture, and if that particular pixel was set on the texture, increases the probability of a star being shown.
  • Things would probably look better if I added a bit of pseudo-randomness to make the stars twinkle. Unfortunately I was getting a bit bored with the whole thing by the point it came to do this part ;-)

Some observations from my ill-informed stumbling around in a technology I don't really understand:

  • Performance seems fine on the 2 of the three machines I've tested it on so far - a dual-boot Linux/Win7 box with AMD hexacore and nVidia GTS450 and 2008 white MacBook are quite happy; a Celeron netbook with integrated graphics understandably less so, although still churning out an acceptable framerate.
  • Curiously the dual-boot box reporting consuming way more CPU when running Firefox or Chromium under Linux compared to Windows 7. I'm not quite sure why, as CPU usage should be pretty minimal - all the "clever" stuff should be happening on the graphics card, and all that the CPU should be doing each frame is updating a counter and getting the graphics card to re-render based on that updated counter. (Both operating systems have fairly up-to-date nVidia drivers, with the browsers configured to use "proper" OpenGL as opposed to ANGLE or suchlike.) What the cause is, I haven't yet investigated - it could be some quirky difference in the way CPU usage is reported.
  • I reduced Chromium CPU usage from mid-30s to mid-20s (as measured on the Linux box) by moving code out of the main animation loop that didn't need to be there - stuff that defined the geometry, pushed the texture through, etc.
  • I still need to find a way to mentally keep track of the various different coordinate systems in use - vertex shader uses -1.0 to 1.0, fragment shader uses 0.0 to 1.0, plus also remembering the real pixels. (And not to mention that 2D canvas is inverted compared to WebGL canvas!)
  • It feels a bit odd to me that *GL makes it easier to use floating-point rather than integer values. I guess I'm still stuck in an '80s mentality of writing stuff for the 6502 (which didn't really have 16-bit integers, never mind floating point) and stuff like Bresenham's algorithm. (Ironically enough, Bresenham's algorithm was a topic of discussion just last night, in the talk about Raspberry Pi at this month's Hacker News London event.)
  • In a similar vein, I was a tad surprised to find minimal support in shaders for doing bit manipulation stuff like you see in "pure CPU" Satori demos. The same goes for the lack of a proper random number generator, although in the context of this experiment, my controllable pseudo-random numbers were probably a better fit. (I get the impression that this functionality is available in newer versions of regular OpenGL, just not the variant supported by WebGL?)

What's the best way of including SVGs in a responsive web page?

Posted by John Smith on

TL; DR: <object> seems the best bet - although Safari 5.1 has issues compared to the other browsers. Second choice is having the SVG inline in the HTML, but that has issues for WebKit and IE.

As a diversion from my more usual diet of Python, I've spent a fair bit of time over the past week or so revisiting SVG. My previous experiments have usually been done using fixed sizes, but I've always wanted to do something that fits in better with what these days is called "responsive design", especially given as these are supposed to be scalable vector graphics.

For an example of the sort of thing I've been aiming for, take a look at the main chart on a Google Finance page. This Flash chart resizes horizontally as you change the size of the browser window. (NB: the chart makes extra data visible as you make the browser window wider, which isn't exactly what I want, but you should get the idea.)

Unless I've been particularly boneheaded about the way I've investigated this, this isn't as straightforward a problem as it first seemed. If you put a regular bitmap image in an HTML page with something like <img src="whatever.png" width="100%" /> the image will scale as you'd hope when the browser window is resized. This isn't necessarily the case with SVG.

As outlined in the W3C docs, there are five ways that you can pull SVGs into a page in a modern browser:

  • <embed>
  • <frame> / <iframe>
  • <object>
  • <img>
  • Inline <svg>
(You can also create an SVG via DOM function calls, but I'm not particularly interested in that approach right now.)

I've built tests for each of these, and run them through the latest versions of the five main browsers. This obviously ignores a lot of issues with older browsers and mobile browsers, many of which don't even support SVG at all, but as it turns out, the "big 5" are enough to worry about on their own :-(

In the following sections, when I refer to a particular browser, the tests were done in the following versions, which (AFAIK) are the current ones as of early April 2012:

  • Firefox 10
  • Opera 11.62
  • Chromium 17 or 19 (I didn't notice any difference between the two
  • Internet Explorer 9
  • Safari 5.1
Where relevant, issues are illustrated with screengrabs from the Windows version of a browser, but much of my testing was done with Linux versions - except in the case of IE and Safari. I also tested whether JavaScript functionality worked - both within the SVG itself, and from the enclosing document trying to manipulate the SVG.

Just for clarity's sake: all of this messing around is (probably?) only needed if you want an SVG to be scalable within your web page. If you're happy for it to be a fixed size, then you shouldn't have to worry about any of the following stuff.

<embed>

Test link: http://js-test.appspot.com/svg/embedscaling.html

The image gets scaled correctly in Firefox, Chromium and IE. The image does not get scaled correctly in Opera or Safari.

Screengrab of SVGs using the embed tag in Opera 11.62 Screengrab of SVGs using the embed tag in Safari 5.1

<iframe>

Test link: http://js-test.appspot.com/svg/iframescaling.html

(I've assumed <frame>s behave the same; I couldn't be bothered to write a separate test for them.)

Only Chromium rendered the page completely as desired. Screengrab of SVGs using the iframe tag in Chromium 19

IE and Safari both failed to scale the images properly, but were able to modify the SVGs from the enclosing document's JavaScript. Screengrab of SVGs using the iframe tag in IE9 Screengrab of SVGs using the iframe tag in Safari 5.1

Firefox and Opera failed to scale the images, or modify them via the JavaScript in the enclosing document. I'm not sure if the JS issue is down to some DOM API difference and/or security problem - but as the scaling is broken, I couldn't be bothered to investigate further. Screengrab of SVGs using the iframe tag in Firefox 10 Screengrab of SVGs using the iframe tag in Opera 11.72

(All browsers did at least run the JavaScript contained within the SVG files.)

<object>

Test link: http://js-test.appspot.com/svg/objectscaling.html

Only Safari lets the side down, by failing to scale the SVGs. All other browsers work as desired. Screengrab of SVGs using the iframe tag in Safari 5.1

<img>

Test link: http://js-test.appspot.com/svg/imgscaling.html

If you have any JavaScript-based interactivity, forget about using <img> tags - the JS in the SVGs won't be run, and the JS in the document doesn't do anything either. WebKit-based browsers also have weird issues with additional padding and squashed images. You might hope that the preserveAspectRatio might be able to solve that, but I was unable to find any value which fixed things. Screengrab of SVGs using the img tag in Chromium 19 Screengrab of SVGs using the img tag in Safari 5.1

Inline <svg>

Test link: http://js-test.appspot.com/svg/inlinexhtmlscaling.html

This method is what the BBC uses for the position chart in its football tables, which is the highest profile use of SVG in a mainstream site that I know of.

Scaling works in all browsers, except IE. Screengrab of SVGs using inline SVG in IE9

However, WebKit browsers suffer from excessive vertical padding - it seems that WebKit assumes the height of the image is the same as the browser window, rather than the viewBox attribute in the <svg>. A slightly messy fix is to manually alter the height of the SVG elements after the page has loaded - this wasn't incorporated into this particular test, but an example from another test is here, and other people have documented similar workarounds. There are some open bugs on the WebKit tracker that might be related, here and here. Screengrab of SVGs using inline SVG in Chromium 19 Screengrab of SVGs using inline SVG in Safari 5.1

Whilst doing some earlier tests in this area, I also found a couple of bugs in Opera where

  • An HTML5 page wouldn't properly render, and would never trigger the load event - but an effectively identical XHTML page was fine. Warning: this bug also causes Opera to consume 100% CPU on the processor it is running on Screengrab showing bug in Opera in HTML5 page with embedded SVGs
  • If a page had multiple copies of the same SVG pulled in via the <img> tag, then if the page was reloaded, most of the duplicates would not appear: Screengrab showing bug in Opera after a page with duplicate SVGs is reloaded
Both of these have been reported to Opera via the tool built into their browser

Summary

As can be seen, of the five methods available, none is 100% foolproof. It seems to me that the best bet is <object>, as it works fine on all browsers except Safari, without need for JavaScript hacks. Second-place is embedding the SVG into the HTML, which entails a simple hack for WebKit browsers - but unfortunately is still broken in IE.

Disparity in requestAnimationFrame behaviour between Chrome and Firefox

Posted by John Smith on

This is a brief prelude to another post I hope to make in a couple of days or so, once I've solved my problem to my satisfaction. In the meantime, here's a related curio that I hadn't seen documented online before I had to start digging...

requestAnimationFrame is something that's been pushed in the last year or two as a more efficient way of doing animations in JavaScript than the traditional technique of using setInterval. In particular, it aims to avoid having your machine burn CPU on executing animations in a tabbed page that's not currently visible.

At time of writing, only Firefox and Chrome seem to actually support this function, albeit with moz and webkit vendor prefixes. caniuse.com doesn't have too much information about future support in other browsers - it'll appear in IE10, but it's unclear about Safari or Opera. Certainly the Opera Next 12.0 I downloaded yesterday doesn't appear to have it.

Now, for the most part this isn't the end of the world, as there are published shims to implement a workable alternative using setInterval() or setTimeout(). Unfortunately, these will just churn away as normal in a background tab, whereas what I wanted to do was to see how things were different in a background tab.

It turns out that the two implementations we have so far differ in their behaviour. Chrome comes to a dead stop when a page is in a background tab, which is probably what you'd naively expect to happen. Firefox on the other hand does some gradual throttling - you'll get one frame in the first second of being backgrounded, then another after a further two seconds, then a further four seconds, eight seconds, sixteen seconds, etc.

I knocked up a very rough demo for this, so that you can see for yourself - take a look here, and see what happens when you r-click the link on the page and open it in another tab - the function called via requestAnimationFrame() updates the page title, so you can see how often it gets called from the text in the tab.

I'm not completely clear why Mozilla have implemented this the way they have - I've not dug out any official specs, but going by the year old Chromium issue to add this functionality, I don't expect this behaviour to show up in Chrome/Chromium.

In the next post I'll elaborate on the problem I've been trying to solve - suffice to say, using requestAnimationFrame was a bit of a hacky way of trying to achieve something that I'd have thought should have been extremely straightforward...

NYT Chrome app is probably the buggiest thing I've ever seen

Posted by John Smith on

I should probably have read the Hacker News thread first, but Christ, what an atrocious piece of buggy shit this is. How on earth it managed to get positioned as one of the top launch apps on the Chrome App Store, one can only guess at.

Running in Chrome 8 initially, I noticed that the pagination algorithm seems completely borked. Very minor resizing of the browser window causes the indicated number of pages for the story to randomly fluctuate - I think I managed to get the same article to claim to be between 1 and 13 pages in length, at least if the footer on the right is to be believed.

Screengrab of NYT Chrome app, claiming to show page 1 of a 6 page story Screengrab of NYT Chrome app, now saying the same story is 4 pages long Screengrab of NYT Chrome app, this time saying story is 10 pages long

OK then, let's start advancing through this ten page story. The second page is fine - if rather text-heavy and image light - but the third page is slightly empty though...

Screengrab of NYT Chrome app, showing page 3 of 10, but the story seems to end

... I'm sure there must be more to come though ...

Screengrab of NYT Chrome app, showing a blank page 4 of 10

... oh.

Safari is unsurprisingly similar. What is a surprise though, is that this is as good as it gets.

Opera 10.63 just keeps kicking you back to the front page every time you click on a story link, whereas on Firefox you have a choice of illegibility or invisibility...

Screengrab of NYT Chrome app in Firefox 3.6.13 - text is illegible due to multiple paragraphs appearing on top of each other Screengrab of NYT Chrome app in Firefox 4 beta - only the navigation sidebar appears, the rest of the page is blank

One can only imagine what further travesties might await were I to dare view it in IE...

CSS3 transitions on properties which haven't been explicitly defined

Posted by John Smith on

While continuing to fiddle with the CSS and JavaScript on this blog, I noticed a rather strange behaviour whilst testing on Chrome, where the page would render differently, apparently at random. This turned out to be largely down to issues with JavaScript executing before or after CSS had been applied, but it's also shown up something unpleasant with CSS3 transitions, where it seems that the only thing consistent between browsers is that none of them do what you (probably) want...

First off, a very quick overview of CSS transitions for the uninitiated. These are a means of having smooth changes between styling property values of HTML elements, and are supported in modern browsers i.e. Chrome, Opera, Safari, and Firefox from 4.0 onwards. (Forget IE, not even IE9 beta supports them.) Here's a bit of sample code that animates a <div> when you click on it - the most relevant bits are in cyan.

<div style="position: relative; left: 50px; font-size: x-large; -webkit-transition: all 2s linear; -moz-transition: all 2s linear; -o-transition: all 2s linear; transition: all 2s linear;" id="css3-demo-click-me">Click me</div> <script> document.getElementById("css3-demo-click-me").onclick = function(ev) { ev.target.style.left = (300 - parseInt(ev.target.style.left)) + "px"; }; </script>

Which you can see here:

Click me

The above example should work fine on all browsers supporting CSS3 transitions, on ones which don't the text will jump immediately to the alternate position.

Now, in the example above, the left property is always explicitly defined. What happens if you try to transition a property which isn't explicitly defined at the start of the transition?

The answer seems to be, "it depends on what browser you're using". More annoyingly, no browser does what I'd think would be the expected/preferred behaviour, namely to transition from the computed value to the new value. However, Opera and the WebKit browsers transition from a zero value to the target value, and Firefox 4.0 ignores the transition altogether, jumping straight to the target value.

I've coded an example that transitions the width of some text, as you might want if you had to make some space for a new element on the right hand side of the page. Unfortunately it doesn't play well within this blog, due to the use of position: fixed; breaking the layout, so instead you'll have to look at it via this link.

The somewhat crappy solution is to:

  1. Turn off transitions on the element you're going to change
  2. Explicitly set the CSS property to the computed value, a jQueryish example would be something like $("#my-element").css("width", $("#my-element").width()) + "px");
  3. Re-enable transitions on the element
  4. Change the property to the target value
Not too bad in simple isolated cases, but I wouldn't want to do that amount of faffing around on anything complex.

I haven't looked at the official CSS3 spec to see what - if anything - is the expected behaviour in this sort of scenario, but the fact that we have browsers doing different things indicates that someone needs to fix something.

About this blog

This blog (mostly) covers technology and software development.

Note: I've recently ported the content from my old blog hosted on Google App Engine using some custom code I wrote, to a static site built using Pelican. I've put in place various URL manipulation rules in the webserver config to try to support the old URLs, but it's likely that I've missed some (probably meta ones related to pagination or tagging), so apologies for any 404 errors that you get served.

RSS icon, courtesy of www.feedicons.com RSS feed for this blog

About the author

I'm a software developer who's worked with a variety of platforms and technologies over the past couple of decades, but for the past 7 or so years I've focussed on web development. Whilst I've always nominally been a "full-stack" developer, I feel more attachment to the back-end side of things.

I'm a web developer for a London-based equities exchange. I've worked at organizations such as News Corporation and Google and BATS Global Markets. Projects I've been involved in have been covered in outlets such as The Guardian, The Telegraph, the Financial Times, The Register and TechCrunch.

Twitter | LinkedIn | GitHub | My CV | Mail

Popular tags

Other sites I've built or been involved with

Work

Most of these have changed quite a bit since my involvement in them...

Personal/fun/experimentation