Memory in Motion with KnockoutJS and 3D CSS Animations
My goal yesterday was to get some one-on-one time with KnockoutJS as well as try out using 3D CSS effects. Since the whole 3D Flipping animation reminds me of flipping playing cards, and I really enjoy creating games, I figured I'd make a card game. I decided to create Memory but I wanted to put a twist on it. Instead of the player memorizing positions of the cards like in typical memory; I'd move the cards around but give the user imagery to memorize.
Here's what I ended up using for the design. It's simple. The top side of the card shows images. While the under-side shows a number. You need to turn over cards in twos trying to find matching numbers. If you find a match, the cards remain revealed and stop moving. If you flip over two non-matching cards, they flip back over and you continue. There's some info logged at the top like the number of matches found, as well as the number of misses.
[caption id="attachment_435" align="alignnone" width="300"] Memory In Motion[/caption]
1. First I'll start with the 3D CSS portion of the project
Creating CSS only 3D Transitions were ridiculously easy. Check out the few lines of code below that were used to create this CSS-only flip card. I modified it a bit to flip when a particular class was applied, that way I could manage state via my JS. But otherwise its the same code being used.HTML
-
FRONTBACK
CSS
@media all and (-webkit-transform-3d) { /* Use the media query to determine if 3D transforms are supported */ .flip-container { -webkit-perspective: 1000; display: inline-block; top: -300px; left: 500px; } .flip-card { width: 175px; height: 175px; margin: 10px 10px 0 0; -webkit-transform-style: preserve-3d; -webkit-transition: -webkit-transform 1s; animation:myship 10s; -moz-animation:myship 10s; /* Firefox */ -webkit-animation:myship 10s; /* Safari and Chrome */ cursor: pointer; } .flip-container:hover .flip-card { -webkit-transform: rotateY(180deg); } .flip-card img { border-radius: 8px; } .face { position: absolute; width: 100%; height: 100%; text-align: center; -webkit-backface-visibility: hidden; background-color: rgb(44, 110, 155); border-radius: 8px; box-shadow: 5px 4px 8px rgb(151, 151, 151); font: 30px'ChunkFiveRegular'; color: #fff; text-shadow: 0px 1px 0px #999, 0px 2px 0px #888, 0px 3px 0px #777, 0px 4px 0px #666, 0px 5px 0px #555, 0px 6px 0px #444, 0px 7px 0px #333, 0px 8px 7px #001135; } .face.back { display: block; -webkit-transform: rotateY(180deg); -webkit-box-sizing: border-box; color: white; -webkit-border-radius: 10px; } }I had really hoped to do all my animations via JavaScript but unfortunately I wasn't able to do the position animating that way. The reason was due to the fact that when difining your CSS keyframes you have to define your start/end locations as seen below. Since I'm randomly choosing two cards to swap places, I don't know ahead of time which positions to set up animations for.
@keyframes mymove { from {top:0px;} to {top:200px;} } @-moz-keyframes mymove /* Firefox */ { from {top:0px;} to {top:200px;} } @-webkit-keyframes mymove /* Safari and Chrome */ { from {top:0px;} to {top:200px;} } @-o-keyframes mymove /* Opera */ { from {top:0px;} to {top:200px;} }
One option would have been to write JS that would modify the css styles via referencing them on the DOM like seen below. But I was lazy so I stuck to my guns and rolled some simple jQuery animations.
Modifying stylesheet via JS
var cssRuleCode = document.all ? 'rules' : 'cssRules'; //account for IE and FF var rule = document.styleSheets[styleIndex][cssRuleCode][ruleIndex]; var selector = rule.selectorText; //maybe '#tId' var value = rule.value; //both selectorText and value are settable.
Animating via jQuery
$(cardTwo).animate({ left: cardOne.left, top: cardOne.top }, 1000);
2. Initial thoughts about KnockoutJS
I only looked into Knockout two days ago and went through the quick introduction tutorials. I have to say they were very well put together and it was quite easy to understand. I was able to quickly apply what I had learned to this project. The one hiccup I ran into was the fact the the tutorials don't really show you how to reference your Models outside the scope of themselves. Their examples all look like:function AppViewModel() { this.firstName = ko.observable("Bert"); this.lastName = ko.observable("Bertington"); this.fullName = ko.computed( function() { return this.firstName() + " " + this.lastName(); },this);this.capitalizeLastName = function() { var currentVal = this.lastName(); this.lastName(currentVal.toUpperCase()); };
}
// Activates knockout.js
ko.applyBindings(new AppViewModel());
But what happens if you wanted to call your Model.capitalizeLastName function externally? Maybe that's a poor design choice and they would argue that you need to keep all your model's method calls internal. But I wanted to, and so the answer I found is to break apart applyBindings and calling new on your model. Like so..
flashCardGame = new FlashCardViewModel(); ko.applyBindings(flashCardGame);
This provides you with a reference to your model, 'flashCardGame'. The reason I had to do this was probably caused by a misuse of Knockout in the first place. The problem I ran into was positionCards() on start which would query the DOM via the jquery below. But that query was returning no elements because it was being executed before KnockoutJS would apply the data model to the DOM. Someone whose more experienced with KnockoutJS probably knows the fix to this issue, but I was trying to wrap this up quickly.
var cardsNotPositioned = $(".notPositioned");