Thumbnail Image

Date Posted: Mar 23 2017

Tags: CSS, Design, Space, Layout, Fluid, Tricks, Tutorial

Controlling a Spaceship With CSS

In the previous article we showed you how to create pure CSS modal pop-ups utilising a trick with the CSS selector :target.

In this article we will look at a pure CSS technique for controlling CSS style changes with input elements. By utilising input elements we can trigger multiple style changes using only HTML and CSS; we will use these techniques to control the direction and movement of a spaceship through a parallax animated starfield.

Click for a demo of the project we will be recreating

SETTING UP THE BASIC LAYOUT

We are going to recreate the example shown in the demo above, and will start by creating a basic HTML markup linked to a stylesheet; that will contain the CSS styles that are going to be applied throughout the article:


<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="x-au-compatible" content="ie=edge">
	<title>CSS Controlled Starship</title>
	<meta name="description" content="CSS Controlled Starship">
	<meta name="author" content="Author Name Here">
	<meta name="viewport" content="width=device-width, intitial scale=1">
	<link rel="stylesheet" type="text/css" href="styles/styles.css">
</head>
<body>
</body>
</html>

Next, we will recreate the gradient background:


body {
    height: 100%;
    background: radial-gradient(ellipse at bottom, #1B2735 0%, #090A0F 100%);
    overflow: hidden;
}

Create a div element with id wrap that will act as a container for the project:


<body>
	<div id="wrap">
	</div>
</body>

Apply the following styles:


#wrap {
    position: absolute;
    top: 50%;
    left: 50%;
    height: 400px;
    width: 400px;
    border: 1px solid #FFFF00;
    -moz-transform: translate(-50%,-50%);
    -webkit-transform: translate(-50%,-50%);
    -ms-transform: translate(-50%,-50%);
    transform: translate(-50%,-50%);
    overflow: hidden;
}

The positioning of this element may seem a bit unusual; however, the combination of absolute positioning and translating centres the element vertically and horizontally inside its parent.

We will use this technique several times throughout the tutorial, so I will now explain how it works:

  • position: absolute This positions the element absolute to its parent.
  • top: 50%, left: 50% Since the position has been defined as absolute, this will move the element 50% from the left and top of the element's parent.

    Unfortunately this alone will not centre the element, and only centres the top left corner of the element to the centre point of its parent. However we can use the above properties in conjunction with transform: translate to resolve this issue.
  • transform: translate (-50%, -50%) When using percent values with translate, the value is based on the size of the element applied. For example, if we had an element with a width and height of 1000px and we implement a translate of 50%, this would result in translating the element 500px.

    When we translate the above element by -50%, we are translating the element -200px to the left and top this will position the centre point of our element onto the centre point of its parent.

    Using this technique will also work for fluid height and width elements allowing you to vertically and horizontally centre any element with ease.
  • -moz-, -webkit-, -ms- As you can see we have declared our translate property multiple times, these are vendor prefixes that improve backwards compatibility for older browsers. Many modern CSS3 properties require prefixes to function in older browsers, and we will use prefixes multiple times throughout this tutorial.
  • overflow:hidden The overflow property affects the rendering of any content that overflows boundaries of the element. We use the value hidden to prevent the overflowing content from rendering.

At this point you should have a gradient background and a container with a yellow border, as shown in the example displayed the below:

Image example of basic layout

CREATING THE STARFIELD

Next we will create the starfield.
The demo contains three starfields, each one containing stars of differing sizes: small, medium and large.

Let’s start by creating three div elements, one for each of the starfields.
Apply the class starfield to each div, this class will contain the styles shared between the three starfields. We will also add the classes small, medium and large, to provide the styles that are unique to the each starfield based on its size.


<div id="wrap">
	<div class="starfield small">
	</div>
	<div class="starfield medium">
	</div>
	<div class="starfield large">
	</div>
</div>

Next apply the shared styles for the starfields:


.starfield {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 600px;
    height: 600px;
    -moz-transform: translate(-50%,-50%) translateZ(0);
    -webkit-transform: translate(-50%,-50%) translateZ(0);
    -ms-transform: translate(-50%,-50%) translateZ(0);
    transform: translate(-50%,-50%) translateZ(0);
}

  • width: 600px, height: 600px You may have noticed that we have declared a height and width that is larger than our container; the reason for this is that later on we will be rotating our starfields when changing the direction of the starship. Having a starfield that is larger than our container allows the starfield to cover the container at all angles when rotated.
  • translateZ(0) We have used the translate function as before to centre the starfields inside of our container. Since we will be using rotate transform functions on this element later on, we have also added translateZ(0), this is a 3D transform. Declaring a 3D transform, even with a value of 0, causes the GPU (Graphics Processing Unit) to handle the render of the transform instead of the CPU (Central Processing Unit).

    The majority of desktop browsers will not notice any difference when changing the rendering processor; however, older mobile devices may struggle with animations on the CPU. By letting the mobile devices GPU handle the transform and animations, you will provide a smoother, more accelerated experience for mobile users. We will be applying this function to many of our graphics-heavy transforms and animations throughout the tutorial.

Now we need to create some stars for inside of our starfield.
Create a div element with the class starfield__stars as a child of each of the three starfield div elements:


<div id="wrap">
	<div class="starfield small">
		<div class="starfield__stars"></div>
	</div>
	<div class="starfield medium">
		<div class="starfield__stars"></div>
	</div>
	<div class="starfield large">
		<div class="starfield__stars"></div>
	</div>
</div>

Apply the following styles:


.starfield__stars {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: 100%;
}

Now we will use a trick with box-shadow to create our stars.
Using the CSS preprocessor SASS (Syntactically Awesome Style Sheets) we can quickly create several randomly generated numbers for our box-shadow values.

For those not familiar with SASS, or those who do not wish to use SASS, I will also provide a raw CSS version as an alternative.

SASS Version (Optional):

We will start by creating a function called create-stars with the variable parameter n:


@function create-stars ($n) {
    $value: '#{random(600)}px #{random(600)}px #FFF';
    @for $i from 2 through $n {
        $value: '#{$value} , #{random(600)}px #{random(600)}px #FFF';
    } 
    @return unquote()
}

This function creates a variable called value that contains a string value with two random pixel values, each with a maximum value of 600 (the width and height of our starfield__stars elements), and the hex code for the colour white.
An example of the string value output of the variable value would be: ‘453px 12px #FFF’.

The function will then perform a for loop from 2 to n, appending more random value strings to the value variable. With n being the number of stars we want to generate, we will define this as an argument when we call our function.

At the end of the functions for loop, the function returns the variable: value.

Next, we will create 3 more variables: stars-small, stars-medium and stars-large.


$stars-small:  create-stars(100);
$stars-medium: create-stars(40);
$stars-large:  create-stars(15);

The value of stars-small will be the output of our function create-stars looped 100 times, since we have attributed the argument of 100 to the function. We provide smaller arguments of 40 and 15 for stars-medium and stars-large as we want more small stars than medium and large. These values can be adjusted to suit your personal preference.

Next, we will apply these variables to our starfield__stars elements box-shadow property, like so:


.starfield.small .starfield__stars {
    width: 1px;
    height: 1px;
    box-shadow: $stars-small;
}

.starfield.medium .starfield__stars {
    width: 2px;
    height: 2px;
    box-shadow: $stars-medium;
}

.starfield.large .starfield__stars {
    width: 3px;
    height: 3px;
    box-shadow: $stars-large;
}

Resulting in 100 1px stars, 40 2px stars and 15 3px stars.

Raw CSS version:

You can use the following styles to create the stars created as shown above using SASS:


.starfield.small .starfield__stars {
    width: 1px;
    height: 1px;
    box-shadow: 489px 468px #FFF, 364px 571px #FFF, 43px 463px #FFF, 209px 205px #FFF, 59px 60px #FFF, 473px 456px #FFF, 208px 391px #FFF, 518px 463px #FFF, 255px 131px #FFF, 582px 331px #FFF, 165px 297px #FFF, 32px 70px #FFF, 585px 302px #FFF, 532px 459px #FFF, 242px 453px #FFF, 85px 389px #FFF, 282px 598px #FFF, 554px 250px #FFF, 347px 299px #FFF, 93px 352px #FFF, 179px 106px #FFF, 119px 572px #FFF, 405px 248px #FFF, 88px 57px #FFF, 228px 135px #FFF, 519px 302px #FFF, 94px 260px #FFF, 57px 111px #FFF, 450px 4px #FFF, 5px 544px #FFF, 321px 589px #FFF, 438px 298px #FFF, 399px 319px #FFF, 278px 551px #FFF, 134px 468px #FFF, 481px 318px #FFF, 253px 14px #FFF, 53px 33px #FFF, 229px 248px #FFF, 393px 386px #FFF, 490px 96px #FFF, 392px 109px #FFF, 180px 49px #FFF, 537px 174px #FFF, 204px 317px #FFF, 27px 408px #FFF, 571px 580px #FFF, 512px 410px #FFF, 288px 280px #FFF, 168px 504px #FFF, 214px 148px #FFF, 283px 43px #FFF, 221px 368px #FFF, 582px 89px #FFF, 445px 334px #FFF, 4px 455px #FFF, 185px 392px #FFF, 198px 260px #FFF, 202px 91px #FFF, 145px 558px #FFF, 90px 110px #FFF, 346px 473px #FFF, 25px 238px #FFF, 159px 457px #FFF, 468px 549px #FFF, 466px 106px #FFF, 298px 222px #FFF, 428px 477px #FFF, 433px 169px #FFF, 344px 582px #FFF, 207px 527px #FFF, 333px 381px #FFF, 51px 383px #FFF, 415px 590px #FFF, 268px 238px #FFF, 337px 93px #FFF, 536px 584px #FFF, 169px 31px #FFF, 343px 37px #FFF, 33px 258px #FFF, 337px 577px #FFF, 115px 408px #FFF, 566px 552px #FFF, 518px 41px #FFF, 480px 254px #FFF, 288px 140px #FFF, 14px 381px #FFF, 482px 185px #FFF, 83px 252px #FFF, 33px 326px #FFF, 175px 87px #FFF, 502px 551px #FFF, 171px 58px #FFF, 33px 133px #FFF, 266px 294px #FFF, 522px 120px #FFF, 119px 559px #FFF, 227px 378px #FFF, 423px 536px #FFF, 260px 230px #FFF;
}

.starfield.medium .starfield__stars {
    width: 2px;
    height: 2px;
    box-shadow: 83px 561px #FFF, 299px 500px #FFF, 422px 71px #FFF, 191px 473px #FFF, 265px 582px #FFF, 49px 588px #FFF, 574px 142px #FFF, 205px 94px #FFF, 185px 392px #FFF, 25px 148px #FFF, 27px 102px #FFF, 199px 543px #FFF, 37px 51px #FFF, 78px 212px #FFF, 227px 155px #FFF, 218px 25px #FFF, 186px 556px #FFF, 82px 506px #FFF, 477px 568px #FFF, 586px 165px #FFF, 557px 304px #FFF, 311px 210px #FFF, 37px 1px #FFF, 575px 24px #FFF, 262px 150px #FFF, 321px 551px #FFF, 285px 548px #FFF, 115px 141px #FFF, 414px 590px #FFF, 132px 201px #FFF, 7px 291px #FFF, 438px 437px #FFF, 469px 350px #FFF, 127px 394px #FFF, 179px 539px #FFF, 145px 427px #FFF, 168px 576px #FFF, 67px 580px #FFF, 370px 494px #FFF, 243px 176px #FFF;
}

.starfield.large .starfield__stars {
    width: 3px;
    height: 3px;
    box-shadow: 464px 394px #FFF, 98px 87px #FFF, 271px 211px #FFF, 121px 274px #FFF, 530px 259px #FFF, 226px 448px #FFF, 480px 198px #FFF, 358px 460px #FFF, 161px 479px #FFF, 214px 127px #FFF, 432px 186px #FFF, 525px 166px #FFF, 118px 206px #FFF, 448px 522px #FFF, 150px 207px #FFF;
}

As you can see it is technically possible to achieve this effect without using SASS, since SASS compiles to Raw CSS, but it would take an extremely long time to create 155 random values for each star.

You should now have something that looks like the example below (obviously your stars may differ in position due to random generation):

Image example of starfield

ANIMATING THE STARFIELDS

Before we begin to animate the starfields, we are going to reposition the stars. Change the top property of the starfields__stars class from 0 to -600px, this will position the stars 600px above the container.


.starfield__stars {
    position: absolute;
    left: 0;
    top: -600px;
    height: 100%;
    width: 100%;
}

Next, we will define the animation keyframes that we will use to animate the position of the stars:


@keyframes stars {
    0% {
        -moz-transform: translateY(0) translateZ(0);
        -webkit-transform: translateY(0) translateZ(0);
        -ms-transform: translateY(0) translateZ(0);
        transform: translateY(0) translateZ(0);
    }
    100% {
        -moz-transform: translateY(1200px) translateZ(0);
        -webkit-transform: translateY(1200px) translateZ(0);
        -ms-transform: translateY(1200px) translateZ(0);
        transform: translateY(1200px) translateZ(0);
    }
}

The position of the stars is currently 600px above the container, by translating the stars on the Y axis by 1200px we move them to 600px below the container. As discussed earlier, we will also apply the 3D transform translateZ to the transform in our animation to accelerate performance on older mobile devices.

Add the animation to the starfield__stars elements:


.starfield.small .starfield__stars {
    -moz-animation: stars 20s linear infinite; 
    -webkit-animation: stars 20s linear infinite; 
    animation: stars 20s linear infinite;  
}

.starfield.medium .starfield__stars {
    -moz-animation: stars 30s linear infinite; 
    -webkit-animation: stars 30s linear infinite; 
    animation: stars 30s linear infinite;  
}

.starfield.large .starfield__stars {
    -moz-animation: stars 50s linear infinite; 
    -webkit-animation: stars 50s linear infinite; 
    animation: stars 50s linear infinite; 
}

By providing different timings to the animations, we can change the speed of the stars movement.
We have declared 20s to the small stars, 30s to the medium stars and 50s to the large stars; this is the duration the animation will take to go from 0% to 100% keyframes. The smaller the number, the faster the stars will move.

Feel free to adjust the timings of the stars to your own personal preference.

Linear determines the animation effect, and will transition the animation equally throughout.
Infinite informs the animation to loop an infinite amount of times, without this value the animation would end after one cycle.

LOOPING THE STARFIELD ANIMATION CLEANLY

We now have a looping animation, but there are gaps in the animation when the stars are outside of the container. This problem is easily resolved by adding three secondary starfield__stars elements trailing behind each of the original starfield__stars elements.

Add an extra div element with the class starfield__stars as a child of each starfield element:


<div id="wrap">
    <div class="starfield small">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <div class="starfield medium">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <div class="starfield large">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
</div>

Next, we are going to edit our CSS so that the animations only target the original starfield__stars element of each starfield element.

Add the selector :nth-child(1) as shown below:


.starfield.small .starfield__stars:nth-child(1) {
    -moz-animation: stars 20s linear infinite; 
    -webkit-animation: stars 20s linear infinite; 
    animation: stars 20s linear infinite;  
}

.starfield.medium .starfield__stars:nth-child(1) {
    -moz-animation: stars 30s linear infinite; 
    -webkit-animation: stars 30s linear infinite; 
    animation: stars 30s linear infinite;  
}

.starfield.large .starfield__stars:nth-child(1) {
    -moz-animation: stars 50s linear infinite; 
    -webkit-animation: stars 50s linear infinite; 
    animation: stars 50s linear infinite; 
}

Now the animation will only target the original starfield__stars elements.

Add the following styles:


.starfield.small .starfield__stars:nth-child(2) {
    -moz-animation: stars 20s linear infinite 10s; 
    -webkit-animation: stars 20s linear infinite 10s; 
    animation: stars 20s linear infinite 10s;  
}

.starfield.medium .starfield__stars:nth-child(2) {
    -moz-animation: stars 30s linear infinite 15s; 
    -webkit-animation: stars 30s linear infinite 15s; 
    animation: stars 30s linear infinite 15s;  
}

.starfield.large .starfield__stars:nth-child(2) {
    -moz-animation: stars 50s linear infinite 25s; 
    -webkit-animation: stars 50s linear infinite 25s; 
    animation: stars 50s linear infinite 25s; 
}

These styles will target each of the new, secondary starfield__stars elements.
We have added a delay timing to the end of our animations; delay determines when the animation will first start. The animations above will start after 10s, 15s and 25s delays, which is specifically half of the duration of the animations. This will result in a constant flow of stars with no gap in between the animations, as these secondary starfields will begin transitioning directly behind the original starfields.

Note that if you have used different timings to the ones above you will need to make the delay exactly half of your custom timings, in order for the transition to work correctly.

ADDING THE STARSHIP

Click here to download the SVG of the starship in the demo for FREE!

Create a div with the class starship containing an img element pointing to the location of the starship SVG file:


<div id="wrap">
    <div class="starfield small">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <div class="starship">
        <img src="images/starship.svg" alt="Starship" />
    </div>
    <div class="starfield medium">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <div class="starfield large">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
</div>

We have positioned the starship element between the small starfield and the medium starfield, this will cause the small stars to pass under the ship, and the medium and large stars to pass over the ship.
You can adapt this to suit your own preference by changing the positioning of the element in the markup.

Apply the following styles to the starship class:


.starship {
    position: absolute;
    top: 50%;
    left: 50%;
    height: 75px;
    width: 75px;
    -moz-transform: translate(-50%, -50%) translateZ(0);
    -webkit-transform: translate(-50%, -50%) translateZ(0);
    -ms-transform: translate(-50%, -50%) translateZ(0);
    transform: translate(-50%, -50%) translateZ(0);
}

You should now have something that looks like the below example:

Image example of starship

CREATING THE SHIP’S CONTROLS

Now we will create a control panel that will control the direction of our spaceship. To achieve this, we are going to use HTML input elements to control CSS style changes on click.

Create four input elements before the div element with the id wrap:


<body>
    <input type="radio" name="shipcontrol" id="forward">
    <input type="radio" name="shipcontrol" id="right">
    <input type="radio" name="shipcontrol" id="backward">
    <input type="radio" name="shipcontrol" id="left">
    <div id="wrap">

We use radio type inputs as we only want one input to be selectable at a time, we can achieve this by declaring the same value for the name property on each input element. The value of the id property on each input element corresponds with the direction the spaceship will be travelling in.

Next, let’s create the buttons that will trigger these input elements, by utilising HTML input labels. When you click a label for a checkbox or radio type input, it will also trigger clicking the input itself. The advantage of this is that we can position the labels anywhere in the markup, while keeping the input elements at the top of the markup.

It is important that the input elements remain at the top of the markup, as the CSS selectors that we will be using later on cannot target elements that come before the input elements; this is due to the cascading nature of CSS.

Add the following label elements as children of the div element with id wrap, make sure to add the label elements after the last starfield element in the mark up, so the labels will appear above the starfields:


<div id="wrap">
    <div class="starfield small">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <div class="starship">
        <img src="images/starship.svg" alt="Starship" />
    </div>
    <div class="starfield medium">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <div class="starfield large">
        <div class="starfield__stars"></div>
        <div class="starfield__stars"></div>
    </div>
    <label for="forward" class="shipcontrol forward">
    </label>
    <label for="right" class="shipcontrol right">
    </label>
    <label for="backward" class="shipcontrol backward">
    </label>
    <label for="left" class="shipcontrol left">
    </label>
</div>

The property for links the label to the corresponding input with the same id value as the for value. We add the class shipcontrol for the shared styles across each label, and the classes forward, right, backward, left for the individual styles for each label.

Next, we will add the shared styles for our labels with the shipcontrol class:


.shipcontrol {
    width: 50px;
    height: 50px;
    border: 2px solid #468CDC;
    position: absolute;
    border-radius: 20px;
    -o-transition: border-color 1s linear;
    -moz-transition: border-color 1s linear;
    -webkit-transition: border-color 1s linear;
    transition: border-color 1s linear;
    box-shadow: 0 0 15px rgba(0, 255, 204, 0.15), 0 0 15px rgba(0, 255, 204, 0.15) inset;
}

You should be familiar with most of these properties by now. The box-shadow creates a blue glow effect around the element; we have also added a transition property for the border-color, as we will be creating a hover effect that changes the colour of the border to white.

At this point all of the labels are stacked on top of each other, so we will add styles for each label to position them around the spaceship:


.shipcontrol.forward {
    top: 0;
    left: 50%;
    -moz-transform: translate(-50%, 10px);
    -webkit-transform: translate(-50%, 10px);
    -ms-transform: translate(-50%, 10px);
    transform: translate(-50%, 10px);
}

.shipcontrol.right {
    top: 50%;
    right: 0;
    -moz-transform: translate(-10px, -50%);
    -webkit-transform: translate(-10px, -50%);
    -ms-transform: translate(-10px, -50%);
    transform: translate(-10px, -50%);
}

.shipcontrol.backward {
    bottom: 0;
    left: 50%;
    -moz-transform: translate(-50%, -10px);
    -webkit-transform: translate(-50%, -10px);
    -ms-transform: translate(-50%, -10px);
    transform: translate(-50%, -10px);
}

.shipcontrol.left {
    top: 50%;
    left: 0;
    -moz-transform: translate(10px, -50%);
    -webkit-transform: translate(10px, -50%);
    -ms-transform: translate(10px, -50%);
    transform: translate(10px, -50%);
}

Now, we are going to change the border-color of the selected directions label; we will also add an on hover effect that similarly changes the border-color, letting us know which direction is selected.

Add the following styles:


#forward:checked ~ #wrap .shipcontrol.forward, 
#right:checked ~ #wrap .shipcontrol.right, 
#backward:checked ~ #wrap .shipcontrol.backward, 
#left:checked ~ #wrap .shipcontrol.left,
.shipcontrol:hover {
    border-color: #FFF;    
}

There are several selectors used above, which I will now go through to eliminate any possible confusion.
Starting with first selector #forward:checked ~ #wrap .shipcontrol.forward.
:checked, this means that this style will be applied if the input with the id forward is checked.
#forward:checked ~ #wrap tells the selector to select all elements with the id wrap that are preceded with an checked element with the id forward.
Therefore, the full line: #forward:checked ~ #wrap .shipcontrol.forward targets all child elements of wrap with both classes shipcontrol and forward; as long as the element with the id wrap is preceded by an element with the id forward that is checked.

We replicate this part of code for the remaining directions in the next three lines, on the final line we create a selector that targets all the elements with the shipcontrol class that are being hovered.

The border-color should now change on click and hover of each of your labels, as shown below:

Image example of ship control hover

Next, we are going to add a directional arrow to each label, to allow us to easily identify each label and its direction.
We will create the directional arrows with only CSS, by using a trick that allows you to create triangles using the CSS border property.

To do this, start by adding a div element with the class arrow as a child of each label:


<label for="forward" class="shipcontrol forward">
        <div class="arrow"></div>
    </label>
    <label for="right" class="shipcontrol right">
        <div class="arrow"></div>
    </label>
    <label for="backward" class="shipcontrol backward">
        <div class="arrow"></div>
    </label>
    <label for="left" class="shipcontrol left">
        <div class="arrow"></div>
    </label>

Apply the following styles:


.shipcontrol .arrow {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 7.5px 12.5px 7.5px;
    border-color: transparent transparent #468CDC transparent;
    -o-transition: border-color 1s linear;
    -moz-transition: border-color 1s linear;
    -webkit-transition: border-color 1s linear;
    transition: border-color 1s linear;
}

It is really that simple to create a triangle in CSS. This works by making the top, left and right borders transparent, and leaves you with only the bottom border visible; when the element is of 0 width this creates the effect of having a three sided shape.

The illustration below shows a visual example of how this works; I have applied colours to the left and right borders to demonstrate how the shape is created:

Image example of CSS triangle border trick

The width of the triangle can be modified by adjusting the size of the left and right borders, and the height of the triangle can be modified by adjusting the size of the bottom border.

Next, we add the translate transformation to centre the arrow elements, and a rotate transformation to the right, bottom and left arrow elements so that they point in the correct direction:


.shipcontrol.forward .arrow {
    -moz-transform: translate(-50%, -50%);
    -webkit-transform: translate(-50%, -50%);
    -ms-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
}

.shipcontrol.right .arrow {
    -moz-transform: translate(-50%, -50%) rotate(90deg);
    -webkit-transform: translate(-50%, -50%) rotate(90deg);
    -ms-transform: translate(-50%, -50%) rotate(90deg);
    transform: translate(-50%, -50%) rotate(90deg);
}

.shipcontrol.backward .arrow {
    -moz-transform: translate(-50%, -50%) rotate(180deg);
    -webkit-transform: translate(-50%, -50%) rotate(180deg);
    -ms-transform: translate(-50%, -50%) rotate(180deg);
    transform: translate(-50%, -50%) rotate(180deg);
}

.shipcontrol.left .arrow {
    -moz-transform: translate(-50%, -50%) rotate(-90deg);
    -webkit-transform: translate(-50%, -50%) rotate(-90deg);
    -ms-transform: translate(-50%, -50%) rotate(-90deg);
    transform: translate(-50%, -50%) rotate(-90deg);
}

Lastly, to finish off the ship controls we will apply a colour change to the arrows when their direction is selected or hovered. We do this using the method used previously for the labels:


#forward:checked ~ #wrap .shipcontrol.forward .arrow, 
#right:checked ~ #wrap .shipcontrol.right .arrow, 
#backward:checked ~ #wrap .shipcontrol.backward .arrow, 
#left:checked ~ #wrap .shipcontrol.left .arrow,
.shipcontrol:hover .arrow {
    border-color: transparent transparent #FFF transparent;   
}

Your ship control panel should now be complete, as displayed below:

Image example of completed ship control

NAVIGATING THE SHIP

Now that we have a control panel for our ship, we just need to add some styles to allow us to change the direction of the ships navigation when we click on the controls.

Add the following styles:


#forward:checked ~ #wrap .starfield,
#forward:checked ~ #wrap .starship {
    -moz-transform: translate(-50%, -50%) rotate(0deg) translateZ(0);
    -webkit-transform: translate(-50%, -50%) rotate(0deg) translateZ(0);
    -ms-transform: translate(-50%, -50%) rotate(0deg) translateZ(0);
    transform: translate(-50%, -50%) rotate(0deg) translateZ(0);
}

#right:checked ~ #wrap .starfield,
#right:checked ~ #wrap .starship {
    -moz-transform: translate(-50%, -50%) rotate(90deg) translateZ(0);
    -webkit-transform: translate(-50%, -50%) rotate(90deg) translateZ(0);
    -ms-transform: translate(-50%, -50%) rotate(90deg) translateZ(0);
    transform: translate(-50%, -50%) rotate(90deg) translateZ(0);
}

#backward:checked ~ #wrap .starfield,
#backward:checked ~ #wrap .starship {
    -moz-transform: translate(-50%, -50%) rotate(180deg) translateZ(0);
    -webkit-transform: translate(-50%, -50%) rotate(180deg) translateZ(0);
    -ms-transform: translate(-50%, -50%) rotate(180deg) translateZ(0);
    transform: translate(-50%, -50%) rotate(180deg) translateZ(0);
}

#left:checked ~ #wrap .starfield,
#left:checked ~ #wrap .starship {
    -moz-transform: translate(-50%, -50%) rotate(-90deg) translateZ(0);
    -webkit-transform: translate(-50%, -50%) rotate(-90deg) translateZ(0);
    -ms-transform: translate(-50%, -50%) rotate(-90deg) translateZ(0);
    transform: translate(-50%, -50%) rotate(-90deg) translateZ(0);
}

When clicked, the controls will now rotate the starship and starfields in the selected direction.
Add a transition to smooth out the effect:


#forward:checked ~ #wrap .starfield,
#forward:checked ~ #wrap .starship,
#right:checked ~ #wrap .starfield,
#right:checked ~ #wrap .starship,
#backward:checked ~ #wrap .starfield,
#backward:checked ~ #wrap .starship,
#left:checked ~ #wrap .starfield,
#left:checked ~ #wrap .starship {
    -o-transition: transform 6s cubic-bezier(.25,.8,.25,1);
    -moz-transition: transform 6s cubic-bezier(.25,.8,.25,1);
    -webkit-transition: transform 6s cubic-bezier(.25,.8,.25,1);
    transition: transform 6s cubic-bezier(.25,.8,.25,1);
}

Instead of linear this time we use a transition with the value cubic-bezier, this will make the ship look like it is slowing down as it comes to the end of the rotation.
There is a great tool at http://www.cubic-bezier.com that allows you to experiment with different cubic-bezier values to create the perfect animation.

FINAL CHANGES

We can control the input elements with the labels and therefore have no use for the actual inputs, so let’s hide them.
Apply the following styles:


input {
    display: none;
}


Finally, add the following media queries:


@media only screen and (max-width: 399px) {
  #wrap {
    width: 98%;
  }
} 

@media only screen and (max-height: 399px) {
  #wrap {
    height: 98%;
  }
} 

These queries will apply a fluid width and height for devices with a smaller display than the element with id wrap, making our animation responsive for small screen devices.

SUMMARY

In this article we demonstrateed how to use input elements and the :checked selector to control CSS changes. You can utilise this same technique to achieve different effects with various projects; some possible uses include: drop down menus, modal pop-ups, changing font size and much more.

As always, I would love to see what you have created using these techniques, so please post a link below; and if you require any further help to understand any of the CSS techniques I have covered in this article, please feel free to ask in the comments.

Author Avatar

BY CODIN

Owner of Codesmite.com

Codin is a self taught web developer based in London, UK.
Over the years he has dedicated a lot of time to helping new developers, becoming a well known moderator at Team Treehouse