There are a bunch of example available in the demos folder of the library, and you can view them at . They use many of the feature of Sandpit, and are designed to show the various ways you can interact with the library.

Getting Started

Add Sandpit to your project:

npm install sandpit --save

Or if you're into Yarn, that works too:

yarn add sandpit

In your app, add:

import Sandpit from 'sandpit' // for es6
var Sandpit = require('sandpit').default // for es5

Celebrate. You are a success.

Setting up a new project

If you're looking for a quick way to set up a sandpit with an ES6 friendly environment, you can use create-react-app:

npm install create-react-app -g
create-react-app party
cd party
npm install sandpit --save
npm start

Then replace src/index.js with:

import Sandpit from 'sandpit'

var sandpit = new Sandpit('body', Sandpit.CANVAS)
sandpit.settings = {youAreGreat: {value: true}}
sandpit.loop = () => { console.log(sandpit.time) }

You can grab an example from, too.

Sticking with the script tag

Alternatively, you can use the latest Sandpit version in the browser, which is useful for Codepen and the like.

<script src=""></script>
<script>var sandpit = new Sandpit('body', Sandpit.CANVAS)</script>

You can see it in action using ES6, and ES5.


There are a number of ways to initialise a new Sandpit.

// 2D, using a container that Sandpit will add a canvas to
const sandpit = new Sandpit(document.querySelector('body'), Sandpit.CANVAS)
// You can also pass options, including 'retina', 'queryable' and 'stats'. 'retina' and `queryable` default to `true`.
const sandpit = new Sandpit(document.querySelector('body'), Sandpit.CANVAS, {retina: true, queryable: true, stats: false})
// 2D, using an existing canvas
const sandpit = new Sandpit(document.querySelector('#id_of_your_canvas'), Sandpit.CANVAS)
// 3D, using a string reference to a DOM element that Sandpit will try and find
const sandpit = new Sandpit('.some-container', Sandpit.WEBGL)
const renderer = new WebGLRenderer({canvas: sandpit.canvas, antialias: true})
// 3D, passing the canvas to Three.js
const sandpit = new Sandpit(document.querySelector('#id_of_your_canvas'), Sandpit.CANVAS)
const renderer = new WebGLRenderer({canvas: sandpit.canvas, antialias: true})

NOTE: It's worth noting that Sandpit works a little differently if you're using it outside of ES6. If you're requiring it in ES5, you'll need to append default after the require: var Sandpit = require('sandpit').default. Utilities are available at var Mathematics = require('sandpit').Mathematics, etc. If you're using it via a <script> tag directly in the browser, you can use var sandpit = new Sandpit('body', Sandpit.CANVAS), and the named utilities are available at Sandpit.Mathematics, Sandpit.Color, etc. This is due to the different ways in which ES5 and ES6 manage named exports. If you can think of a better way to handle this, you should 100% get in touch.

If Sandpit finds a DOM element that isn't a canvas, it'll add a canvas to it. If it finds a canvas element, it'll use that instead. Everyone wins!

Sandpit will automatically manage retina displays, or more specifically, displays with a pixel ratio greater than 1. If you'd like it to not do that, you can pass a third property to the options: new Sandpit('.container', Sandpit.CANVAS, {retina: false}).

That will skip the retina bits in case you wanna handle them yourself, or if performance is a concern.

Once you've made a sandpit, you'll have access to a bunch of helpers and properties to get started.

NOTE: Sandpit will not automatically fill the viewport - if you're looking for something fullscreen, you'll need to make your sandpit 100% width and height via CSS. This is to ensure flexibility for different applications. You can also use sandpit.resize = () => {} if you'd like to use window.innerWidth and window.innerHeight, but it's probably easier to just set canvas { width: 100vw; height: 100vh; } in your CSS. Up to you!

sandpit.resize = () => {
  sandpit.width = window.innerWidth
  sandpit.height = window.innerHeight



To ensure you can mess with your Sandpit in real time, without having to change a line of code, you'll want to pass it some settings.

sandpit.settings = {}

Settings take the following format:

sandpit.settings = {
  name: {value: 'default'}

Simple as that. Once you've set the settings property Sandpit will start handling them for you, and you'll get a GUI (powered by dat.gui). There are a bunch of different setting types, and generally they will be chosen automatically.


If the value of a setting is a boolean, it will be a checkbox:

sandpit.settings = {
  beGreat: {value: true}


If the value of a setting is a string, it will be a string:

sandpit.settings = {
  beGreat: {value: "Yep no worries"}


If the value of a setting is a number, it will be a number:

sandpit.settings = {
  beGreat: {value: 10}

From here you can add minimum, maximum, and step:

sandpit.settings = {
  beGreat: {value: 10, min: 5, max: 15, step: 1}

Which will work with floats and negatives, too:

sandpit.settings = {
  beGreatAndReallySmall: {value: 0.009, min: -0.009, max: 0.019, step: 0.001}


If a setting has a color property set to true, it'll be a color:

sandpit.settings = {
  strokeColor: {value: 'red', color: true}

Tip! Don't forget the color parameter. This tells dat.GUI that it should use colour picker. It would still work as a string, but you lose the fanciness. And no one wants that.


If you'd prefer a setting not be editable, just add the editable: false property to it. You can see this being used in the demos.

sandpit.settings = {
  demo: {value: 'generative', editable: false}


If you'd prefer a setting to always revert to the default, regardless of what's in the query string, just add the sticky: true property to it. You can see this being used in the demos.

sandpit.settings = {
  demo: {value: 'generative', sticky: true}


Out of the box, Sandpit will store and manage your settings as a query string. This means if you and some other great humans are working on something, and one of you finds a series of settings that looks great, you can copy and paste it to someone else (so long as they're running the same demo as you are).

If you don't want settings to be stored in the query string, you can pass that option to the Sandpit constructor:

const sandpit = new Sandpit('body', Sandpit.CANVAS, {queryable: false})

Which can be good if you want to use Sandpit in a production environment, or if you're using pushState and it's breaking things.

Clear and reset

Sandpit will also automatically add a clear and reset button.

  • Reset will destroy the query string, and refresh the page with default values.
  • Clear will, if on a 2D canvas, clear a rectangle the size of the Sandpit. If it's a 3D canvas, it will reset the clearColor to transparent, and clear the COLOR_BUFFER_BIT and DEPTH_BUFFER_BIT.

It is possible to remove clear and reset if you'd prefer not to have displayed. To do this, pass a boolean with the key clear, or reset, instead of the setting object:

sandpit.settings = {
  youAreGreat: {value: true},
  reset: false,
  clear: false


If you'd like to check the performance of your sandpit, you can do so by adding {stats: true} to your options when initialising.

const sandpit = new Sandpit(document.querySelector('body'), Sandpit.CANVAS, {stats: true})

This will add stats.js to the top-left corner of the body, and will measure the loop(). If you'd like to manipulate its styles to move it around, you can access the dom element with sandpit.stats.domElement or sandpit.stats.dom.


If you want to see debug messages from Sandpit itself, you can.

sandpit.debug = true
sandpit.debug // returns true

And you can turn them back off, too:

sandpit.debug = false


Sandpit won't automatically clear the canvas on each loop, so if you want that to happen, you can set autoClear to true:

sandpit.autoClear = true
sandpit.autoClear // returns true

And you can also turn it off again.

sandpit.autoClear = true


That said, if you need more control, you can manually clear in the loop (or anywhere, really).


Clear will, if on a 2D canvas, clear a rectangle the size of the Sandpit. If it's a 3D canvas, it will reset the clearColor to transparent, and clear the COLOR_BUFFER_BIT and DEPTH_BUFFER_BIT.


If you're looking for the context, so you can draw stuff on it, you can grab it with context

sandpit.context // returns context


If you're looking for the canvas element you can grab it with canvas

sandpit.canvas // returns canvas element


Returns the canvas width, from canvas.clientWidth.

sandpit.width // returns canvas.clientWidth


Returns the canvas height, from canvas.clientHeight.

sandpit.height // returns canvas.clientWidth


Let's say you have a multitouch demo, and you want nothing else to be clickable. That's cool, you can do that. Just enable it:

sandpit.focusTouchesOnCanvas = true
sandpit.focusTouchesOnCanvas // returns true

NOTE: You'll need to enable it before you run start(), as it will change the way the events are initialised.

It will mean your settings aren't interactive though, so it's primarily for presentations on mobile where you don't want pesky pinch-to-zoom stuff happening. You should be okay with the standard touch events, which support multitouch, but hey, who knows?


Once the loop starts, Sandpit will keep a timer running that you can use for time based stuff. It increments by 1 each frame.

sandpit.time // returns number


On a 2D canvas, fill sets the fillStyle to the string passed, and fills the canvas.

NOTE: Fill isn't currently supported for WEBGL instances

sandpit.fill('rgba(0, 0, 0, 0.5)')


Resizes the canvas to match the canvas element width, meaning if you can set and forget your canvas in CSS (for example, 100% width) without having to use window.innerWidth or similar. Portability!

If your canvas is 3D, it will also set the viewport using the drawingBufferWidth and drawingBufferHeight.


Once fired, Sandpit will:

  • Set up all events
  • Run the setup() hook, if defined
  • Set up and start the loop

It is, essentially, the big red button that gets the party started.



stop() does the opposite it of start(). When fired Sandpit will:

  • Cancel the loop
  • If the canvas exists, remove it and delete the element
  • Remove the GUI
  • Remove all events


Looking for seed-able randomness? Look no further. Pass a seed to random and it will return a function you can use to get a seeded random value. It uses seedrandom under the hood.

const random = sandpit.random('hello.')
console.log(random()) // Always 0.9282578795792454
console.log(random()) // Always 0.3752569768646784


Looking to use data inside your sandpit? Sandpit offers a handy hook to fetch, which will return a promise with the response.

const url = ''
sandpit.get(url).then((response) => {
  response = JSON.parse(response)


Clears the query string, in case you want to manually do that. Your call, you maverick.



There are a ton of hooks you can use to make fancy things happen inside your sandpit. Some have existing behaviours that you can overwrite, and some are optional. You can use them as you need.


The heart of animating your sandpit, loop() starts running when you fire start(). It uses requestAnimationFrame behind the scenes, too.

sandpit.loop = () => {}


Setup is a hook you can use to get anything ready just before the loop() starts. All the events, settings and other calls will have been made, so you can access everything that the loop() can.

sandpit.setup = () => {}


If you've got settings, Sandpit will automatically add a clear and reset option. If you don't have any settings, this will be ignored.

Normally reset will clear the query string and refresh the page, but you can override it if you'd prefer.

sandpit.reset = () => {}


If you'd prefer to manage the resizing of the canvas object yourself, you can do that. You'll lose the fancy retina management though, but I believe in you.

sandpit.resize = () => {}

NOTE: If you don't override the resize hook, resize will fire change() when the canvas element changes size.

NOTE: The resize event is tied to the window object.


The touch() hook fires when a mouse is clicked (mousedown) or a tap occurs (touchstart). The hook has access to the event, or you can take advantage of the normalised input from sandpit.input.

sandpit.touch = (event) => {}


The release() hook fires when a mouse is released (mouseup) or a tap finishes (touchend). The hook has access to the event, or you can take advantage of the normalised input from sandpit.input.

sandpit.release = (event) => {}


The move() hook fires when a mouse is moved (mousemove) or a tap is dragged (touchmove). The hook has access to the event, or you can take advantage of the normalised input from sandpit.input.

sandpit.move = (event) => {}

The mouse entering the canvas will also cause sandpit.input.inFrame to return true. Leaving the canvas will switch it back to false.


If the browser supports accelerometer events, the accelerometer() hook will fire as the device is moved. You can take advantage of the normalised input from sandpit.input.

sandpit.accelerometer = () => {}

The accelerometer uses the GyroNorm.js library, and so has access to its range of normalised device orientation and device motion information.

sandpit.input.accelerometer has helpers to the xAxis (tipping the device back and forth), the yAxis (tilting the device left and right) and rotation (the direction the device is facing). They are also available as gamma, beta and alpha, if you prefer to stick to standard naming, or if you're using example code that follows that convention.

The deviceorientation data is available on the do property:

The devicemotion data is available on the dm property:


sandpit.input = {
  x: 50,
  y: 50,
  touches: [
      x: 50,
      y: 50,
      previousX: 45,
      previousY: 55,
      force: 0.75
      x: 214,
      y: 87,
      previousX: 200,
      previousY: 85,
      force: 0.6
  accelerometer: {
    // duplicated deviceorientation helpers
    xAxis:      // beta value
    yAxis:      // gamma value
    rotation:   // alpha value
    gamma:      // gamma value
    beta:       // beta value
    alpha:      // alpha value
    // deviceorientation events
    do: {
      alpha:    // alpha value
      beta:     // beta value
      gamma:    // gamma value
      absolute: // absolute value
    // devicemotion events
    dm: {
      x:        // acceleration x value
      y:        // acceleration y value
      z:        // acceleration z value
      gx:       // accelerationIncludingGravity x value
      gy:       // accelerationIncludingGravity y value
      gz:       // accelerationIncludingGravity z value
      alpha:    // rotationRate alpha value
      beta:     // rotationRate beta value
      gamma:    // rotationRate gamma value


The input property gives you normalised input data, allowing you to skip some of the heavy lifting of dealing with inputs.


The x position of the mouse or first touch.


The y position of the mouse or first touch.


Tipping the device backwards and forwards.

Also available as input.accelerometer.beta


Tilting the device left and right.

Also available as input.accelerometer.gamma


The direction the device is facing.

Also available as input.accelerometer.alpha


An array of touch objects, each with their current x and y position, and their previous x and y position (if the touch has moved).

If the interaction is from a mouse, the touches array will contain a single object - the x and y and previous x and y position of the mouse. This allows you to use the touches array as your primary source of input data without needing to differentiate between whether the interaction is touch or mouse based. Basically, you get multitouch for free.

Full credit for this genuinely great idea goes to Sketch.js and soulwire. You really should go and check out Sketch.js—it was a big inspiration for Sandpit, it's super tiny, and there are a ton of great examples.

If you don't want to worry about that, though, you can just stick with sandpit.input.x and sandpit.input.y.

Finally, if force is available on the touch event (for example, if the interaction is on a force touch enabled iOS device) it will be available as the force property on each touch.


Returns true when the mouse is inside the canvas.


There are a number of small helper libraries, both within Sandpit and external, that you can import to help make things easier. Many of the demos show examples of these in action.


A small utility to check if a reference is something.


Returns true if the reference is a DOM element.

// true
// false


Returns true if the reference is an array.

// true
// false


Returns true if the reference is an object, not an array.

// true
// false


Returns true if the reference is a canvas element.

// true
// false


A small utility to help with some simple maths stuff.


Returns π * 2



Returns π / 2



Returns π / 4


Mathematics.randomBetween(min, max, random = Math.random)

Returns a random float between two numbers, including negatives. You can also supply your own random function, if you'd prefer seeded results.

Mathematics.randomBetween(-50.5, -25.5)

Mathematics.randomFrom(array, random = Math.random)

Not techically a mathematic function, but randomFrom will return a random item in an array, or a random key value pair from an object. You can also supply your own random function, if you'd prefer seeded results.

Mathematics.randomFrom(['you', 'are', 'so', 'great'])


A hook for the Victor.js library, which gives a ton of helpers for manipulating vectors. You can read the full API here.

Includes some additional functions, vector.setLength(scalar) and vector.addLength(scalar).

Credit to Max Kueng and contributors for Victor. ❤️


A hook for the Color library, which gives a ton of helpers for manipulating color. You can read the full API here

Credit to Qix- for Color. ❤️


A hook for the Stats library, which visualises performance. You can read about it here

It is currently built into Sandpit, but if you were keen to record stats outside of the loop(), you could implement it manually with: import { Stats} from 'sandpit'

Credit to mrdoob for stats.js. ❤️