Pure CSS Submarine

Background

About six years ago I was working with a very mundane data processing program. While looking into some styling for it I researched all the new features being added to CSS e.g. gradients, transitions, and animations. Also, at this time I realized that it was possible to abuse certain aspects of the CSS pseudo-classes. It was then that I had the completely impractical idea of building a submarine game with just CSS. Fortunately in the intervening six years things have greatly improved. For instance I no longer have to deal with browser prefixes.

Over Christmas I suffered some from JS burnout and just took a break from it. The result, I decided to try my hand at mangling CSS.

Here is the completed project. To go down press tab. To go back up press shift+tab. To fire the weapons appropriate at the different levels press the space. To detonate the weapon press space again (This last part isn’t fully functional see bottom for details). Works best in Firefox and Edge. For some reason Chrome doesn’t show the waves.

Design

Design is pretty easy for this, lots of linear gradients and grey coloring. To get some of the angled edges I at first was going to use the transparent border trick to get triangles, but using gradients on borders was difficult. So, I found and used a technique that used angled linear background gradients.

The sub design was based on this Wikipedia image and various image searches for trident missiles and mk torpedoes.

Techniques

User Interface

The key to the user interface is two invisible inputs

1

2

<input id="l1"type="checkbox"autocomplete="off"autofocus="true">

<input id="l2"type="checkbox"autocomplete="off">

tab and shift+tab toggle back and forth between the two inputs and space allows you to toggle the checked state.

This means you can use :hover and :checked to perform actions on the sub. Also, very key is the ~sibling selector. This allows any subsequent elements to refer to the state and react accordingly.

1

2

3

4

5

6

7

#l2:focus ~ #sub{/*move sub up*/

margin-top:30%;

animation:down3slinear;

}

#l1:focus ~ #sub .fin{/*angle fins when sub moves*/

animation:up3slinear;

}

Tail

The tail presented several interesting challenges. First was the simulation of the spinning propeller.

1

2

3

4

5

6

7

8

9

10

11

12

@keyframes turn{

0%{

transform:skewY(20deg)scaleY(1);

}

50%{

transform:skewY(0deg)scaleY(0);

background:#666;

}

100%{

transform:skewY(-20deg)scaleY(1);

}

}

First the rectangle is skewed to give it the angled tips. Then we scale the div from full height to 0 and back every .3 seconds. Also, as it gets shorter we lighten the color. This makes it look more transparent in the center. The second challenge was adding the vertical fins.

1

2

3

background:linear-gradient(-20deg,transparent63%,#48b63%),

linear-gradient(-160deg,transparent63%,#48b63%),

linear-gradient(0deg,#222,#777,#222);

If you’ll notice this gives the triangular tail, but it also colors the outside blue. Meaning layering is an issue and the vertical fins have to sit adjacent to the edges. This can be done by rotating the :before and :after pseudo-elements to the angle of the triangular tail, but we still want an angle on the fin as well.

1

2

3

4

5

6

7

8

9

10

11

12

13

#tail:before,#tail:after{

width:70px;

height:100px;

top:-55px;

left:40px;

background:linear-gradient(-160deg,transparent50%,#48b50%),

linear-gradient(-70deg,transparent60%,#48b60%),

linear-gradient(20deg,transparent50%,#22250%,#777,#222);

transform:rotate(70deg);

content:"";

z-index:1;

position:absolute;

}

After rotating the whole element we again add angled linear gradients. First we need the 20deg angle to get the perpendicular grey gradient (20+70=90). Then we add the blue -70deg angle to create a vertical blue strip that creates the left edge. Finally the -160deg angle finishes off the angled top right edge. Mapping this to the bottom fin was actually easy all that was needed was a negative scaling transform to flip what was already created, the supplementary angle of 70 degrees and 55 instead of -55.

1

2

3

4

#tail:after{

top:55px;

transform:rotate(110deg)scaleX(-1);

}

Weapons

Adding the torpedo was not complicated. The design was similar to that of the sub itself. The path was pretty easy well. The biggest complication was getting the z-index and layering correct.

The difficulty with the missile was getting a somewhat natural looking trajectory. I found this code that I was able to tweak to get something that looked close.

Sonar

I have to mention something about the sonar. The effect is really simple and I imagine could be useful in actual web pages e.g. showing an active connection.

1

2

3

4

5

6

7

8

9

10

#sonar:before,#sonar:after{

content:"";

position:absolute;

right:0;

top:25px;

width:50px;

height:50px;

border-right:1pxsolid#444;

border-radius:50%;

}

To begin with use border-radius:50% to get a circular shape. Add a border on the right side so that it looks like it is coming from the nose cone. Then use animation to scale it and make it fade out the large it gets.

The one thing I originally tried was using the pseudo-elements and the parent element, but since the pseudo-element’s position changes based on the parent I scrapped that idea.

Improvements

First thing that could be improved would be using a CSS precompiler like SASS. Not only would it help with a number of the repetitive linear gradients it would allow me to break this up into smaller units. This was one of those projects that you start out just hacking away at and suddenly find it would have been much better to use some type of modularity.

Another big disappointment was the fact that I couldn’t get the weapons to explode. The main problem is when you cancel the animation I want to use an animation to make the weapon disappear and a transition to make the weapon wait for the explosion and then jump back to starting position.

1

2

animation:invis1s;

transition:top0s1slinear,left0s1slinear;

Firefox it works great…the first time. After that it acts like all the other browsers and as soon as you toggle the space bar again the whole element jumps back to the starting position with no pause. Either this is the same bug in every browser or Firefox has a bug by letting it happen the first time.

The problem I addressed in this Stackoverflow question is the fact that when you have an animation it needs a start and end point. If an animation gets canceled in the middle, how do you smoothly transition back to the starting state (Or in my case wait and jump back). A return animation doesn’t work well because you don’t know the starting position. If whatever you are trying to do can be done with transitions to both states then its not a problem. But, if you are trying to create say a torpedo path you can’t just use a transition.

Another thing I couldn’t seem to work around is the the up animation occurring when you load the page and anytime you switch back to it from anything else. Because you have to use an animation for when the input is active and another one for when it isn’t, the sub is always going animate when you switch back to the page and the input receives focus again.

Some other animation improvements would be the path of the missile. I didn’t find many tutorials on creating ballistic trajectories using CSS animations. Also, a more detailed wave animation would be nice, but I wasn’t sure how to tackle that one. Also, since I didn’t get the explosion to happen I didn’t add a shaking animation, but it would be relatively simple to add an element before the sub that contracts and flexes after an explosion to simulate the concussive force. Perhaps possible, but more difficult would be adding splashing water. Related to the weapons would be the trails left by the missile and torpedo, but I didn’t have time see if that was even possible.

Conclusion

I hope you found this interesting if not practical. I have ideas for further abusing CSS pseudo-classes that I may explore in the future. Also some credit is due to several resources I used repeatedly while creating this.