cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - Activehtmlicon-personicon-teamoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <header></header>
<main id="main"></main>
<footer>
  <ul>
    <li class="misses">+</li>
    <li class="misses">+</li>
    <li class="misses">+</li>
    <li class="misses">+</li>
    <li class="misses">+</li>
    <li class="misses">+</li>
  </ul>
  <div id="score">0</div>
</footer>
<div id="game-over">
  <p>Game Over</p>
</div>
            
          
!
            
              $light: #eee;
$dark: #252627;
$bar: #aa6657;

body {
  height: 100vh;
  background: $dark;
  overflow: hidden;
  display: flex;
  align-items: stretch;
  flex-direction: column;
  color: $light;
  font-family: -apple-system, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
  font-family: monospace;
  font-size: 5vh;
  position: relative;
}
main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  
  p {
    font-size: 1rem;
    text-align: center;
    margin-top: 5vh;
    padding: 0 2rem;
    max-width: 30rem;
    line-height: 1.4;
  }
}
header,
footer {
  height: 5vh;
  line-height: 5vh;
  font-size: 3vh;
  background: $bar;
  text-align: right;
  text-transform: uppercase;
  padding: 0 2.5vh;
}
footer {
  display: flex;
  justify-content: space-between;
  ul {
    display: flex;
    flex-direction: row-reverse;
    
    .misses {
      padding: .5vh;
      transition: all .225s ease-out;
      &.missed {
        opacity: .4;
        transform: rotate(-45deg);
      }
    }
  }
}

main > div {
  position: absolute;
  top: 5vh;
  left: 0;
  text-transform: uppercase;
  perspective: 300px;
  transition: opacity .7s ease-in;
  font-size: 5vh;
  
  &.popped {
    opacity: 0;
    > span {
      b {
        opacity: 0;
      }
      animation-play-state: paused;
    }
  }
  &.missed {
    opacity: 0;
    > span {
      //  transform: scaleY(0);
      animation-play-state: paused;
    }
  }
  
  > span {
    position: absolute;
    display: block;
    animation: waver 2s infinite alternate ease-in-out;
    perspective: 300px;
    b {
      display: block;
      padding: 2.5vh;
      transition: opacity .25s linear;
    }
  }
}

@keyframes waver {
  100% {
    transform: translate3d(6vw, 0, 0);
  }
}


#game-over {
  opacity: 0;
  pointer-events: none;
  transition: opacity .75s ease-out;
  background: rgba(0,0,0,.75);
  
  position: absolute;
  top: 5vh;
  right: 0;
  bottom: 5vh;
  left: 0;
  width: 100%;
  
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  
  text-transform: uppercase;
  
  &.indeed {
    opacity: 1;
    pointer-events: auto;
  }
}
button {
  appearance: none;
  border-radius: 0;
  border: .3rem solid $light;
  color: $light;
  font-size: 3vh;
  padding: 1.5vh 2vh;
  background: transparent;
  margin-top: 5vh;
  font-family: monospace;
  
  &:hover {
    border-color: $bar;
  }
}
            
          
!
            
              loadGame();
polyfillKey(); 


function loadGame() {
  var button = document.createElement('button');
  button.textContent = 'Start Game';
  var main = document.getElementById('main');
  main.appendChild(button);
  var rules = document.createElement('p');
  rules.textContent = 'Letters will fall... if you have a keyboard, press the correct key to knock it away before it hits the ground';
  main.appendChild(rules);
  button.addEventListener('click', function startIt(e) {
    main.textContent = '';
    playGame();
  });
}

function playGame(replay) {
  var LETTERS = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
  var animations = {'a':[],'b':[],'c':[],'d':[],'e':[],'f':[],'g':[],'h':[],'i':[],'j':[],'k':[],'l':[],'m':[],'n':[],'o':[],'p':[],'q':[],'r':[],'s':[],'t':[],'u':[],'v':[],'w':[],'x':[],'y':[],'z':[]};
  var gameOn = true;
  var timeOffset = 2000; //interval between letters starting, will be faster over time
  var DURATION = 10000;
  var main = document.getElementById('main');
  var header = document.querySelector('header');
  var scoreElement = document.getElementById('score');
  var score = parseFloat(scoreElement.textContent);
  var rate = 1;
  var RATE_INTERVAL = .05; //playbackRate will increase by .05 for each letter... so after 20 letters, the rate of falling will be 2x what it was at the start
  var misses = 0;

  //Create a letter element and setup its falling animation, add the animation to the active animation array, and setup an onfinish handler that will represent a miss. 
  function create() {
    var idx = Math.floor(Math.random() * LETTERS.length);
    var x = (Math.random() * 85) + 'vw';
    var container = document.createElement('div');
    var letter = document.createElement('span');
    var letterText = document.createElement('b');
    letterText.textContent = LETTERS[idx];
    letter.appendChild(letterText);
    container.appendChild(letter);
    main.appendChild(container);
    var animation = container.animate([
      {transform: 'translate3d('+x+',-2.5vh,0)'},
      {transform: 'translate3d('+x+',82.5vh,0)'}
    ], {
      duration: DURATION,
      easing: 'linear',
      fill: 'both'
    });
    
    animations[LETTERS[idx]].splice(0, 0, {animation: animation, element: container});
    rate = rate + RATE_INTERVAL;
    animation.playbackRate = rate;
    
    //If an animation finishes, we will consider that as a miss, so we will remove it from the active animations array and increment our miss count
    animation.onfinish = function(e) {
      var target = container;
      var char = target.textContent;
                                      
      animations[char].pop();
      target.classList.add('missed');
      handleMisses();
    }
  }
  
  //When a miss is registered, check if we have reached the max number of misses
  function handleMisses() {
    misses++;
    var missedMarker = document.querySelector('.misses:not(.missed)');
    if (missedMarker) {
      missedMarker.classList.add('missed');
    } else {
      gameOver();
    }
  }
  
  //End game and show screen
  function gameOver() {
    gameOn = false;
    clearInterval(cleanupInterval);
    getAllAnimations().forEach(function(anim) {
      anim.pause();
    });

    //Could use Web Animations API here, but showing how you can use a mix of Web Animations API and CSS transistions
    document.getElementById('game-over').classList.add('indeed');
  }

  //Periodically remove missed elements, and lower the interval between falling elements
  var cleanupInterval = setInterval(function() {
    timeOffset = timeOffset * 4 / 5;
    cleanup();
  }, 20000);
  function cleanup() {
    [].slice.call(main.querySelectorAll('.missed')).forEach(function(missed) {
      main.removeChild(missed);
    });
  }
  
  //Firefox 48 supports document.getAnimations as per latest spec, Chrome 52 and polyfill use older spec
  function getAllAnimations() {
    if (document.getAnimations) {
      return document.getAnimations();
    } else if (document.timeline && document.timeline.getAnimations) {
      return document.timeline.getAnimations();
    }
    return [];
  }
  
  //On key press, see if it matches an active animating (falling) letter. If so, pop it from active array, pause it (to keep it from triggering "finish" logic), and add an animation on inner element with random 3d rotations that look like the letter is being kicked away to the distance. Also update score.
  function onPress(e) {
    var char = e.key;
    if (char.length === 1) {
      char = char.toLowerCase();
      if (animations[char] && animations[char].length) {
        var popped = animations[char].pop();
        popped.animation.pause();
        var target = popped.element.querySelector('b');
        var degs = [(Math.random() * 1000)-500,(Math.random() * 1000)-500,(Math.random() * 2000)-1000];
        target.animate([
          {transform: 'scale(1) rotateX(0deg) rotateY(0deg) rotateZ(0deg)',opacity:1},
          {transform: 'scale(0) rotateX('+degs[0]+'deg) rotateY('+degs[1]+'deg) rotateZ('+degs[2]+'deg)', opacity: 0}
        ], {
          duration: Math.random() * 500 + 850,
          easing: 'ease-out',
          fill: 'both'
        });
        addScore();
        header.textContent += char;
      }
    }
  }
  function addScore() {
    score++;
    scoreElement.textContent = score;
  }
  
  document.body.addEventListener('keypress', onPress);

  //start the letters falling... create the element+animation, and setup timeout for next letter to start
  function setupNextLetter() {
    if (gameOn) {
      create();
      setTimeout(function() {
        setupNextLetter();
      }, timeOffset);
    }
  }
  setupNextLetter();
}

function polyfillKey() {
  if (!('KeyboardEvent' in window) ||
        'key' in KeyboardEvent.prototype) {
    return false;
  }
  
  console.log('polyfilling KeyboardEvent.prototype.key')
  var keys = {};
  var letter = '';
  for (var i = 65; i < 91; ++i) {
    letter = String.fromCharCode(i);
    keys[i] = letter.toUpperCase();
  }
  for (var i = 97; i < 123; ++i) {
    letter = String.fromCharCode(i);
    keys[i] = letter.toLowerCase();
  }
  var proto = {
    get: function (x) {
      var key = keys[this.which || this.keyCode];
      console.log(key);
      return key;
    }
  };
  Object.defineProperty(KeyboardEvent.prototype, 'key', proto);
}


            
          
!
999px
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console