JSFiddle - React, Tailwind, and code Playground

HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Touch test</title>
    <meta name="viewport" content="user-scalable=0">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">

  </head>
  <body ontouchstart="">
    <canvas id="content"></canvas>

    <ul class="toolbar">
      <li><a href="#sparkler">Sparkler</a></li>
      <li><a href="#circle">Circle</a></li>
      <li><a href="#notes">Notes</a></li>
    </ul>
      
  </body>
</html>

CSS

html, body {
    background-color: black;
    width: 100%;
    height: 100%;
    margin: 0;
}

canvas {
    display: block;
    margin: 0;
    padding: 0;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 0;
}

a, a:visited {
    text-decoration: none;
    color: white;
}

ul.toolbar {
    color: white;
    margin: 0 auto;
    padding: 0;
    list-style: none;
    font-size: 1em;
    text-align: center;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 10;
    width: 100%;
}

ul.toolbar li {
    display: inline-block;
}

ul.toolbar li a {
    display: block;
    margin: 0 .5em;
    background-color: #333333;
    padding: 1em;
}

ul.toolbar li a.selected {
    background-color: green;
}

JavaScript

// *** WARNING ***
//
// The code below is incredibly bad. It was hacked on quickly and code
// only for the specific use-cases I wanted to play around with. I may
// clean it up eventually so it's helpful for other people.

var requestAnimFrame = (function(){
    return window.requestAnimationFrame       ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        window.oRequestAnimationFrame      ||
        window.msRequestAnimationFrame     ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        };
})();

var numParticles = 25;
var ctx;
var canvas;
var audioCtx = null;
var soundBuffer = null;
var particles = [];
var particleImg;
var effect;

function playSound(x, y, quick) {
    if(soundBuffer) {
        var sound = audioCtx.createBufferSource();
        var gain = audioCtx.createGainNode();
        sound.buffer = soundBuffer;
        sound.playbackRate.value = x/canvas.width*2;
        sound.connect(gain);
        gain.connect(audioCtx.destination);

        var volume = y/canvas.height/4;
        gain.gain.value = volume;

        if(quick) {
            sound.noteGrainOn(0., .2, .4);
        }
        else {
            sound.noteOn(0);
        }
    }
}

function Note() {
    if(audioCtx) {
        this.node = audioCtx.createJavaScriptNode(1024, 1, 1);
        this.incr = 0;

        this.gain = audioCtx.createGainNode();
        this.node.connect(this.gain);
    }
}

Note.prototype.setFrequency = function(x, y) {
    if(audioCtx) {
        var freq = x / canvas.width * 2000 + 100;
        this.incr = 2 * Math.PI * freq/audioCtx.sampleRate;

        var volume = y / canvas.height / 4;
        this.gain.gain.value = volume;
    }
};

Note.prototype.playNote = function() {
    if(this.playing) {
        this.stopNote();
    }

    if(audioCtx) {
        var x = 0;

        var _this = this;
        this.node.onaudioprocess = function(e) {
            var data =...