Jonathan Olson
PhET Interactive Simulations
| University of Colorado Boulder
Most libraries focus on either Canvas, SVG or WebGL
Most libraries only support one renderer at a time
Most libraries have few of these
Higher performance by splitting content into layers
greenContainer.layerSplit = true; // will be isolated in a layer
// Create a scene graph over a positioned block-level element
var scene = new scenery.Scene( $( '#hello-world-scene' ) );
// Add our text
scene.addChild( new scenery.Text( 'Hello World', {
centerX: 200, // the center of our text's bounds is at x = 200
centerY: 50, // the center of our text's bounds is at y = 50
font: '40px sans-serif', fill: '#eee'
} ) );
// Paint any changes (in this case, our text).
scene.updateScene();
// simple shapes can be accelerated
scene.addChild( new scenery.Rectangle( 10, 10, 100, 80, {
fill: '#f00', stroke: '#fff', lineWidth: 2
} ) );
scene.addChild( new scenery.Path( {
shape: kite.Shape.regularPolygon( 8, 30 ),
fill: '#0f0', x: 150, y: 50
} ) );
scene.addChild( new scenery.Path( {
// Canvas-like Path API. Supports SVG Shape( 'M0 90Q155 50 0 10Z' )
shape: new kite.Shape().lineTo( 0, 90 )
.quadraticCurveTo( 155, 50, 0, 10 )
.close(),
fill: '#00f', x: 190
} ) );
var imageUrl = 'http://phet.colorado.edu/sims/energy-skate-park/' +
'energy-skate-park-basics-thumbnail.png';
// direct URL reference, but cannot position based on bounds
scene.addChild( new scenery.Image( imageUrl, {
x: 100, y: -30, rotation: Math.PI / 6,
} ) );
// can also pass in HTMLImageElement or HTMLCanvasElement references
var img = document.createElement( 'img' );
img.src = imageUrl;
scene.addChild( new scenery.Image( img, { opacity: 0.25, scale: 2 } ) );
var element = document.createElement( 'span' );
element.innerHTML =
'<label style="display: inline;">Type in me:</label>' +
'<input type="text">';
scene.addChild( new scenery.DOM( element, {
x: 120, rotation: Math.PI / 6
} ) );
// HTML, with the same styling and positioning as Text
scene.addChild( new scenery.HTMLText( '<em>Italic</em> and <b>Bold</b> ', {
fontSize: 30, bottom: 230, centerX: 200, fill: '#9aa'
} ) );
first.fill = 'rgba(255,127,0,0.8)'; // full support for CSS colors
// linear gradients
secondBottom.fill = new scenery.LinearGradient( 0, 0, 100, 0 )
.addColorStop( 0, '#000' )
.addColorStop( 1, '#666' );
// radial gradients
secondTop.fill = new scenery.RadialGradient( 32, 32, 5, 64, 64, 60 )
.addColorStop( 0, '#0ff' )
.addColorStop( 1, 'rgba(0,127,255,0)' );
// patterns
var transform = dot.Matrix3.rotation2( Math.PI / 6 )
.timesMatrix( dot.Matrix3.scale( 0.3 ) );
third.fill = new scenery.Pattern( img ).setTransformMatrix( transform );
first.stroke = 'rgba(255,127,0,0.8)'; // full support for CSS colors
// linear gradients
secondBottom.stroke = new scenery.LinearGradient( 0, 0, 100, 0 )
.addColorStop( 0, '#000' )
.addColorStop( 1, '#666' );
// radial gradients
secondTop.stroke = new scenery.RadialGradient( 32, 32, 5, 64, 64, 60 )
.addColorStop( 0, '#0ff' )
.addColorStop( 1, 'rgba(0,127,255,0)' );
// patterns
var transform = dot.Matrix3.rotation2( Math.PI / 6 )
.timesMatrix( dot.Matrix3.scale( 0.3 ) );
third.stroke = new scenery.Pattern( img ).setTransformMatrix( transform );
Supports all Canvas line styles
// vertical layout
path.top = 20;
fastText.top = path.bottom + 20;
accurateText.top = fastText.bottom + 20;
// horizontal layout
fastText.left = 20; // offset from the left
path.centerX = fastText.centerX; // center the path above
accurateText.left = path.left; // align the lower text with the path
Multi-touch abstraction over DOM3/Touch/Pointer/MSPointer
section.cursor = 'pointer'; // change the cursor when the mouse is over a section
_.each( scene.children, function( section ) {
// move each section individually
section.addInputListener( new scenery.SimpleDragHandler() );
} );
Under development:
And 2 more: Build an Atom and Balloons and Static Electricity
Require.js for modularization
Mostly feature-complete, working on performance
2D Shape Handling | phetsims.github.io/kite
Math, with a focus on linear algebra | phetsims.github.io/dot
var v = dot( 10, -5 ) // shorthand for Vector2, used for points and vectors
// v.x and v.y are the components
v.normalized().times( 5 ).plus( dot( 1, 2 ) ).x // immutable, matches formulas
v.normalize().multiply( 5 ).add( dot( 1, 2 ) ).x // mutable, minimizes GCs
dot.Matrix3.rotation2( Math.PI / 6 ).timesVector2( v ) // immutable
dot.Matrix3.rotation2( Math.PI / 6 ).multiplyVector2( v ) // mutable
Chrome | |||||
Firefox | |||||
IE | |||||
Safari |
If opacity is applied to a node whose children are across different layers, they will need to be handled differently:
<!-- layers before -->
<div style="opacity: 0.6;">
<canvas><!-- red circle layer --></canvas>
<svg><!-- blue circle layer --></svg>
</div>
<!-- layers after -->
Mixed Canvas/SVG/DOM not accessible by default
Soon: Creating DOM peers for each interactive node, with subtree control over the tab order
(or catch me after)
Jonathan Olson
github.com/jonathanolson | jonathan.olson@colorado.edu
Thanks to the PhET team (phet.colorado.edu), especially Sam Reid, Chris Malley, John Blanco and director Kathy Perkins