What do you want to learn?
Skip to main content
by Lea Verou and Geoffrey Grosenbach
Start CourseBookmarkAdd to Channel
Table of contents
Data Model and Game Mechanics
So I'm here at Amelia Island, Florida with Lea Verou. Very excited to be at CSSConf and JSConf. And you've been on a massive traveling tour of the world, I mean, you speak all over the place, but you drove down here from-- Washington From Washington D.C, along the coast. I bet that was pretty beautiful? Egh, it was okay. It was all on the I95 so it was kind of boring. And it was exciting because your boyfriend is British so he was tempted to drive on the wrong wide of the road. He tried to exit on the left on the highways. That was awkward. He had to pull over and like -- half a mile before the exit in front of every other car. So then -- but then you have a few little break in traveling before you go on to MIT, one of your dreams, now you're going to be getting PhD. A PhD, yeah. Fantastic. And this is my-- I only have one conference left before MIT. This was my almost last one. Almost last one, well, and it's a nice place to be. It's a lovely place. Because it's been beautiful, and it's the beach, and yeah. We actually-- we need to find out how to do this session on the beach; it'd probably just be a little windy, and hot. That would be great: coding on the beach. (Laughing) There's actually a thing called Hack your Beach where people gather on a tropical beach every January and code on the beach for, like, a month. I need to find out about this, wow. Yeah, we went to Hack your Beach in January. It was Kenya. It was lovely. Wow.
Description of Conway's Game of Life
Initial Strategy and Starting Point
Initial Console Debugging
Yeah, so let's implement the toString method first so we can start, because we will need it a lot for debugging. So we want to print out the current board status so let's map every line to a string. So we would return row joined by white space, and then we get an array of all the rows converted to strings, so we want to join this with a line break. With that new line, okay. So this should at least print the current state, even if it doesn't do anything. So let's start testing what we're doing here. Undefined is not a function. 42. 42. 42. 42, game.next. So we've defined game here. Could I have done something wrong here? Nope. Undefined is not a function. And it's printing an object. What have I done here? New life, okay. Does next need to return-- no, I guess it need to return. No, no, it doesn't need to return anything. Anyway we haven't implemented next at all yet so we just want to print out game. So it's not printing out the correct thing. So let's remove this string conversion and see what it's printing. So it seems that toString is not working. Why is it not working? This seems to be the object we have. So it's got the instance variables of, like, the seed width, and height. Let's see if I return a random string, like yolo, will it work? No. Hmm. Have I made a typo in toString? No. So it's the method that's not working correctly. So is it not calling toStr-- oh, it's not calling toString at all on our life object. Yeah, it's like this doesn't exist, I guess. Yeah. Oh, because I've misspelled prototype. Agh. Of course. And it didn't pull that out as an error? So let's go back to the original thing we had. Yes, this is printing the array. That's better. Yeah. So this is printing the array. So that's my job as the pair programmer is to catch all your typos, but I didn't catch it there. (Laughing) So right, we set the current state to the previous state; we send it to the previous board variable. So now let's go over every row, and cell, and see what's happening. (Typing) So the reason we're doing rows first instead of columns first is that actually the first number here is the row. (Typing) The X and Y, see, usually I don't like single letter variable names, but in this case it really is just kind of a mathematical X, Y grid. Yeah, exactly. So that makes sense. I guess we could still do row and column, but it's-- but that's not exactly what we're talking about either.
Let's have a method, let's call it aliveNeighbors, that gets an array, and then X and Y, and returns the number of alive neighbors. So this is our first rule that we're implementing? Because we can print out the state of the array, but now we need to actually apply the logic. Yeah. So the array we're doing this checking on is previous board because we've already set it with the current state and we don't want to modify it with what we're doing right now. And now we only have an array, and an X and Y. We need to count the number of neighbors. So we could do a loop, but we only need to check eight neighbors, and we would need to do a two-dimensional loop anyway so it's kind of pointless, I think, to loop. Instead, I think, I'd rather do an array of neighbors, like-- and actually let's separate it by rows so that it's even more clear what we're doing. Because when this function is called it gets the entire board. It doesn't just get a row-- and it needs the entire board because it has to look in all different directions. Okay. Yeah, yeah, exactly. So the previous row would be array Y-1, and X-1, then X, then X+1. We just got the three squares above the current square. Okay. Then we need the two squares left and right of the current square, so that would be this, and this. And we also need the row underneath the element so that would look like this. See that was so fast. I would have to spend 20 minutes sketching out a tic tac toe board to figure out all those, but that gives us what we need. So we need to go; now we have the neighbors but we haven't added them yet. We don't know what state they have. So if we use, now if we use-- we need to go over the neighbors and return zero when-- And, oh, we have a sum here. We can have a sum here with zero, then go over the neighbors. That could be a forEach actually. And it's just iteration so I'll just use a single letter variable. And so we'll use plus equals to add things, and if A, we want-- if A is undefined or zero we don't add anything so we add zero to the same thing. If A is true we add one. Yeah, so we want to convert both undefined and zero into zero, so if we use plus, and this is undefined, it will return not a number, none. So let's convert it to a Boolean value first and then use plus. Okay. So we convert the Boolean value to a number. Because if we use the console here, if we have undefined, and we use plus on it, it returns none. If we use undefined and we convert it to a Boolean with the two exclamation marks, it returns false. And if you convert a Boolean to a number with a plus sign it returns zero. So, together that will give us a number; then we can add up to find out how many neighbors are still alive. Yup. Concise. And then we can return the sum. However here, we have a problem here because as you will see-- actually let's try to run it and you'll see what the problem is. Oh, we're not calling next yet. So this will give an error. Yup, because we will end up running into values of the array that don't exist as rows, and then we're trying to call a certain cell in the row, in a row that doesn't exist. So we're trying to get a property from an array that doesn't exist, so we're trying to get a property of undefined, which gives us this error. And is that middle, the current row, where we're looking at the left and right? So assuming, for example, assuming the current cell is this, the top row, and it's trying to check the neighbors above it. Yes, so there are multiple cases where we wouldn't have a neighbor-- Exactly, so we wouldn't mind if that returned undefined, but we don't want to try to get properties from undefined arrays. So instead of doing this, we'll define a previous row, which is-- and also this might make the code a bit more understandable as well, so two birds, one stone. And next row, which is this. So what this does is if this doesn't exist then it gets this value which is an empty array so we can call properties on it. These properties will still return undefined because it's an empty array, but we're fine with undefined, in that case. So let's replace these rows with this. And the next row. And we don't need the current row because we know that the current row exists, so we don't need to do this. We just need to do this for the previous row and the next row. So this does exist, but we're not doing anything with the neighbors yet so we get the same state back. Let's print the numbers of neighbors so we know what we're doing and if they make any sense. Because in theory, right now, we should be able to see how many neighbors are alive. Yeah, if this works we should be able to see how many neighbors are alive. So we need to also print the coordinates we have. And let's print the coordinates first; that probably makes more sense. And then a colon. So let's get some-- a sample, for example, this one. This would be one, zero, the second row, because they're in zero indexed, and the column one, so it's the-- we need to find two in one, and it says three neighbors so it seems to be working. So let's improve the code here a little bit. We can probably do this with only one function, so we have the neighbors array and instead of using forEach I'll user reduce. So reduce takes a function with a previous and current values, and you also specify an initial value, so you can just return previous value plus the current value converted the way we said earlier. To a Boolean and then a number, okay. If this works, I should just be able to use return here. I don't even need this sum anymore. So let's pray this works. Yeah, it seems to work. We could check our Y of two, and X of one, and does it still say three? Yup. Straight on, nice, little refactor. Yeah, I love reduce; it's not getting as much love as it deserves. Okay, so we have the number of neighbors. That was, like, an ES5 new thing? Yeah. Or was it their previous--? Yeah, that was ES5. But even though people have been using, like, forEach, and filter, and mop quite a lot, reduce doesn't get much love. Okay, so we have the number of neighbors. We know they're correct. Let's implement the logic. So, this board, what is this board currently? Oh. Okay. We cloned the seed. So let's set a variable; is it alive? this.board Y X, and let's convert it to a Boolean. Okay.
HTML Game View
Implement the LifeView Object
Let's center this first, because it's kind of awkward on the side. Make it sans-serif, just in case. And we don't want any border spacing so let's cancel this. (Typing) Yup, so we don't want these to look like checkboxes. Okay, they'll just be a black or white square? Yup. But we want them to behave like a checkbox that we can touch it. Yeah, exactly. So let's add a border to the cells and the checkboxes shouldn't be visible at all. Let's cancel paddings, and stuff, because there are some default paddings here. Okay, I know this looks awful right now, but that's how it's supposed to look. (Typing) So let's style the checkboxes a bit. And this is the style they'll have when checked. Oh, a little browser prefix. Oh, and you're using-- well, we were in chrome so that is webkit. Yeah. And if I remember correctly, appearance is actually nonstandard with the nonvalue, but it should get the value normal eventually. But I'm not sure if that's actually in the spec yet. So let's check what the spec says. See, you're probably one of the few developers who actually reads the spec and can understand it. I think the-- so the appearance property was removed from the spec at some point, and then we resolved to re-add it with just the normal value, but no browser actually accepts the normal value. But I think this spec hasn't been updated in awhile--yes, since 2012. So let's see the in development version of this spec. Ah, that's somewhat more recent. It's not even there. Does it have the appearance property? (Scrolling) Doesn't seem to. So only vendor prefix, vendor prefix is the only way to get it. Yeah. It will be there eventually because I remember the discussions in the CSS working group, but apparently the editor of this spec hasn't added it yet. So in the meantime, and we can't just use display none because then that would remove any kind of interaction at all. Exactly, you wouldn't be able to interact with it at all. And even if you specify visibility hidden, it still hides it from screen readers and other assistive software. And that's one of the goals here is to also make this accessible, so we want to make sure we do CSS in the right way. So let's give it a background of yellow so we see what we're doing and then we'll remove it. So that now, they don't have any dimensions, the checkboxes, so let's give them-- Okay, first we need to set a display of block, and then let's give them, I don't know, 20 pixels, maybe, and a height of 20 pixels. Let's see. Yellow. Yeah. But the borders of the cells are-- if we have two neighboring borders they seem to create the appearance of a really thick border. Right. So let's specify border.collapse. Right. And we need to specify that on the table. Yup. And we could do this. So right now, this kind of works. However, from what I remember in Firefox, this doesn't appear at all. Actually, let's check what we're done in Firefox so far. You like Alfred for launching apps? Yeah. So Firefox has a problem that should be fixed pretty soon with the borders of checkboxes. There's absolutely no way to set that you don't want borders on the checkboxes. So we could try. We should specify a border of zero, none, or actually none's the default value so just a border of zero. And it should work on Firefox, but there's an open bug that because this doesn't work yet, so you get this awful border. And there are ways to hide it visually, by clipping the checkbox off, but let's not spend too much time on that. So for now, this will be webkit? Well, it will work on Firefox. Oh, it'll work, it just won't look-- So the problem is with Firefox, we don't get the border. Oh, okay. We get these ugly checkboxes, which is not what we wanted. So let's try a small hack and instead of setting a background, or in addition of setting a background, let's set an inset box shadow with no offsets and no blur, and let's say 10 pixel spread, black, and inset. So as you can see this works. Wow. And let's set it to 99 pixels just in case we want to change the size of the cells. We don't want to have to change that as well. So as you can see now, these-- Now, we at least get a solid color instead of a check mark. Yeah, and eventually Firefox will soon solve, fix this bug, because many people are complaining about it. So let's go back to Chrome. We have this yellow grid at this point. let's change the yellow because-- oh, white. Okay. So we have the interface, and we have the model, but they're not connected yet.
Connecting the Interface to the Model
And this is how the game will start. We'll get a blank board and we'll decide which boxes need to be on. Exactly. And then we'll have some kind of play button that runs the logic. Yeah. Well in the beginning, we'll have to call play in the console and then we'll add a button. Okay. So, let's add a play method here, and a next method. So the play method will create an instance of the life class. Let's call it game. And what prompters does the life class need? A seed. So we need to create a seed from the checkboxes we have, so we need to convert these checkboxes into an array with zeros when the checkboxes aren't checked, and ones when the checkboxes are checked. So, let's say-- So that why you set up this.checkboxes earlier? Yes, exactly. So let's have a boardArray getter that will return this. And since this is basically a function with no parameters, I specified it as a getter so we can use it as a property. That's also ES5. And I've never seen that syntax, so get creates a getter for any-- any properties. And you can also use set to create a setter, so that when you set a property something happens in a similar way as the native getters and setters work. Okay, and that works right within the object syntax that you're using right now? Mmmhmm, yup. So let's see, we have an array of checkboxes, and we want to create an array of zeros and ones. So let's map the array of checkboxes to what we want. So now we have a row with checkboxes, so we also want to map this array as well. Actually this is a checkbox. At this point we've reached the checkbox. So we return, if the checkbox is checked-- so this is a Boolean so I use a plus in front of it to convert it to a number. So now this should return zero if it's checked, and one if-- one if it's checked and zero if it's not checked. So this-- at this point this boardArray should work. So actually, before I test this, let's test the boardArray getter first. Unexpected token, 101. So what did I do here? Did I miss parenthesis? There is something. Needs another parenthesis in 100? Oh yes, yes, I missed the parenthesis. Okay, so, let's check a few checkboxes, and see what boardArray returns. So we assigned it in a global variable for now just so that we're able to test things easily. Oh, right. So let's check. This would be-- now let's check this checkbox. That's easier. So yeah, this is at one. It's on. Seems to be working.
Implementing the 'next' Function
So we have the array with the seed now, so let's create a game model. So now-- okay, so we've connected the number of cells on the visible board with our checkboxes array but we still haven't hooked it into the logic. Exactly. So now, basically what we need the checkboxes to do is A, to act as an input in the beginning to get the initial seed, and then we need them to function as outputs to show the state of the game. So that will happen on next. So on next, we'll call next on the game, and then we need to represent the array on the checkboxes. So the array of the game-- the board of the game is on the board property, and now we need to reflect this on the grid of checkboxes. Okay, we already had code to set up the board, so now we're not creating new check boxes, we'll just modify their state. Yes, exactly. So the board property of this class returns a two dimensional array of the current board status, which is why I've used forEach to loop over it. Or actually, let's just use a traditional for loop. (Typing) (Typing) So, let's cache the board in variable. (Typing) So we will reference the checkbox like this, and we want to set the check property, and this would probably work. We probably don't need to convert it to a Boolean that way, but let's convert it anyway and let's see. So, maybe, this should work. Okay, because board just has zeros and ones, but we maybe want to make it true or false when we set the checkbox status. Yeah, but I think that's done automatically anyway, but we've converted it just in case. I think it's not needed. So let's try this pattern that we already know what it should create. And let's call play, and then next. Oops, cannot read property zero of undefined. Okay. So maybe it doesn't like the board variable, or what? Oh, we don't get a line number. Yeah, exactly. So let's-- let's log a few things. Let's make sure that everything here-- Cannot read property ‘0' of undefined. What could it be? Sometimes these error messages are really cryptic. Right. (Typing) Still. And it doesn't print anything Oh, so it's not getting to that point? Yeah, it's not even printing anything. So, let me check something. So, the game property, let's see, is this one correct? So this one is correct; it has the correct status, so the problem is-- Yes it moved. Okay, this is the Toad. Yeah, I think it's the the Toad. Okay. So the problem is with getting the checkboxes to show the next state. So this worked. Oh, of course, this prototype does not have a board property. It's the game that has a board property. Ahh, that error message gave us no help in finding that. Yeah. So let's see. (Typing) Huh. Blam. So it works, and then we can keep calling next. Back and forth. (Laughing)
Adding Buttons to Run the Simulation
Amazing. So that's-- we connected the visible board to the logic. MmmHmm. And we can play and we can advance each stage of the game, but it's not doing it automatically right now. No, right now we have to call next and play from the console. So let's add two buttons for those, and we'll style them later. So right now it won't look very pretty. But at least we'll be able to do it without needing to run commands. And then we can do autoplay. So-- (Typing) I'm also, jumping ahead, I'm also curious about speed. Will this play so fast that we can't even see each stage, or are we going to have to do something about that? I guess we'll find out. Yeah. (Typing) So I like to add a convenience dollar method, sometimes, which gets a selector and a container, and returns-- (Typing) So then I can do things like this. (Typing) And I could-- (Typing) I like that. A little tiny jQuery. Really, really tiny. (Laughing) Yeah, because some people use jQueries just to be able to get elements by selector, and by using the dollar notation. It's kind of-- If that's all you need then why get the rest? Yeah. (Typing) Or actually, the only thing we need is a next. Because it's going to call play when it sets up the board initially? Yeah. Oh, but don't we-- So we set up the board, and then we call next-- So that might be able to handle it itself? We did talk about not wanting to manipulate the state of the game in process, while it was running. Yeah. Does that play into whether or not we need a play state? Yeah. Yeah, because this is like an implementation detail that shouldn't be exposed to the user. As far as the user is concerned, if they make a change, and they press next, it should just work. They shouldn't have to think, oh, should I press play or should I press next? Right. Well, good. So we can abstract that away a little bit, or at least hide it from the UI. MmmHmm. Then I forget what play even did? That just sets up the initial--? Play just creates a new game. Okay. So maybe we can have a flag that when we check a checkbox, it kind of, it sets the flag to false so it knows that it has to call play first. So let's try to do that. (Typing) So if the game has started, like if you've clicked next, because right now we're not autoplaying, so if you click next, and it started then it should-- then this should be already true and it should set it to false. So if you call next-- if it's already started, it should call next. So, actually, let's do it the other way around. Right, if it's not started then we play. If it's not started OR there's nothing in the game property, just in case we screwed up. Okay. So then we call next. Because every time we'll have to call next is just _____ -- Yeah, yeah, yeah. So-- (Typing) And after we've done this, we set started to true. And do you want-- if it's not started, and there's not a game, or if it's, like right now it says if it's not started or there is a game? So, yeah, so if we check a checkbox, any checkbox, started should become false. Okay. And we should start a new game. Oh, we could try it out. Let's try it out, yeah. (Typing) So let's add an event on the table, because I guess the change event on the checkboxes should bubble. So that we can just use it on the table. And let's add this to a variable. So--(Typing) Let's assume a checkbox then, because we don't have any other inputs in there. (Typing) So anytime someone interacts with the checkboxes that just stops the whole game so that it then would have to, someone would have to hit next to start it up again. Is that what we just did? Well, that should be transparent to the user. Okay. At least that the thinking behind it. So right now we haven't really done anything with these buttons yet. Actually let's remove these buttons for now. Let's make sure it does what we want to do first. So the only thing we want to call out from the outside is next. Okay. So let's see if this works. Yes. It does. Okay good. So now if we modify it, to something else— let's do something completely different. Maybe just our vertical line? Yeah, that was my plan. Yes. It does it. It does work! So it reset the state of the logical board based on the current state of the visual. Yeah, it's not very performant, but I presume users won't interact with it too much once it's started. And it's not done very frequently, so as long as the performance is not-- as long as there's no noticeable delay, I am not sure if it's worth to optimize this. For now. And you said, maybe, we could disable all the checkboxes, that's another benefit of the fact that we used checkboxes. Yeah, but then we lose this. Okay. So I think, why shouldn't the user interact with it once it's started? What if they want to change it? Yeah.
Refining the 'next' and 'autoplay' Buttons
So we just need a next button. Good, got rid of an entire button. (Typing) Yeah, ¬¬¬¬Espresso's not great in uncommenting and commenting things sometimes. So we just need to call next. Let's check that this worked, and yes, I know, it looks really bad right now. Wow, there it is. It works. Or we can change it. Great. So, let's do the autoplay now. Okay. So the autoplay would be a checkbox, I guess. (Typing) And I'm going to use a separate label because I want to style the label eventually. So let's give it an ID then, because we need the ID to connect the label to it. (Typing) Right, we've styled all the checkboxes. Oh, because we've styled all the checkboxes. And actually, we only wanted to style this grid, not all the tables. A little bit more specific. So, yeah, let's be a bit more specific. And let's indent everything inside of grid, so that we can find rules more easily. Oh okay, so you'll do that for yourself visually almost as if you were using SAS, or some-- Yeah, because now it's just a small CSS file, and you can find everything, but once it gets, like, thousands of lines this becomes really useful to find rules. Yup. And to understand what's going on. What's inside what. Okay, so now we have this and it works when you click the label, but it doesn't autoplay. So, we'll style it afterwards. Let's first make it work. So if you click-- if autoplay is clicked by default, then it starts autoplaying before you even do anything to the board. So, I guess, it should be unchecked by default. I have to rant, slightly. It annoys me so much when people don't connect the label to the checkbox. Me too. I feel like the browser should just crash immediately if the developer fails to do that, or something dire should happen. Yeah. And it's not that hard. We thought give it an ID, label for that ID, done. Yeah, exactly. Or, well, if you don't want to, because here I've given it a separate label because I want to style the label eventually, and hide the checkbox completely, and, like, style the label as a button. But usually you just need to nest the checkbox in the label. You don't even need to give them an ID and connect them. So I hate it when people don't do that. So, how would you picture the interaction working? You would click autoplay after you set up the board, and it would start playing? Good questions, well, I guess there are two options, one is-- oh, because now the next button is kind of nebulous because if it's autoplaying, then next really just means start playing. Exactly, nothing, yeah. Maybe you could click Autoplay, and then this becomes start? Oh, that works. So it's still just one button, but it's either-- it says next if Autoplay is off, or it says start if Autoplay is checked? MmmHmm. But that still doesn't seem great. Or what's the other option? We just have two buttons: one says Autoplay indefinitely, and the other one's just next? And do we have a stop button, or they would-- one of those would change to stop it? Now, we're getting into game design. Yeah. And should it have a mascot? No, it's basically UX. And I haven't played Conway's Game of Life enough to know how other people would set it up. Yeah, me neither. But would people expect. And usually, I guess-- Well, this is almost like a movie player, can we take inspiration from that? I mean, because you have, a lot of movie players have next frame, or just play indefinitely. Almost, so do we almost just like have a-- it's just a play button just means autoplay? Or maybe, yeah maybe you could click Next, and if Autoplay is checked it becomes disabled so you can't stop. But no, no, no. I'm feeling like autoplay is going to be the default so that should be, that should be the easiest thing to do. Because most of the people probably just set up the board, and then just hit play, and they would just watch it run forever. You think so? Unless they're studying each-- I mean, at this point, we want to study each transition to make sure it's happening correctly. Right. But you were skeptical. You think people would prefer to just see the frame by frame? No, I'm not sure. I prefer to see the frame by frame, but I don't think I represent most people in this. Yeah, let's just do this. Let's make it become start when autoplay is checked. Okay, and if it's not checked, it's next. Yeah. I like that. So, autoplay already has an ID anyway. This should probably be in the view class instead of outside. Okay. I guess, so let's-- Although then we would need to pass buttons into it, and maybe it's better contained this way, like just the table. Yeah, yeah, let's do it that way. So does it go in the view or the logic, that's what we're trying to decide? No, no, no, it's definitely in the view if it goes in here, but I was thinking, should it even be in this class at all, or should it be just some code outside of that class? Okay.
Simple Controller Functionality
Accessibility and Styling
We've got the majority of this game working, at least in the most basic element. We can interact with it; it follows all the rules; and we can even autoplay so that we can just keep watching it endlessly and this could be entertainment at parties, or something like that. Several things that we could keep doing though. First, is something completely unrelated, which is source code control. Do you use source code control? What do you like to use? How would you go about storing a code project like this? I use Git. I usually write few lines of code before I start using Git in a project, so I think this is a good point to start using Git here. So let's Open Parent in Terminal. Okay. So just standard command line? Yup. Old school. So we've added everything And you like a message just right on the command line. Yup. I guess I should use longer commit messages. I usually use commit messages of, like, one line long so can just write a commit message from here without switching to an editor, or to VI, or anything. But the best practice is usually to write, like, a longer commit message. Well, it's always nice to just have the summary be brief, and then if you want to write paragraphs, you can do that. Exactly, but that's something I don't do yet. It's in my resolutions for the future.
So, lots and lots of things could still be done to make this game fascinating, addicting, social, or otherwise, but we want to do a little bit of styling to make this-- because you haven't been really happy with that autoplay checkmark. Yeah. So you want it to be a button? Yeah, I want it to look like a button so let's style-- But then that kind of changes what the Next button is going to do because right now, that's been our button. Yes. So you're going to style the Autoplay checkmark, checkbox, as a button. Yes, and I'll need to style this button as well because it looks awful, so I'll do that at the same time. Okay. So, I'll add the class of button to the label, and then, so the CSS is here; let's make it second. Let's add some padding. The top padding should be always a bit smaller than the left and right padding. And why is that? It's just visually we need to see more space? Because the top and bottom padding always looks more because of all the gaps that the font has. Oh, okay. So let's-- Yeah, and you said you started in graphic design, or you're still interested in graphic design, so that informs your work? I always-- so I started computer science but I was always interested in graphic design. I read a lot of books on graphic design. It's, I guess, like a hobby of mine. I'll never be a good designer. I mean, I'm kind of decent, but I'll never be like as much of a developer as-- I'll never be as much of a designer as I am a developer. I'm much more of a developer than I am a designer, but I love design, but I don't have much talent in it. Well, that's enough to start to have an interest in it, and it helps when you're adding padding to a button. And let's cancel the border that browsers add by default. Oh, a nice, flat design. No, we'll make them a bit-- Okay, we'll make it better than that. So let's add some margins to the table. (Typing) So now we're just styling buttons so let's add anything that has a class of button as well. And right now buttons have their own styles so let's inherit the font. So now they're the same, so because the button has had its browser styles applied, it had a separate font size and now it inherits the font size, and font family, and everything from the parent. And let's add some border radius. That's always confused me because I thoughts a whole point of cascading style sheets is that everything inherits from the parent, but buttons are special. Yeah, and font is a property that should inherit, not every property inherits, but font is one of the properties that are inherited by children. Border, for example, isn't inherited, but buttons, and inputs, and text areas, and some other elements, and actually every element has a user agent style applied. So if the user agent sets a style on the element obviously it doesn't inherit and that property. So for buttons the browser sets the form property so it's not inherited unless you explicitly say it should be inherited. So let's add some border radius, make it a bit bigger. No, it was better before. I always like to add border radius, and everything, in ems so then you can scale the button. Right, then if you make the font size bigger it will still look right with the border radius. So basically you have a button that's, basically, vector. You can make it smaller or bigger. But you have to style almost, to specify almost everything in ems for that to work, but for example, I want the border to be one pixel so I would specify it in pixels. And then let's say it will be slightly darker than the element. Something like that. RGBA, I thought you were a fan of HSL, or some of these other ones. Yeah, but RGBA is more convenient when it's semi-transparent black. Black is one of the colors that it works well for, yes. Because it's all zeros. I use HSLA if I want to specify, like, semi-transparent white, or really any other color. Okay. And let's give it a box-shadow of, like, one pixel, photo clean, or blur. So here I want to use semi-transparent white. So now we get HSL, okay. So, yeah. And inset. Alright, a little a— a little gloss, a little highlight. A little bit. And let's make it a bit more subtle, maybe. And let's give it a bottom shadow as well. That could be an ems. (Typing) Like semi-transparent black, and it should be a little smaller than the element because right now it won't look very nice, so let's make it a bit smaller. And actually, the blurring was too much, so, no. That's kind of better. And let's make a pressed style as well. Actually let's add a gradient since we went that route. Were you the one who was complaining at CSSConf that now that browsers can do gradients, and fancy borders, that graphic design has moved on from all those, and actually things that people look at today with flat design could've been done in 1998? Yeah, that's a good point, but it wasn't me that made it. Okay. But I think buttons are some of the things that do benefit from not having a flat design because otherwise the user is like, what's clickable? What's decorative here and what can I actually click? So I do like flat design, but for buttons I think it's a good idea to make it obvious that they're clickable. Exactly. Or link underlines, that's one of my pet peeves. I received a letter from a mailing list the other day and there were links in the body of text, but there was no way that you could tell that any of those were links. Yeah, and that's why we've been adding things like text underlined color to CSS, or is it text decoration color, I forget, which allows you to specify the color of the underline. Because many people were complaining that underlines are too conspicuous so they specify text decoration, none, but with these properties they can actually set the color of the underline. So let's add a little gradient here from semi-transparent white to completely transparent white. Yeah, it's not too bad. Subtle. Yeah. Works. And so that the way we've defined the styling of this button, we can change the color of the button whenever we want and it just adjusts. Wow! Okay, with a named color. With any color. I'm just using named colors because it's easier, but we can use any color. And let's fix the text a bit. That's nice. So if we make the text white, we could make the font weight bold, but I suspect that white isn't-- You have a tool to show where which color works best on which background. So I've built a tool to calculate color contrast, so if the background is yellowgreen and the text color is white, that's bad. So I could make, I could use an HSL color, and let's say something like this, that's even worse, wow. I didn't mean to have a saturation of that much. So it should it be more-- no. And I know we're looking for that button to be green, or at least not red, that tells us that it would be good, but are we looking for a number of one? We're looking for a number of, I forget, three. Oh, okay. We want something higher than three. That means there's a lot of contrast between the two different colors. Yeah, and it's only three because we have bold text of a certain size, otherwise it would need to be even higher. But the rules for contrast ratio require a bit of a lower number when you have bold text. So, I don't like this color very much. And this is one of those addicting color matching games. One could play this forever. Yeah. So let's settle on something. Settle on this, I think it should be okay. Let's see how it actually looks. Doesn't look too bad, what do you think? I like it. There is definitely a lot more contrast, and roughly a green. And let's add a text shadow to increase the contrast a bit more. (Typing) No. Wrong side. Not just, no, no, it's not just that. It's just way too obvious. (Typing) And I think that padding could be a little higher. No. Yeah, maybe, I'm going to get the other side. And I don't like the shadow very much to be honest. Oh, the overall button shadow. Yeah, let's make it a bit-- Okay, let's say, I don't like this very much, but let's say this works. I remember spending half a day doing that in Photoshop, and now we can just do it in CSS -- so much better. So now that if we try to press these buttons, they don't change state so that's quite confusing. So let's add an active style. So let's remove the background image from that, and the shadow needs to be different. So the box-shadow could be something like, maybe this, to make it look a bit pressed. So we want to make it a little darker? Or lighter? And actually let's cancel the outline because we're going to add our own focus styles anyway. So we want to make it look like it's actually pressed. Yep. but this is a bit too subtle maybe. (Typing) Something like that.
Styling Labels and Rethinking Interactions
But this doesn't quite-- All right, because we haven't added anything for the label. Right. So we also-- we want the label to have the active style every time the checkbox is checked, so-- (Typing) When the checkbox is checked, the label that's next to it, if it has a class of button, it should get this style, even when the mouse pointer is not over it. Speaking of the mouse pointer, we should add cursor pointer to those buttons, so that they get this. So now we should hide the checkbox over there, but we don't want to hide all the checkboxes, so let's hide this one. And would we also use--? We need to use-- we can't hide it with display none, or with visibility hidden because then we won't be able to interact with it. Yup. So we need to hide it by using position absolute and then clip. Oh, okay, I thought we would have to use those vendor-prefixed appearance-- can we use appearance none, or that's in a different issue? We could use appearance none, but it would still be there. Okay. So it's better to hide it in this way, because what this does is basically clipping clips the element, based on this rectangle you specify, so if you specify rectangle of all zeros it ends up hiding the element, but you can still interact with it. So as you can see here, I've hidden the element, but this still works as a, sort of, toggle button. Right, because there's no toggle input element. But now if we've styled it as a button, it needs to act as a button. So an act to start the animation, start the game. Yes, because we've designed our entire interaction based on the fact that it's a checkbox. So we need to change that so that, because right now it doesn't work in the way people expect. You would expect that if you press this it would start, right? Right. So maybe Next should be disabled once you click this, and clicking this would start the game? Do you think that makes sense? That makes sense, and also the Next should not display that Start label anymore because it doesn't do anything. Of course. That makes sense. Let's go to the button code again. So then if you wanted to just go frame by frame you would have to turn off autoplay, and then just use Next. That works for me. MmmHmm. So let's set this to disabled. So if this is checked we want this to be disabled, so that works. Let's style the disabled state first. Let's make it slightly-- And this could be only on the actual button object because we're not doing this to the button class? Yeah, exactly. It's the only one that needs that. So let's set the background color only so it still has the same style, but it's more grayish. Yeah. Ahh, that works. Now we can make it a bit lighter. You know? I mean we could test this again, but I think the contrast is sufficient. Yeah, more than enough. And oh, then there's a disabled cursor we can use for disabled buttons. I forget what it is though. So let's go up to the spec, CSS3-UI. Because we got some new cursor values that are pretty useful. Not allowed. I think that's it, the most appropriate one. So you can see how-- Ooh, you get the-- that's nice. And that's a standard CSS3 feature, pretty widely supported. And it's supported pretty much by every browser in use today. See, I should put myself to sleep by reading the spec. I might learn some of the things like that. Well, the problem with reading specs is that sometimes you read about features that aren't supported yet, at all, but if you know that something is supported, like the spec would be the first place I'd look if I know that something is supported. And then I would go to some resource like Can I use, or web platform, or MDN, to see if it has any support. And sometimes it's a good idea if it doesn't have any support, to open a bug report with the browser vendor, and ask for support, because that's how browsers prioritize what they're going to implement. Oh, user bug reports. For Open Source browsers like Blink, Webkit, and Gecko, basically. You can't really do that with IE, but they have their own channels. But they don't use bug reports in that way. But for Gecko, and Webkit, and Blink, I've done it and it worked. Wow. So, right, we wanted to make this act like a button. So if this is checked now, actually this is like a double negative so let's do this instead since we need both states. If this is checked, then autoplay and click next. Otherwise clear timeout. That should work. Let's set something. Using our convenient keyboard feature. So if I try to click next, ahh, that shouldn't work. Oh, so it shows it visually but it doesn't do anything. So I believe there's a pseudo-class for enabled. Okay, stop. So I believe there's a pseudo-class for enabled. I forget in which spec it is. So this lifter specification defines several user interface selectors, so this should be in the selector's level three spec. Enabled, yes, so there is a pseudo-class for enabled. So we only want to do this when the button is enabled otherwise we don't want it to respond to active at all. Oh, okay, so enabled and active. Yeah. So now it's not clickable at all. And we've tested that autoplay works, but do you think the interaction is good? Is it better than before? I think it's definitely better than before. The, I guess, some thought could be put into whether the Next and Autoplay buttons should each have equal weight like they do now, or do we need to somehow emphasize the one that people are more likely to use. But the interaction I really like. I think it's covered a lot of the edge cases and seems to work well.
Okay, how about we add some animation? Okay, so we want to wrap up with some animation. So the simplest way to add animation would be to do something like this. So if, so let's say, one second? That's a bit long though. Let's say half a second. So that, basically, tells the browser anything you can animate, animate it and make it last half a second, or 500 milliseconds. That's might be easier to read. So that will animate both the background and the box shadow. So now if, even when we're clicking it will have the animation, or-- it's even animating the outline so we might want to restrict the properties it animates. So let's see, so now it animates these as well. That's good. But what if we want to make it go through red when it dies and green when it gets born? Right, as someone who likes data visualization, I just felt like could we reflect that somehow if it's in the dying state, or the birthing state. So let's try-- I don't even know if it's going to look good, but we could try it. Yeah, we can't do that with transitions, but we can do it with animations because transitions just go from the start value to the end value. You can't tell them, so as you're going from white to black maybe do a detour and go through red as well. You can't tell transitions that, but you can do that with animations. Okay. So let's add a keyframes animation. Birth, we'll name it birth, and we have the-- so let's add a state at 50 percent. We'll have a background; it would be green; and we also want the box-shadow. Or, actually, since we have the box-shadow, why are we even using a background of black? We can just have the box-shadow. So, unfortunately, we can't just set the color. We have to copy the entire thing because box-shadow is not a short hand. Oh, there's no box-shadow color. Exactly, sadly. It's something people have been asking for for a while. I don't think-- it's going to happen eventually, I guess, but for now there aren't any discussions in the CSS working group about that. Sadly. So now we haven't applied this anywhere yet, so let's use an animation property, but now it will work in-- so let's apply this in the checked state, otherwise it would show even when the board is created. So let's say, birth, 500 milliseconds. Oh, so you can use-- okay, you can use the keyframes there in the animation directive. Yeah, I'm not sure how well this would work though. So now it just snaps to life when you click it. Yeah, apparently. Which I think is good. I think that if I'm interacting, I feel like I want instant action. Actually, it doesn't seem to work. Not much animation. Nope. Oh, maybe—does Chrome have unprefixed animations in this version? I use prefix every so often; I've forgotten which browsers need prefixes for what. Right, and that's your plug-in to--? Yeah, it's a library I've written that allows you to write CSS without any prefixes. But I didn't want to use a library here so we'll have to do everything by hand. Style, animation, yeah, it's not there. Webkit Animation is there. So we have to do everything twice. So let's see, and this would be webkit keyframes, and then we should copy it and add the prefixes as well. But let's see if it works first. Oh, we got a green pulse. Yeah. And I think, okay, we don't have an animation when it disappears. We could probably-- Can we see that just with the birth animation? No. I feel like-- no, okay, well those were the ones that were added. Might get a little crazy if we have red on death, and green on birth. This might be enough. You think so? Or we could leave it as an exercise for the reader? Yeah. Yeah, I guess. Is that as good or as bad as you thought it was going to be? (Laughing) I can't decide if I like it or not, but I think it's an interesting effect. Let's not forget, though, that if we're using prefixes manually we should also do this. Do the right thing, yup. Yeah. Because every other browser supports animations unprefixed: Firefox, IE, so, even Safari, I think, I'm not 100 percent sure about that. Okay. So we should never forget the unprefixed version. Wow, we've written a lot of code and a bit of CSS in that time, had a few other tasks for the reader, of course. They could add the red dying animation or have an adjustable board size. Because now the board size is hard-coded here in this line, so this number could be set through some form control. Or you could also have a slider that sets the time, when it's autoplaying you could have a slider that sets how frequently this happens. Because one second might be good for some things, but might be too slow for other things if you just want to go to the final state. That's a good point. So, now that would be tough though, because then we'd have to make sure that all of our animations fit within the speed that was being specified. Yeah, or you could have a reasonable lower limit, I guess. Like 100 milliseconds as a lower limit would probably be more than-- That makes sense. It could still be visible. Yeah. And then we could have a button to clear the board if you just wanted to start over. Yeah, that's a good idea too. That's probably not too difficult but it would be interesting. Or another idea would be to use local storage to save the last state of the board and then when you refresh, it would show you the last state, so it would continue where it left. But you definitely need to add a clear button then because there's no way to clear. Right now, now I'm refreshing to clear, but if you did that then I wouldn't be able-- if we implemented that, I wouldn't be able to clear the board by refreshing so there would need to be a clear button. Right. And then for the more zany features, we could have it tweet every time it went through a lifecycle. Another good idea would be to make it detect when it's reached a point where it will just repeat indefinitely. Yes. And show that to the user. Some kind of stale mate. Or if you've reached a point where it's a still, so Wikipedia has some information about still lives and oscillators so it would detect those somehow and show it to the user, or it could count the number of generations and display that somewhere. There's so many things you could do. So many-- Or my favorite, making it kind of a Rubik's cube thing to where you have six of these, and they could may be even influence. Wrap around. Wrap around, yes. That would be a cool use of 3D transforms. Yes. Plus a little extra matrix math in there somewhere. Yeah. Well, thanks. That was fun. It was.
Released19 Jul 2014