Getting Started

  1. Use the Video Tutorial & Example to print data from the Leap Device.
  2. Follow the guide below to build your first app.
  3. Visit the downloads page for libraries, plugins, and tools.


    Working in VR?

    First check out the JavaScript VR examples and then get the lay of the land with our quickstart guide.


API Docs

Frames

LeapJS works by connecting to a websocket server running locally through the Leap Service. As often as every 10ms, the service sends a frame over the websocket and to the open page. This frame is a blob of JSON data including the positions of hands and fingers.

The browser only repaints the page every 16ms (60Hz) at best, and so these deviceFrames are not used directly. Instead, a LeapJS controller generates animation frames based off of the browser's "animation loop". (Using Node? Read up on the Javascript Frame Detection Loop)

The easiest way to access frame data is with the Leap Loop.

<div id=output></div>

<script>
  var output = document.getElementById('output');
  Leap.loop(function (frame) {
    output.innerHTML = 'Frame: ' + frame.id;
  });
</script>

ProTip™ — Although we're big fans of console.log here at Leap, it's actually not recomended while printing out frame data, as that can flood your console pretty quickly. For relaxing times, try instead setting the contents of an element on the page. You don't event need to quote your attributes.

Frame Objects

Every frame contains a lot of stuff, but the basic structure of what's needed most of the time is pretty simple. Here's the essence of a frame:

// Don't copy-paste this, it's not actually code.
LeapFrame = {
  hands: [
    {
      palmPosition: [x,y,z]
      palmNormal: [x,y,z]
      direction: [x,y,z]
      roll()
      pitch()
      yaw()
      pinchStrength: 0-1
      grabStrength:  0-1
      fingers: [
        {
          tipPosition: [x,y,z]
          direction: [x,y,z]
          bones: [
            {
              prevJoint: [x,y,z]
              nextJoint: [x,y,z]
              length: mm
              width:  mm
              center()
              matrix()
            }
          ]
        }
      ]
    }
  ]
}

Fingers and bones can be accessed through their names - thumb through pinky, and carpal, proximal, medial, and distal.

There actually a lot more that is available, such as length, speed, timeVisible, rotationMatrix, and so on. Check out the docs on Hand and Pointable for the details.

Here's an image that will be useful later. Maybe you should even save it to disk now.

Leap_axes_annotated

Example

Enough talk! Let's write an app that moves cat based on how you move your hand. We want it to move left/right and up/down on the screen as your hand moves above the Leap, and to tilt when your hand tilts.

Here's how we'll break it down:

  • An HTML image tag for the cat
  • CSS absolute positioning to place the cat on the screen
  • CSS Transforms to rotate the cat
  • Leap screenPosition plugin to know where it should be placed
  • Leap hand.roll() to tilt the cat.

Architecture

<script src="//js.leapmotion.com/leap-1.1.0.js"></script>
<script src="//js.leapmotion.com/leap-plugins-0.1.12.js"></script>

This lays out the structure of our application. We start with the `cats` data-store, a humble javascript object.

var cats = {};

Next we set up the loop. On every frame, look at every hand using the native javascript forEach method, and position the cat accordingly. setTransform() is stubbed out with no arguments, until we know what we need.

Leap.loop(function(frame) {

  frame.hands.forEach(function(hand, index) {
    var cat = ( cats[index] || (cats[index] = new Cat()) );
    cat.setTransform(...);
  });

})

We set up our Cat class, which will handle the DOM elements & interactions.

var Cat = function() {
  var cat = this;
  var img = document.createElement('img');
  img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/109794/cat_2.png';
  img.style.position = 'absolute';
  img.onload = function () {
    document.body.appendChild(img);
  }

  cat.setTransform = function() {
    // todo
  };

};

Finally, we'll be using a LeapJS plugin to handle the conversion from coordinates in the real world to a position on the screen. Plugins run one after another before the frame is given to your application, allowing them to augment the frame with additional data before it gets used.

Manipulation

ProTip™ — Manipulate, noun: "to move or control something with your hands".

The screenPosition() plugin returns an array of numbers [x,y,z] of where the hand is on-screen in pixels. It does this by taking hands `palmPosition`, given in mm relative to the Leap Device, multiplying by a constant `scale` factor, and adding a constant `verticalOffset`.

In order for this to be available on the hand object, we have to tell the loop to use plugin. We do that by calling use, and passing in a custom scale factor.

roll() describes the motion your hand makes while turning a doorknob. It returns a number in radians from your palm facing downwards. Following the right-hand-rule with the z-axis coming towards us, we know that it is positive in the counter-clockwise direction.

Leap.loop(function(frame) {

  frame.hands.forEach(function(hand, index) {
    var cat = ( cats[index] || (cats[index] = new Cat()) );
    cat.setTransform(hand.screenPosition(), hand.roll());
  });

}).use('screenPosition', {scale: 0.25});

On the CSS side of things. We look at the first two values of the position array, x and y; and use them to position the image. We're careful to subtract the image dimensions, so that it stays on-center.

CSS transforms make quick work of the rotation value; and we take care to keep things cross-browser compatible. (css3please.com can help).

cat.setTransform = function(position, rotation) {

  img.style.left = position[0] - img.width  / 2 + 'px';
  img.style.top  = position[1] - img.height / 2 + 'px';

  img.style.transform = 'rotate(' + -rotation + 'rad)';

  img.style.webkitTransform = img.style.MozTransform = img.style.msTransform =
  img.style.OTransform = img.style.transform;

};

That's almost it! Let's add a little shim so that the cat shows up even without the Leap.

// inside the Cat definition:
  img.onload = function () {
    cat.setTransform(
      [
        window.innerWidth/2,
        window.innerHeight/2
      ],
      0 );
    document.body.appendChild(img);
  }

// At the tail-end of the script
cats[0] = new Cat();

Putting it all together

var cats = {};

Leap.loop(function(frame) {

  frame.hands.forEach(function(hand, index) {

    var cat = ( cats[index] || (cats[index] = new Cat()) );
    cat.setTransform(hand.screenPosition(), hand.roll());

  });

}).use('screenPosition', {scale: 0.25});


var Cat = function() {
  var cat = this;
  var img = document.createElement('img');
  img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/109794/cat_2.png';
  img.style.position = 'absolute';
  img.onload = function () {
    cat.setTransform([window.innerWidth/2,window.innerHeight/2], 0);
    document.body.appendChild(img);
  }

  cat.setTransform = function(position, rotation) {

    img.style.left = position[0] - img.width  / 2 + 'px';
    img.style.top  = position[1] - img.height / 2 + 'px';

    img.style.transform = 'rotate(' + -rotation + 'rad)';

    img.style.webkitTransform = img.style.MozTransform = img.style.msTransform =
    img.style.OTransform = img.style.transform;

  };

};

cats[0] = new Cat();

Next Steps

At this point, you know how to make fully-qualified a Leap app. But there are plenty of bells and whistles to keep you going.

Controllers

You should know that under the hood, Leap.loop is running a controller. Any time you need to, a controller can be set up and used without the loop. Remember to call connect().

var controller = new Leap.Controller()
  .use('screenPosition')
  .connect()
  .on('frame', function(frame){
    // Try making some circles
  })

Backgrounding

You may notice that at the bottom of the codepen example itself, there is an extra line. By default, LeapJS stops animating when a windows is out of focus, in order to save CPU usage. This functionality can be easily disabled:

controller.setBackground(true);

Further Resources

We love to hear from you! Visit the community forums for help or to share your work, or visit the issue tracker on GitHub if you've got feedback on the library itself.