Estelle Weyl | @estellevw | Github | Press to advance, 2 for comments, 4 to read/write notes.

HTML5 JavaScript APIs

Estelle Weyl

http://estelle.github.io/html5jsapis/

HTML5, JavaScript and CSS APIs

Estelle Weyl

http://estelle.github.io/html5jsapis/

CubeeDoo

  • localStorage
  • sessionStorage
  • selectors API
  • custom data attrs/dataSet API
  • CSS Generated Content
  • application cache
  • Transforms
  • Transitions
  • animation
  • Audio

Also, JSON.stringify, touch events, classList

Deck Feature:
You can take notes

Click on the #4 to show some notes

They're stored with local storage

LocalStorage (+ classList, selectors API)

addNotes: function(){
  var ta = document.querySelector('.current .notes'),
      key;

  ta.classList.toggle('temphidden');
  key = firstPartOfKey() +  window.location.hash;
  ta.value = window.localStorage.getItem(key) || '';

  ta.addEventListener('keyup', function(){
      window.localStorage.setItem(key,ta.value);
  });
}

LocalStorage

localStorage.setItem('keyname', 'data value')

localStorage.getItem('keyname')
localStorage.keyname

localStorage.removeItem('keyname');

localStorage.clear();

localStorage.key(position)

localStorage.length

SessionStorage

sessionStorage.setItem('keyname', 'data value')

sessionStorage.getItem('keyname')
sessionStorage.keyname

sessionStorage.removeItem('keyname');

sessionStorage.clear();

sessionStorage.key(position)

sessionStorage.length

IE 8+

Touch Events

The 'Pause' and 'New' button event handler

eventHandlers: function(){
 if('ontouchstart' in window ||
    'createTouch' in document ||
    (window.DocumentTouch &&
    document instanceof DocumentTouch)) {
        qbdoo.btn_pause.addEventListener('touchend',
        qbdoo.pauseGameOrNewGame);
    } else {
        qbdoo.btn_pause.addEventListener('click',
        qbdoo.pauseGameOrNewGame);
    }
  },

Event Order

  1. touchstart
  2. touchmove
  3. touchend
  4. mouseover
  5. mousemove
  6. mousedown
  7. mouseup
  8. click

User may have touch & mouse!

Pause / New Button

pauseGameOrNewGame: function(){
  if (qbdoo.game.classList.contains('over')) {
    qbdoo.startNewGame();
  } else {
    qbdoo.pauseGame();
  }
},

Pause Game

pauseGame: function(newgame) {
  var currentState = {}, i, cardinfo = [];

  // if game is paused
  if(qbdoo.game.classList.contains('paused')){
    qbdoo.playGame(); return false;
  }

  // else
  qbdoo.pauseOrPlayBoard('pause');
  currentState = qbdoo.storeValues();
  for (i = 0; i < qbdoo.cardCount; i++) {
    cardinfo.push(qbdoo.cards[i].dataset);
  }
  // add level to value set
  currentState.currentLevel = qbdoo.currentLevel;
  currentState.cardPositions = JSON.stringify(cardinfo);

  // add to local storage
  localStorage.setItem('pausedgame', JSON.stringify(currentState));

  //clear the board
  qbdoo.clearAll();
},

JSON.stringify()

JSON.stringify converts an object to JSON notation representing it.

IE8+, and all other browsers

Selectors API

IE8+, and all other browsers

Don't do this

<script src="jquery.js"></script>

<script type="text/javascript">
   $('li:first').addClass('first');
</script>

jquery.min.js
from: ajax.googleapis.com/ajax/libs/jquery/1.7.2
96.2KB + HTTP request + 4J + DNS lookup

Natively...

vanilla js

document.querySelector('li').classList.add('first');

or simply

li:first-of-type {
    /* .first declarations */
}

Selectors API*

var chil = $('#bar .foo');

Natively

var el   = document.querySelector('#bar');

var chil = el.querySelectorAll('.foo'); **

or

chil = d.querySelectorAll('#bar .foo');

*Supported since IE8

**Returns a static nodelist

classList

IE10+, Android 3+, and all other browsers

classList

$('#foo').addClass('bar');
$('#foo').removeClass('bar');
$('#foo').toggle('bar');
$('#foo').hasClass('bar');
document.querySelector('#foo').classList.add('bar');
document.querySelector('#foo').classList.remove('bar');
document.querySelector('#foo').classList.toggle('bar');
fooCL = document.querySelector('#foo').classList;
fooCL.add('bar');
fooCL.remove('bar');
fooCL.toggle('bar');
fooCL.contains('bar'); // true or false

* Late to the game: Android 3,IE 10

no native iterations

jQuery

$('.foo').addClass('bar');

Native

var foos = document.querySelectorAll('.foo');

for(var i = 0; i < foos.length; i++){
  foos[i].classList.add('bar');
}

Custom Data Attributes

Card Markup

<div id="board" class="level1">
  <div data-value="2" data-position="1">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="4" data-position="2">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="7" data-position="3">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="3" data-position="4">
  <div class="face"></div>
  <div class="back"></div>
  </div>
 <div data-value="1" data-position="5">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="6" data-position="6">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="6" data-position="7">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="7" data-position="8">
  <div class="face"></div>
  <div class="back"></div>
  </div>
 <div data-value="8" data-position="9">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="2" data-position="10">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="1" data-position="11">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="4" data-position="12">
  <div class="face"></div>
  <div class="back"></div>
  </div>
 <div data-value="5" data-position="13">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="3" data-position="14">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="8" data-position="15">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="5" data-position="16">
  <div class="face"></div>
  <div class="back"></div>
  </div>
 <div data-value="0" data-position="17">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="0" data-position="18">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="0" data-position="19">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="0" data-position="20">
  <div class="face"></div>
  <div class="back"></div>
  </div>
 <div data-value="0" data-position="21">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="0" data-position="22">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="0" data-position="23">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  <div data-value="0" data-position="24">
  <div class="face"></div>
  <div class="back"></div>
  </div>
  </div>
    

Dataset API

Author-defined attributes!

Add attributes to elements in the form of data-name and access these through the DOM using dataset[name] on the element in question.

<p data-foo-bar="test">
  That's an author defined attribute
</p>

dataset property returns a DOMStringMap object of element's data- attributes

el.dataset['fooBar']

dataset

dataset is a convenience feature for handling the data-* attributes, which are exposed as camel-cased properties.

e.dataset.fooBar = 'test'
e.dataset.value = '5'
e.dataset.position = '6' 

sets the data-foo-bar or data-value attribute on e.

d.body.dataset.fooBar = 'frog';

alert("A) " + d.body.getAttribute('data-foo-bar'));
alert("B) " + d.body.dataset['fooBar']);
alert("C) " + d.body.dataset.fooBar);

Pause Game

pauseGame: function(newgame) {
  var currentState = {}, i, cardinfo = [];

  // if game is paused
  if(qbdoo.game.classList.contains('paused')){
    qbdoo.playGame(); return false;
  }

 // else
  qbdoo.pauseOrPlayBoard('pause');
  currentState = qbdoo.storeValues();
   for (i = 0; i < qbdoo.cardCount; i++) {
    cardinfo.push(qbdoo.cards[i].dataset);
  }
  // add level to value set
  currentState.currentLevel = qbdoo.currentLevel;
  currentState.cardPositions = JSON.stringify(cardinfo);

  // add to local storage
  localStorage.setItem('pausedgame', JSON.stringify(currentState));

  //clear the board
  qbdoo.clearAll();
},

Clear All. getValue

 clearAll: function() {
    for (var i = 0; i < qbdoo.cardCount; i++) {
      qbdoo.cards[i].dataset.value = 0;
    }
  },
 getValue: function(mycard) {
    return mycard.dataset['value'];
  },

setupGame: function(savedCards) {
  var cardsValues, cards, dbsize;
  // if starting from pause
  if(savedCards) {
    cardsValues = JSON.parse(savedCards);
    for(i = 0; i < qbdoo.cardCount; i++){
      for (key in cardsValues[i]) {
        qbdoo.cards[i].dataset[key] = cardsValues[i][key];
      }
    }
  } else { // not from pause
 if (qbdoo.iterations &&
  (qbdoo.iterations%qbdoo.iterationsPerLevel == 0)){
     qbdoo.levelUp();
 }

// populate all the active blocks with data valuess
for (var i = 1; i <= qbdoo.cardCount; i++) {
  var num = "";
  while (!num) {
    num = qbdoo.randomize();
  }

      // set the data-value for each card
      qbdoo.board.querySelector("div[data-position='" + i + "']").setAttribute("data-value", num);
      }
      // add to iterations so that iterationsPerLevel will eventually cause a level increase.
      qbdoo.iterations++;
    }
    if(qbdoo.storageType !== "local"){
      if(!qbdoo.db){
        if (window.openDatabase) {
          dbSize = 5 * 1024 * 1024;
            qbdoo.db = openDatabase("highscoresDB", "1.0", "scores", 200000);
          }
      }
      qbdoo.loadHighScoresSQL();
      qbdoo.loadHighScoresSQL();
    } else {
      qbdoo.loadHighScoresLocal();
    }
    qbdoo.renderHighScores();
    qbdoo.cardarray = [];
    qbdoo.cardarray2 = [];
    qbdoo.timerPause = true;
    qbdoo.pauseOrPlayBoard('pause');
    qbdoo.cardClicks();
    qbdoo.eventHandlers();
    qbdoo.setTimer(savedCards);
    qbdoo.changeTheme();
    qbdoo.congratulations('off');
    qbdoo.writeLevel();
  },

data-* attributes

Define your own DOM manipulable data attributes

  <div data-value="0" data-position="24">
    <div class="face"></div>
    <div class="back"></div>
  </div>

All browsers support "data-*", but full support of 'dataset started with IE10, FF6, Safari 5.1, Chrome 7, Opera 11.1. Supported in iOS and Android. Polly fill

Notes on data- & dataset

  • Intended for site scripts, not publicly-usable metadata.
  • data-name is case insensitive (html), as it converts to lowercase (xhtml)
  • In JS, data-name is camelCased. Drop the "data-"
  • All browsers can use data-* attributes, accessing via getAttribute.
  • All browsers EXCEPT IE support accessing values with dataset property.

WHAT-WG Specification

CubeeDoo

Play

Generated Content

Part of CSS 2.1. Supported since IE8

for the number theme, change <body> class to "n"

.n div[data-value="1"]  .face:after { content: '1';}
.n div[data-value="2"]  .face:after { content: '2';}
.n div[data-value="3"]  .face:after { content: '3';}
.n div[data-value="4"]  .face:after { content: '4';}
.n div[data-value="5"]  .face:after { content: '5';}
.n div[data-value="6"]  .face:after { content: '6';}
.n div[data-value="7"]  .face:after { content: '7';}
.n div[data-value="8"]  .face:after { content: '8';}
.n div[data-value="9"]  .face:after { content: '9';}
.n div[data-value="10"] .face:after { content:'10';}
.n div[data-value="11"] .face:after { content:'11';}
.n div[data-value="12"] .face:after { content:'12';}

Generated Content

for the font shape theme, change <body> class to "s"

.s div[data-value="1"]  .face:after { content:'★';}
.s div[data-value="2"]  .face:after { content:'⚫';}
.s div[data-value="3"]  .face:after { content:'⬣';}
.s div[data-value="4"]  .face:after { content:'◼';}
.s div[data-value="5"]  .face:after { content:'⬆';}
.s div[data-value="6"]  .face:after { content:'►';}
.s div[data-value="7"]  .face:after { content:'♦';}
.s div[data-value="8"]  .face:after { content:'♥';}
.s div[data-value="9"]  .face:after { content:'♣';}
.s div[data-value="10"] .face:after { content:'♠';}
.s div[data-value="11"] .face:after { content:'☻'}
.s div[data-value="12"] .face:after { content:'⬇';}

Generated Content

What you need to know:

  • Works with :before and :after
  • content: ''; is the minimum required
  • top:0; bottom:0; left:0; right:0;
  • counters
  • attributes attr(x)
  • Tutorial on Generated Content

Going Offline

ApplicationCache

<!doctype HTML>
<html manifest="cubeedoo.appcache">
<meta charset="utf-8"/>
<title>....

Cache Manifest File

CACHE MANIFEST
#version01

#files that explicitly cached
CACHE:
index.html
css/styles.css
scripts/application.js

#Resources requiring connectivity
NETWORK:
signin.php
dosomething.cgi

FALLBACK:
/ 404.html

ApplicationCache

Add to .htaccess file

AddType text/cache-manifest .appcache
if(window.applicationCache){
 var appCache = window.applicationCache;
 appCache.update();
 if (appCache.status == appCache.UPDATEREADY ){
      appCache.swapCache();
 }
}

Transforms

2D Transform Functions

translate(<length>[, <length>])
specifies a 2D translation by the vector [x, y], where x is the translation-value parameter for the x axis and y is the optional translation value along the y axis. parameter. If y is not provided, y==0.
translateX(<length>)
specifies a translation by the given amount in the X direction.
translateY(<length>)
specifies a translation by the given amount in the Y direction.
scale(<number>[, <number>])
specifies 2D scaling operation by the [sx,sy]. If sy is not provided, sy will equal sx (growsing or shrinking with the same scale). Scale(1, 1) or scale(1) leaves an element in it's default state. Scale(2, 2) or scale(2) causes the element to appear twice as wide and twice as tall as its default size, taking up 4-times the original area.
scaleX(<number>)
specifies a scale operation using the [sx, 1] scaling vector, where sx is given as the parameter.
scaleY(<number>)
specifies a scale operation using the [1, sy] scaling vector, where sy is given as the parameter.
rotate(<angle>)
specifies a 2D rotation by the angle specified in the parameter about the origin of the element, as defined by the transform-origin property. For example, rotate(90deg) would cause elements to appear rotated one-quarter of a turn in the clockwise direction.
skewX(<angle>)
specifies a skew transformation along the X axis by the given angle.
skewY(<angle>)
specifies a skew transformation along the Y axis by the given angle.
matrix(<num>, <num>, <num>, <num>, <num>, <num>)
Generally machine generated, specifies a 2D transformation in the form of a transformation matrix of six values. matrix(a,b,c,d,e,f) is equivalent to applying the transformation matrix [a b c d e f].

Transforms

Play

transform-origin property

Specifies the x and y position of the origin, relative to the transform object.

  • Keyword positions: left, right, bottom, top, center
  • Length values
  • Percentage values
  • default is 50% 50% (or center center)
  • Supported in all browsers that support transform. Prefixed if transform is prefixed

Transitions

transitions

Enables the transition of properties from one state to the next over a defined length of time

  • transition-property: properties (or 'all') that transition
  • transition-duration: s or ms it takes to transition
  • transition-timing-function: bezier curve of transition
  • transition-delay: s or ms before transition starts
  • transition: shorthand for 4 transition properties

All browsers, prefixed. No prefix FF16 & IE10. Not in OperaMini or Win✆

Animation Essentials

  • @keyframes
  • animation-name
  • animation-duration
  • animation-direction
  • animation-iteration-count
  • animation-delay
  • animation-timing-function
  • animation-fill-mode
  • animation-play-state

See Tutorial

Animation Essentials

  • Use CSS for animation instead of JS
  • Keyframe ident can be re-used
  • Negative delay makes animation start midway thru iteration
  • Duration always comes before delay in shorthand
  • Timing function can be animated
  • Timing function steps() to animate sprites
  • Fill mode controls before and after animation
     
  • IE10, FF unprefixed since 16, Webkit with prefix, Opera.

Audio

property assignment

var qbdoo = {
  ...
  match: 'assets/match.mp3',
  noMatch: 'assets/notmatch.mp3',
  mute: false,
  ...
var qbdoo = {
  ...
  match: document.getElementById('match'),
  noMatch: document.getElementById('noMatch'),
  mute: false,
  ...

On the fly....

playSound: function(matched){
  //if sound is off for game, skip
  if(qbdoo.mute) {
    return false;
  }
  if(!qbdoo.audio){
    qbdoo.audio = document.createElement('audio')
  }
  if(matched){
    qbdoo.audio.src = qbdoo.match;
  }
  else {
    qbdoo.audio.src = qbdoo.noMatch;
  }
  qbdoo.audio.play();
}
      

in the markup ...

playSound: function(matched){
  if(qbdoo.mute) {
    return false;
  }
  if(matched){
    qbdoo.match.play();
  } else {
    qbdoo.noMatch.play();
  }
}
<audio id=match preload src=match.mp3></audio>
<audio id=noMatch preload src=fail.mp3></audio>
<audio id=gameover preload src=gameover.mp3></audio>

<audio>

  • controls shows controls
  • loop loops the audio
  • autoplay don't do this!
  • preload = none | auto | metadata
  • audio.play()
  • audio.pause()
  • audio.volume += 0.1 or -