I'm currently exploring the potential of CSS3 for casual game scenarios. For Windows Store apps in particular, CSS3 animations are enticing because you don't have to worry about cross-browser compatibility or APIs that won't work with older versions of Internet Explorer, since everything is IE 10+. Compatibility is one of the main reasons why proponents of Flash for Web apps argue against HTML5/CSS3. But for Windows Store apps, these arguments against HTML5/CSS3 are a non-factor. Instead, if you want to do a game (or just animations) for Windows Store, you need to figure out whether you need CSS3, or HTML5 <canvas>, or maybe the newly supported WebGL—if we're talking JavaScript (we are)—for fancier stuff.
Updated info, 2/19/2014: Check out the completed sample (Windows Store version)
Despite a few hiccups, CSS3 development has been pretty fast–which is an advantage of the technology vs. using the HTML5 <canvas> (note that <canvas> also has a lot of advantages, especially for games with a large number of independent objects). However, I thought I'd talk about a couple of areas where I got tripped up a bit using CSS3. In this post, I'll talk about the need to dynamically manipulate the @keyframes rule using JavaScript.
Basic CSS3 animations are pretty straightforward. You create your DOM elements, declaratively or otherwise, and specify a few animation properties. And then you create a set of one or more @keyframes
rules to handle the actual animations.
Here is some of the CSS code for an HTML element representing a game object. We'll say it's for a DIV element containing an image (<div id="piece"></div>). The animation-name rule specifies the @keyframes "enterPiece" rule.
#piece {
position: absolute;
animation-duration: 1.5s;
animation-fill-mode: forwards;
animation-name: "enterPiece"
}
Here is the CSS code for the @keyframes "enterPiece" rule. @keyframes allows you to specify beginning/end states and anything in between (such as what the animation should look like when 50% complete). In this example, I just set the beginning/end states.
@keyframes enterPiece {
from {
transform: translateY(-150px) scale(1);
opacity: 0;
}
to {
transform: translateY(0px) scale(1);
opacity: 1;
}
}
This is all pretty straightforward. The animation for the game object moves the object from a relative starting position of -150px to 0px, and changes the opacity from 0 to 1 while moving it this distance. (I include a scale value for informational purposes but I won't be using it—I'll be setting that value dynamically.)
Here's code to set some of these animation values in JavaScript: In this code, we set the animation duration, fill mode, and the animation name ("enterPiece", again), which specifies the same @keyframes code in CSS. In this case, we're doing it for a new element that we're about to add to the DOM.
newNode.style.animationDuration = "1.5s";
newNode.style.animationFillMode = "forwards";
newNode.style.animationName = "enterPiece";
As I tested my code, I realized I would need to set different scale values for each piece, due to the use of a 3D perspective. This was a little trickier to do in JavaScript, because animated transform values such as scale, translate, and rotate belong to the harder to access @keyframes rule. I refused to create a bunch of extra @keyframes rules in the CSS**. Instead, after some investigation, I was able to obtain the rule using the following code, which iterates through the stylesheets to get the rule I wanted.
**requestAnimationFrame is another way to go. More on this in a later post.
Note: If you don't need to show these values changing within an animation, you can just include them in CSS code, for example, like this: transform: rotateX(180deg).
var cssRule;
// Returns a reference to the specified CSS rule(s).
function getRule() {
var rule;
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
// loop through all the rules!
for (var x = 0; x < ss[i].cssRules.length; ++x) {
rule = ss[i].cssRules[x];
if (rule.name == "enterPiece" && rule.type
== CSSRule.KEYFRAMES_RULE) {
cssRule = rule;
}
}
}
}
To make sure I'm not getting the wrong CSS rule, I check the type, which needs to be CSSRule.KEYFRAMES_RULE, and I check the name. It's best to run this code during app initialization, and cache any rules which you need to modify in JavaScript.
Note: For non-IE browsers, check current recommendations for using the CSSRule.KEYFRAMES_RULE.
Once I had the right rule, I had to change it. The API documentation led me to think that I should use insertRule, but some testing indicated otherwise. Instead, I used appendRule along with deleteRule.
First, I needed to clear out the current rule values. To use deleteRule, you must pass an index to indicate which part of the existing rule to delete. An index of 0 indicates the from portion (that is, the "0%" portion) of the rule. Once I deleted the rule parts, I replaced them using appendRule, and here I passed in my dynamically set scale values.
cssRule.deleteRule("0");
cssRule.deleteRule("1");
cssRule.appendRule("0% { transform: translateY(-150px) "
+ scale +"; opacity: 0; }");
cssRule.appendRule("100% { transform: translateY(0px) "
+ scale + "; opacity: 1; }");
And this worked!
In Part II of this series, I talk about running multiple animations and using requestAnimationFrame. I'll share the full code when it's closer to final state.