Building a JavaScript Yak Bak clone with Tone.js — Part 2
Part 1: https://medium.com/@cgustin/building-a-javascript-yak-bak-clone-with-tone-js-part-1-cb01b3b10529
If you followed along with part 1, welcome back! We built the HTML interface, wired it up to some JavaScript event listeners, and added recording and playback functionality with Tone.js. Function-wise, our Yak Bak is acting like a Yak Bak, but in terms of looks, it’s not quite there. To fix that, let’s add some CSS styles.
Step 1: HTML
Our HTML in part 1 was pretty bare bones, since our focus was around getting the functionality in place first, and worrying about the styling elements second. Here’s where we left off:
<div id="yakbak">
<button type="button" id="sayButton">Say</button>
<button type="button" id="playButton">Play</button>
<button type="button" id="yalpButton">Yalp</button>
</div>
And here’s what our interface looks like:
In the final version, there’s a decorative speaker element at the top, and the buttons are in a vertical column underneath. Let’s add an empty div with id “speaker” for the speaker, and wrap the three button elements in a div with id “buttons” to make them easier to arrange and position as one unit:
<div id="yakbak">
<div id="speaker"></div>
<div id="buttons">
<button type="button" id="sayButton">Say</button>
<button type="button" id="playButton">Play</button>
<button type="button" id="yalpButton">Yalp</button>
</div>
</div>
Our interface still looks the same, but structurally our HTML is done. Let’s start working on the CSS!
Step 2: Colors
The original Yak Bak had a bunch of color schemes you could choose from, but we need to pick one to start. Rather than pick colors at random, we can find some reference images and use a color sampling tool to get more accurate color values for our project. I used this reference image since the colors were relatively flat:
Source: https://www.ebay.com/itm/304238222534
And for color picking, I use the ColorZilla Chrome extension, although there are loads out there so feel free to pick your favorite. Using the color picker on the green and yellow sections in the image, I came away with “#4abbb8” for the tealy-green color, and “#f79b1b” for the yellow.
The speaker is also more of a dark gray with black holes, so I chose “#262729” for the dark gray color. This also works well to soften text that should appear black, since using the full “#000000” black hex value can be intense on light backgrounds.
We could set these colors directly in our CSS like this:
background: #4abbb8;
This approach is fine for a small codebase, however we’re going to use these values throughout our CSS and it would be nice not to have to remember these hex values if our codebase grows, or have to change them in multiple places if we decide to use different colors down the road. Luckily, we can use CSS variables to set our hex values in one place, then use them throughout our code. Then in the future, updating our colors is as simple as updating the values in one place (and since this comes up frequently in client requests, it’s worth taking this extra step).
At the top of our CSS file, we can declare our CSS variables like this:
:root {
--green: #4abbb8;
--yellow: #f79b1b;
--dark-gray: #262729;
}
Then we use them like this:
:root {
--green: #4abbb8;
--yellow: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
background: var(--green);
}
The :root selector is a pseudo selector, like :hover , that targets the root of our document, meaning these variables will be accessible to all selectors we use in our CSS. We could also declare the variables on specific elements, which would make them available to only that element.
This code works, and our main wrapper now has a green background, but there’s a small problem with our variable names. If we decide later that we want our green color to be purple, our --green variable name no longer makes sense, and we would need to do a lot of find-replacing to fix this if we had a big codebase. To avoid this, we can instead think of our app color scheme in terms of primary, secondary, tertiary, etc. colors, which gives us more flexibility if we decide to switch out colors in the future.
Looking at the Yak Bak example image, green is the main color, and yellow is used as an accent for the buttons, so we’ll change --green to --primary and --yellow to --secondary . We’ll leave --dark-gray as it is since we can always update the shade of gray without needing to change the variable name. This not only gives us more flexibility, but having a system for our colors will help us make better design decisions in the future, and give our app a more consistent feel. Our CSS now looks like this:
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
background: var(--primary);
}
And if we update our primary or secondary colors in the future, it’s as quick as changing two hex values at the top of our CSS file.
Step 3: The body and the speaker
With our color system in place, we can start to style out our elements. Starting from the outside and working our way in, the first thing we notice is that our container element is too wide and goes the width of the screen. To fix this, we could explicitly declare a width for the #yakbak element using something like width: 200px; which would work, but we would then have to update that value if any of our inner element widths change in the future. Instead, we can use width: fit-content so our container automatically sizes itself to the content inside. Now if the content size changes, we don’t have to update the container width as well.
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
background: var(--primary);
}
It would also be nice to center our Yak Bak in the middle of the screen rather than have it all the way over to the left. We can do this by setting the left and right margins to auto , which will evenly distribute the free space and center our container.
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
For now, this finishes our container. We have a green background and a flexible width that will resize to the inner content. Let’s get our speaker in place.
Since the speaker is the largest element and doesn’t need to flex or change size, we’ll set an explicit height and width and allow it to determine the overall dimensions for the Yak Bak. I chose 200px since the smallest smart phone screens clock in around 320px, so this frees us up from having to write separate mobile styles or tackle responsive design. This also looked closest to the reference image I was using.
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
}
Since we used a div for the speaker element, we don’t need to declare display: block since that’s the default setting for div elements. Let’s also give it a dark gray background so we can see the space it’s taking up.
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
background: var(--dark-gray);
}
We need to turn it into a circle, which we can do by adding border-radius: 100% .
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
background: var(--dark-gray);
border-radius: 100%;
}
Finally, we want some space between the speaker and our buttons. We could set this with a margin-top on our #buttons element to push the speaker up, but I prefer to use margin-bottom to push elements down. Either way is fine, but I recommend sticking to one or the other as much as possible in your design system to help keep a consistent flow. Let’s add margin-bottom: 20px to push the buttons down off of the speaker.
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
margin-bottom: 20px;
background: var(--dark-gray);
border-radius: 100%;
}
Note: Instead of adding margin-bottom at the bottom of the other style declarations, I’ve added it under height. This is not strictly necessary, but it is good style to group your declarations by structural (display), spacing (padding and margin), and finally decorative (borders, fonts, colors). This helps add an extra layer of organization to your CSS code.
Our speaker and container still need some polish, but for now we can move on to the buttons.
Step 4: Button alignment
Looking at the reference image, we can see that the buttons should be in one column in the center of the Yak Bak. At the moment, our buttons are in a row and smashed over to the left. Flexbox makes this fairly easy to accomplish. The first thing we need to do is declare display: flex on our #buttons element.
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
margin-bottom: 20px;
background: var(--dark-gray);
border-radius: 100%;
}
#buttons {
display: flex;
}
Now we can switch our buttons from row to column by declaring flex-direction: column and we can center our buttons with align-items: center
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
margin-bottom: 20px;
background: var(--dark-gray);
border-radius: 100%;
}
#buttons {
display: flex;
flex-direction: column;
align-items: center;
}
Our buttons are centered and in a column, but they’re all touching at the moment. We could fix this by setting margin-top or margin-bottom on the button elements themselves, but we can also do it using the flexbox gap declaration, which defines how much gap should be left between the elements in the flex container. We’ll set ours to 15px
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
margin-bottom: 20px;
background: var(--dark-gray);
border-radius: 100%;
}
#buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
Note: When choosing sizes, try to stick to multiples as much as possible. In this tutorial I’m using multiples of 5 (15, 20, 200), although I see multiples of 4 used commonly too. Doing this will help give your designs an extra layer of consistency and coherence, and save you time when deciding on sizes.
Our buttons are aligned where we want them, let’s move on to the actual buttons.
Step 5: Buttons
Looking at our reference image, our buttons share the same styling. On some models, the “Say” button is bigger than the “Play” button, and on the Yak Backwards, the “Yalp” button is the inverse of the “Play” button (yellow text on black background).
From a design standpoint, I think these are nice tweaks. The inverted colors help visually communicate the “Yalp” functionality, and the extra size on the “Say” button emphasizes that it’s the core feature, or the first step in using the device.
Apart from those differences though, our buttons will have the same shape and font. Rather than target each button ID and rewrite the same styles for each one, this would be a perfect situation to use a class , which will allow us to target multiple elements while writing one set of styles. Let’s use a base class of button . In our HTML, we’ll add the class to each button element, then add the class selector in our CSS.
<div id="yakbak">
<div id="speaker"></div>
<div id="buttons">
<button type="button" id="sayButton" class="button">Say</button>
<button type="button" id="playButton" class="button">Play</button>
<button type="button" id="yalpButton" class="button">Yalp</button>
</div>
</div>
:root {
--primary: #4abbb8;
--secondary: #f79b1b;
--dark-gray: #262729;
}
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
}
#speaker {
width: 200px;
height: 200px;
margin-bottom: 20px;
background: var(--dark-gray);
border-radius: 100%;
}
#buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.button {
}
Right now, our buttons look like default browser buttons. To change this, we can add appearance: none which will remove any default browser button styling and give us a blank canvas to work from.
Since our Yak Bak isn’t going to change size, we can also declare an explicit height and width (the more flexible option would be to use padding, but this will be fine for our purposes). I’m going with 55px to stick with the multiples of 5 in our design system.
.button {
appearance: none;
width: 55px;
height: 55px;
}
Let’s set the background to our secondary color, the font to our dark gray, and set the font weight to bold.
.button {
appearance: none;
width: 55px;
height: 55px;
background: var(--secondary);
color: var(--dark-gray);
font-weight: bold;
}
We can set our font family on the button class itself, but we likely want to use the same font for our whole app. We don’t have other text right now, but defining the font family on our container, or even on the body element, means our app is more flexible if we decide to add more text in the future. I’m going with “Arial” for mine with a fallback of “sans-serif” since it’s well supported, but feel free to try out different fonts here if you have a preference. Alternately, if we knew we were going to have multiple fonts in our app, this could be a perfect place to use CSS variables again, although it’s not necessary for our small project.
...
#yakbak {
width: fit-content;
margin-left: auto;
margin-right: auto;
background: var(--primary);
font-family: 'Arial', sans-serif;
}
...
.button {
appearance: none;
width: 55px;
height: 55px;
background: var(--secondary);
color: var(--dark-gray);
font-weight: bold;
}
We can make the buttons round with border-radius: 100% , a border to help them stand out, and force our button text to be uppercase with text-transform: uppercase
.button {
appearance: none;
width: 55px;
height: 55px;
background: var(--secondary);
color: var(--dark-gray);
font-weight: bold;
text-transform: uppercase;
border-radius: 100%;
border: 3px solid var(--dark-gray);
}
To finish them up, we’ll explicitly declare the font size, and add cursor: pointer so the users cursor will turn into the little glove hand that indicates an element is clickable. Normally we would want our font sizes to stick to the pixel grid we’ve chosen (5px in this case), but there’s some flexibility there. In this case I’ve gone with 13px, which could also be thought of as 12.5 rounded up, so it sits in between 10 and 15 and is still on our grid.
.button {
appearance: none;
width: 55px;
height: 55px;
background: var(--secondary);
color: var(--dark-gray);
font-size: 13px;
font-weight: bold;
text-transform: uppercase;
border-radius: 100%;
border: 3px solid var(--dark-gray);
cursor: pointer;
}
As a final touch, it looks like the button text in the reference image isn’t on a straight line. We can rotate our buttons slightly to get the same loose feel for ours. To do this, we can use the transform property and set it to rotate() with a specific value passed in. Let’s rotate ours 10 degrees counter clockwise, so the whole line will be transform: rotate(-10deg)
.button {
appearance: none;
width: 55px;
height: 55px;
background: var(--secondary);
color: var(--dark-gray);
font-size: 13px;
font-weight: bold;
text-transform: uppercase;
border-radius: 100%;
border: 3px solid var(--dark-gray);
cursor: pointer;
transform: rotate(-10deg);
}
With that, the base styles for our buttons are done and we can move on to the button variations.
Step 6: Button variations and active state
Our base buttons are done, but they don’t give the user any indication that they’ve been pressed, and our Say and Yalp buttons have a few design differences from the base styles that need to be addressed. Let’s start with giving the user some feedback when they click a button.
Our buttons have different states that we can target and style separately from the base styles using pseudo-classes. We want the styles to change when the button is pressed, so we’re going to use the :active pseudo class.
When the button is pressed, we’ll thicken the border to give the impression of the button moving down and getting smaller. We’ll also lower the font size slightly to add to the effect:
.button {
...
}
.button:active {
border-width: 4px;
font-size: 12px;
}
Now when the user presses a button, they get visual feedback showing the state change.
We also need to make our “Say” button bigger than the “Play” and “Yalp” buttons. If we change the size on our .button class, it will change all three buttons, so we’ll need to add a second class that overrides the base button width and height. Rather than use a class like .say-button , we’ll use .button--large so we can easily add more large buttons in the future if needed, and to communicate that this style is a variation of the base .button class. We’ll add it to our CSS, and add the class to the “Say” button in our HTML:
<div id="yakbak">
<div id="speaker"></div>
<div id="buttons">
<button type="button" id="sayButton" class="button button--large">Say</button>
<button type="button" id="playButton" class="button">Play</button>
<button type="button" id="yalpButton" class="button">Yalp</button>
</div>
</div>
.button:active {
...
}
.button--large {
width: 70px;
height: 70px;
}
We also want the colors on the “Yalp” button to be the inverse of the “Play” button. Let’s add a .button--inverse class to our CSS and to the “Yalp” button in our HTML:
<div id="yakbak">
<div id="speaker"></div>
<div id="buttons">
<button type="button" id="sayButton" class="button button--large">Say</button>
<button type="button" id="playButton" class="button">Play</button>
<button type="button" id="yalpButton" class="button button--inverse">Yalp</button>
</div>
</div>
.button--large {
...
}
.button--inverse {
background: var(--dark-gray);
color: var(--secondary);
}
Our button states and variations are done and we’re nearly there, but the design still needs a little polish.
Step 7: Final touches
Right now, the buttons are touching against the bottom of the Yak Bak container, so we’ll want to add some space there. We could do this by adding a margin-bottom or padding-bottom to our #buttons container, but if we take this element out in the future and replace it with something else, we’ll have the same problem again. So we’ll want to add the padding to the #yakbak container itself to ensure we always have a little bit of padding there. Full disclosure, the values in this section came from trying and tweaking things until it looked right, although I stuck to the 5px grid as much as possible. Let’s add some padding to our #yakbak selector in our CSS:
#yakbak {
width: fit-content;
margin: 0 auto;
padding-bottom: 30px;
background: var(--primary);
font-family: Arial, sans-serif;
}
We can also use border-radius: 100% to round off the container. In this case, because our container is a rectangle and not a square, we’ll get an ellipse shape which is perfect for mimicking the oblong Yak Bak. Alternately, you can set your border-radius values to half the container width (100px in this case), or a little bigger, to get rounded edges and still keep the rectangle intact.
#yakbak {
width: fit-content;
margin: 0 auto;
padding-bottom: 30px;
background: var(--primary);
font-family: Arial, sans-serif;
border-radius: 100%;
}
In real life, if a colored object has rounded or curved edges, they will be slightly darker than the main color of the object. We can mimic this by darkening our primary color value by 10%, and applying it as a border to the #yakbak container.
https://www.cssfontstack.com/oldsites/hexcolortool/ is an excellent tool for darkening or lightening colors easily. Let’s enter our primary color value “4abbb8”, select “darken” and “10%” and click “Submit”. The second value is the 10% darker version “31a29f”. We’ll add it to our CSS as a color variable:
:root {
--primary: #4abbb8;
--primary-dark-10: #31a29f;
--secondary: #f79b1b;
--dark-gray: #262729;
}
And then add it as a border color to our container:
#yakbak {
...
border: 3px solid var(--primary-dark-10);
}
Let’s add it to our speaker element as well to give it a little depth, and help cover up the bump sticking out at the top.
#speaker {
...
border: 3px solid var(--primary-dark-10);
}
That’s better but there’s still a little bump at the top from the container sticking out. We need to shift our speaker element up a few pixels to cover it, which we can do by setting margin-top on #speaker to a negative value, which will pull it upwards. We’ll add this above our margin-bottom property:
#speaker {
width: 200px;
height: 200px;
margin-top: -4px;
margin-bottom: 20px;
background: var(--dark-gray);
border-radius: 100%;
border: 3px solid var(--primary-dark-10);
}
Finally, our speaker needs a dot pattern to simulate the speaker grill on the Yak Bak. I’ve used https://doodad.dev/pattern-generator/ to generate the pattern, and clicked “CSS Background Image” to get the CSS property we need to add to our code. The site gives the code assigned to the background property, which will overwrite the background property we already have assigned for color. We can change it to background-image to work around this (we could also add our hex color code before we define our image URL to keep it on one line):
#speaker {
...
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='20' height='20' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(135)'%3E%3Crect width='100%25' height='100%25' fill='rgba(26, 32, 44,0)'/%3E%3Ccircle cx='40' cy='20' r='0.25' fill='rgba(0, 0, 0,1)'/%3E%3Ccircle cx='0' cy='20' r='1' fill='rgba(0, 0, 0,1)'/%3E%3Ccircle cx='40' cy='20' r='1' fill='rgba(0, 0, 0,1)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E ");
}
And with that, our Yak Bak is done. The functionality works, and we’ve given the interface some style.
In the final part, we’ll go through how to add an option for the user to switch to a different color scheme.
Top comments (0)