绘制曲线

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>绘制曲线</title>
    <script src="https://file.alonesky.com/jscss/jquery.js"></script>
    <style>
        body {
            margin: 0;
            min-height: 100vh;
            overflow: hidden;
            font-family: Helvetica, Sans-serif;
            font-size: 85%;
        }
        form {
            position: absolute;
            top: 0;
            left: 0;
            z-index: 100;
            padding: 8px;
            font-size: 80%;
        }
        form input[type=text] {
            width: 30px;
            border: 1px solid #000;
            text-align: center;
        }
        form button {
            margin: 4px auto;
            border: 1px solid #000;
            display: block;
        }
    </style>
</head>
<body>
    <form id="settings" onsubmit="return false">
        Balls<br />
        <input type="range" id="inNum" value="20" min="4" max="300" /><br />
        Size<br />
        <input type="range" id="inSize" value="20" min="2" max="100" /><br />
        Speed<br />
        <input type="range" id="inSpeed" value="5" min="1" max="20" /><br />
        Delay<br />
        <input type="range" id="inDelay" value="8" min="1" max="50" /><br />
        <button id="btnSet">Go!</button><br />
        Presets:
        <button id="preset1">Atomic</button>
        <button id="preset2">Flower</button>
        <button id="preset3">Spiro</button>
        <button id="preset4">YinYang</button>
        <button id="preset0">Default</button>
    </form>
</body>
<script type="text/javascript">
    // Create Canvas.
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    // Store all particles.
    var particles = [];
    // The default colorset.
    // Configure settings options
    var config = {};
    var settings = document.getElementById('settings');
    initSettings();
    // Init.
    resize();
    // Fire the Go! button.
    settings.btnSet.onclick();
    // Attach canvas.
    document.body.appendChild(canvas);
    // Begin drawing.
    window.requestAnimationFrame(draw);
    // Sync canvas to window.
    window.onresize = resize;
    function createBall(angle) {
        var n = 1;
        var origin = [
            canvas.width / 2,
            canvas.height / 2
        ]
        for (var i = 0; i < n; i++) {
            particles.push(new particle({
                angle: angle,
                pos: [origin[0], origin[1]],
                size: config.size,
                speed: config.speed,
                index: i
            }));
        }
    }
    function start(delay) {
        clear();
        config.toCreate = config.num;
    }
    // Drawing.
    var tick = 0;
    function draw() {
        for (var i = 0; i < particles.length; i++) {
            var p = particles[i];
            p.move();
            p.draw(ctx);
        }
        fade();
        window.requestAnimationFrame(draw);
        if (config.toCreate) {
            if (!(tick % (config.delay))) {
                createBall((180 / config.num) * config.toCreate);
                config.toCreate--;
            }
        }
        tick++;
    }
    function particle(options) {
        this.angle = 0;
        this.curve = 0;
        this.pos = [0, 0];
        this.size = 100;
        this.speed = 1;
        this.tick = 0;
        this.color = 'rgba(255,64,64,.95)';
        this.hue = 0;
        this.waveX = false;
        this.waveY = false;
        this.index = 0;
        // Override defaults.
        for (var i in options) {
            this[i] = options[i];
        }
        this.move = function() {
            this.angle += this.curve;
            var radians = this.angle * Math.PI / 180;
            this.pos[0] += Math.cos(radians) * this.speed * Math.cos(this.tick / 50),
                this.pos[1] += Math.sin(radians) * this.speed * Math.cos(this.tick / 50);
            this.hue++
                this.color = 'hsl(' + this.hue + ', 80%, 65%)';
            this.tick++;
        }
        this.draw = function(ctx) {
            ctx.strokeStyle = this.color;
            ctx.beginPath();
            ctx.arc(this.pos[0], this.pos[1], this.size, 0, 2 * Math.PI);
            ctx.stroke();
        }
    }
    function preset(options) {
        for (var i in options) {
            if (settings[i].type == 'checkbox') {
                settings[i].checked = options[i];
                continue;
            }
            settings[i].value = options[i];
        }
        clear();
        settings.btnSet.onclick();
    }
    function fade() {
        ctx.beginPath();
        ctx.fillStyle = 'rgba(0, 0, 0, .03)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fill();
    }
    function clear() {
        ctx.beginPath();
        ctx.fillStyle = 'rgba(0, 0, 0, 1)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fill();
        particles.length = 0;
    }
    function resize() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }
    /* Common functions */
    function rand(min, max) {
        return Math.random() * (max - min) + min;
    }
    function randColor(min, max) {
        var r = Math.floor(rand(min, max)),
            g = Math.floor(rand(min, max)),
            b = Math.floor(rand(min, max));
        return 'rgba(' + r + ',' + g + ',' + b + ',1)';
    }
    // Settings
    function preset(options) {
        for (var i in options) {
            settings[i].value = options[i];
        }
        settings.btnSet.onclick();
    }
    function initSettings() {
        settings.btnSet.onclick = function() {
            config.num = parseInt(settings.inNum.value);
            config.size = parseInt(settings.inSize.value);
            config.speed = parseInt(settings.inSpeed.value);
            config.delay = parseInt(settings.inDelay.value);
            start(config.delay);
        }
        // Presets.
        settings.preset0.onclick = function() {
            preset({
                inNum: 20,
                inSize: 20,
                inSpeed: 5,
                inDelay: 8
            });
        }
        settings.preset1.onclick = function() {
            preset({
                inNum: 184,
                inSize: 10,
                inSpeed: 5,
                inDelay: 6
            });
        }
        settings.preset2.onclick = function() {
            preset({
                inNum: 79,
                inSize: 10,
                inSpeed: 5,
                inDelay: 6
            });
        }
        settings.preset3.onclick = function() {
            preset({
                inNum: 20,
                inSize: 30,
                inSpeed: 10,
                inDelay: 3
            });
        }
        settings.preset4.onclick = function() {
            preset({
                inNum: 80,
                inSize: 2,
                inSpeed: 20,
                inDelay: 1
            });
        }
    }
</script>
</html>