Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <canvas width="800" height="400" id="canvas"><p class="nope">No canvas, no particles</p></canvas>
<header>
    <h1>Particle Clock</h1>
    <div id="about">
        <a href="#" id="toggle-options"></a>
        <ul id="options">
            <li><a href="#" id="quivers" class="">Quiver</a></li>
            <li><a href="#" id="gradient" class="on">Gradient</a></li>
            <li><a href="#" id="color" class="on">Colorize</a></li>
            <li><a href="#" id="valentineify" class="">Valentine-ify</a></li>
            <li class="group"><span>Mouse down: explode and repel</span></li>
            <li><span>Mouse down + shift: explode and attract</span></li>
            <li><span>Arrow Up: increase particle size</span></li>
            <li class="group"><span>Sorry about your CPU</span></li>
            <li><span id="fps"></span></li>
        </ul>
    </div>
</header>
              
            
!

CSS

              
                      * {
        margin: 0;
        outline: none;
          padding: 0;
      }
      body {
        background: #000;
        font-family: 'Lucida Grande', 'Helvetica', 'Arial';
        font-size: 10px;
        overflow: hidden;
      }
      canvas {
        background: #222;
        cursor: default;
        z-index: 1;
      }
      .nope {
        color: #fff;
        text-align: center;
        margin-top: 150px;
      }
      header {
        position: relative;
        text-shadow: 1px 1px 0px rgba(0, 0, 0, 0.5);
        text-transform: uppercase;
        width: 100%;
        z-index: 10;
      }
      #about {
        color: #fff;
        color: rgba(255, 255, 255, 0.5);
        display: block;
        float: right;
        margin: 20px;
        text-align: right;
        width: 50%;
      }
      h1 {
        color: rgba(255, 255, 255, 0.75);
        float: left;
        font-size: 10px;
        font-weight: normal;
        margin: 20px;
      }
      a {
        color: rgba(255, 255, 255, 0.5);
        display: inline-block;
        text-decoration: none;
        transition: 0.5s ease color;
        -moz-transition: 0.5s ease color;
        -o-transition: 0.5s ease color;
        -webkit-transition: 0.5s ease color;
      }
        a:hover {
          color: rgba(255, 255, 255, 0.75);
        }
      ul#options {
        list-style: none;
        margin: 10px 0 0;
        position: relative;
        right: 0;
        z-index: 10;
      }
        ul#options li {
          margin: 5px 0;
          min-width: 200px;
          opacity: 0;
          transition: 0.25s ease-in opacity;
          -moz-transition: 0.25s ease-in opacity;
          -o-transition: 0.25s ease-in opacity;
          -webkit-transition: 0.25s ease-in opacity;
        }
          ul#options li.group {
            margin-top: 15px;
          } 
          ul#options li * {
            display: none;
          }
          ul#options li a {
            box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25);
            background-color: rgba(0, 0, 0, 0.5);
            border-radius: 3px;
            padding: 3px 5px;
            position: relative;
            transition: 0.5s ease all;
            -moz-border-radius: 3px;
            -o-box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25);
            -moz-box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25);
            -webkit-box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25);
            -moz-transition: 0.5s ease all;
            -o-transition: 0.5s ease all;
            -webkit-transition: 0.5s ease all;
          }
            ul#options li a:hover {
              color: rgba(255, 255, 255, 0.75);
            }
          ul#options li a.on {
            background-color: rgba(255, 255, 255, 0.8);
            color: rgba(0, 0, 0, 0.9);
            text-shadow: 0px 0px 0px;
          }
            ul#options li a.on:after {
              content:  "\2713 ";
            }
          ul#options.on li {
            opacity: 1;
            right: 20px;
          }
            ul#options.on li * {
              display: inline-block;
            }
      ul#borders {}
        ul#borders li {
          position: fixed;
          list-style: none;
          margin: 0;
          background-color: transparent;
          background-color: rgba(0, 0, 0, 0.05);
          z-index: 100;
        }
        li#top {
          height: 10px;
          left: 0;
          right: 0;
          top: 0;
        }
        li#right {
          bottom: 10px;
          right: 0;
          top: 10px;
          width: 10px;
        }
        li#bottom {
          bottom: 0;
          height: 10px;
          left: 0;
          right: 0;
        }
        li#left {
          bottom: 10px;
          left: 0;
          top: 10px;
          width: 10px;
        }
              
            
!

JS

              
                var Clock = (function() {
        
    // private variables
    var canvas, // canvas element
        ctx, // canvas context
        bgGrad = true, // background gradient flag
        gradient, // gradient (background)
        height = 400, // canvas height
        key = {up: false, shift: false}, // key presses
        particles = [], // particle array
        particleColor = 'hsla(0, 0%, 100%, 0.3)', // particle color
        mouse = {x: 0, y: 0}, // position of mouse / touch
        press = false, // pressed flag
        quiver = false, // quiver flag
        text, // the text to copy pixels from
        textSize = 140, // (initial) textsize
        valentine = false, // valentine-ify it for a bit?
        msgTime = 100, // time to show a message before returning to clock
        updateColor = true, // update color of gradient / particles with time?
        width = 800; // canvas width
    
    // Constants
    var FRAME_RATE = 20, // frames per second target
        MIN_WIDTH = 800, // minimum width of canvas
        MIN_HEIGHT = 400, // minimum height of canvas
        PARTICLE_NUM = 600, // (max) number of particles to generate
        RADIUS = Math.PI * 2; // radius of particle
    
    var defaultStyles = function() {
        textSize = 140;
        // particle color
        particleColor = 'hsla(0, 0%, 100%, 0.3)'; 

        // color stops
        var gradientStops = { 
            0: '#333333',
            0.5: '#222222'
        };

        // create gradient
        setGradient(gradientStops);
    };
    
    var draw = function(p) {
        ctx.fillStyle = particleColor;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size, 0, RADIUS, true);
        ctx.closePath();
        ctx.fill();
    };
    
    var explode = function() {
        for(var i = 0, l = particles.length; i < l; i++) {
            var p = particles[i];

            if(p.inText) {

                var ax = mouse.x - p.px,
                ay = mouse.y - p.py,
                angle = Math.atan2(ay, ax),
                polarity,
                C = Math.cos(angle),
                S = Math.sin(angle);

                // change polarity
                // attract particles if mouse pressed, repel if shift + mousedown
                polarity = (key.shift === true) ? -1 : 1;

                p.x += polarity * (Math.pow((C-1), 2) -1) + p.velocityX * p.delta;
                p.y += polarity * (Math.pow((S-1), 2) -1) + p.velocityY * p.delta;

                // set previous positions
                p.px = p.x;
                p.py = p.y;

                draw(p);
            }
        }
    };

    var getTime = function(amPM) {
        var date = new Date(),
            hours = date.getHours(),
            timeOfDay = '';

        if(amPM) {
            hours = ( hours > 12 ) ? hours -= 12 : hours;
            hours = ( hours == 0 ) ? 12 : hours;
        } else {
            hours = pad(hours);
        }

        var minutes = pad(date.getMinutes());
        var seconds = pad(date.getSeconds());
        return {
            hours: hours,
            minutes: minutes,
            seconds: seconds,
            timeString: hours + " : " + minutes + " : " + seconds
        };
    };

    // animation loop
    var loop = function() {
      
        // clear out text
        ctx.clearRect(0, 0, width, height);

        var time = getTime(true);

        textSize = 140;

        // draw text on canvas
        if(valentine === true) {
            if(msgTime > 0) {
                textSize = 180;
                text = '♥';
                msgTime--;
            } else {
                text = time.timeString;
            }
            // valentine-ify it by setting hue to pink
            setStyles(300);

        } else if(updateColor === true && bgGrad === true) {
            // changing color with time
            // @TODO: come up with something better, this is a hacky implementation
            var color = time.hours + time.minutes + time.seconds;
            setStyles(color);
            text = time.timeString;
        } else {
            defaultStyles();
            text = time.timeString;
        }
      
        ctx.fillStyle = "rgb(255, 255, 255)";
        ctx.textBaseline = "middle";
        ctx.font = textSize + "px 'Avenir', 'Helvetica Neue', 'Arial', 'sans-serif'";
        ctx.fillText(text, (width - ctx.measureText(text).width) * 0.5, height * 0.5);

        // copy pixels
        var imgData = ctx.getImageData(0, 0, width, height);
      
        // clear canvas, again
        ctx.clearRect(0, 0, width, height);

        if(bgGrad === true) {
            // draw gradient
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, width, height);
        }

        if(press === false) {
            // reset particles
            for(var i = 0, l = particles.length; i < l; i++) {
                var p = particles[i];
                p.inText = false;
            }
            particleText(imgData);
        } else {
            explode();
        }
        FPS.update('fps');
    };

    var pad = function(number) {
        return ('0' + number).substr(-2);
    };

    var particleText = function(imgData) {

        var pxls = [];
        for(var w = width; w > 0; w-=6) {
            for(var h = 0; h < width; h+=6) {
                var index = (w+h*(width))*4;
                if(imgData.data[index] > 10) {
                    pxls.push([w, h]);
                }
            }
        }

        var count = pxls.length;
        for(var i = 0; i < pxls.length && i < particles.length; i++) {
            try {
                var p = particles[i], 
                    X, 
                    Y;
                
                if(quiver) {
                    X = (pxls[count-1][0]) - (p.px + Math.random() * 5);
                    Y = (pxls[count-1][1]) - (p.py + Math.random() * 5);
                } else {
                    X = (pxls[count-1][0]) - p.px;
                    Y = (pxls[count-1][1]) - p.py;
                }
          
                // tangent
                var T = Math.sqrt(X*X + Y*Y);

                // arctangent
                var A = Math.atan2(Y, X);
              
                // cosine
                var C = Math.cos(A);
              
                // sine
                var S = Math.sin(A);
              
                // set new postition
                p.x = p.px + C * T * p.delta;
                p.y = p.py + S * T * p.delta;
              
                // set previous positions
                p.px = p.x;
                p.py = p.y;
          
                p.inText = true;
          
                // draw the particle
                draw(p);
          
                if(key.up === true) {
                    p.size += 0.3;
                } else {
                    var newSize = p.size - 0.5;
                    if(newSize > p.origSize && newSize > 0) {
                        p.size = newSize;
                    } else {
                        p.size = m.origSize;
                    }
                }
            } catch(e) {
                //console.log(e);
            }
            count--;
        }
    };

    var setCoordinates = function(e) {
        if(e.offsetX) {
            return { x: e.offsetX, y: e.offsetY }; // use offset if available
        } else if (e.layerX) {
            return { x: e.layerX, y: e.layerY }; // firefox... make sure to position the canvas
        } else {
            // iOS. Maybe others too?
            return { x: e.pageX - canvas.offsetLeft, y: e.pageY - canvas.offsetTop };
        }
    };

    // set dimensions of canvas
    var setDimensions = function () {
        width = Math.max(window.innerWidth, MIN_WIDTH);
        height = Math.max(window.innerHeight, MIN_HEIGHT);

        // Resize the canvas
        canvas.width = width;
        canvas.height = height;

        canvas.style.position = 'absolute';
        canvas.style.left = '0px';
        canvas.style.top = '0px';
    };

    var setGradient = function(gradientStops) {
      
        // create gradient
        gradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, width);
      
        // iterate through colorstops
        for (var position in gradientStops) {
            var color = gradientStops[position];
            gradient.addColorStop(position, color);
        }
    };

    var setStyles = function(hue) {
        // color stops
        var gradientStops = { 
            0: 'hsl(' + hue + ', 100%, 100%)',
            0.5: 'hsl(' + hue +', 10%, 50%)'
        };

        // change particle color
        particleColor = 'hsla(' + hue + ', 10%, 50%, 0.3)';

        // create gradient
        setGradient(gradientStops);
    };

    /** 
     * Public Methods
     */
    return {

        init: function(canvasID) {
        
            canvas = document.getElementById(canvasID);
            // make sure canvas exists and that the browser understands it
            if(canvas === null || !canvas.getContext) {
                return;
            }
            // set context
            ctx = canvas.getContext("2d");
      
            // set dimensions
            setDimensions();
        
            // ui
            this.ui();
        
            for(var i = 0; i < PARTICLE_NUM; i++) {
                particles[i] = new Particle(canvas);
            }   
        
            // show FPS
            FPS.initialize(canvas, 'fps');
        
            // set defaults
            defaultStyles();
        
            // let's do this
            setInterval(loop, FRAME_RATE);
        
        },
      
        ui: function() {
        
            // UI: buttons and events
            var toggleOptions = document.getElementById('toggle-options'),
                options = document.getElementById('options'),
                onMsg = '[-] Hide Options',
                offMsg = '[+] Show Options',
                quiverBtn = document.getElementById('quivers'),
                gradientBtn = document.getElementById('gradient'),
                valentineifyBtn = document.getElementById('valentineify'),
                colorBtn = document.getElementById('color');
        
            toggleOptions.innerHTML = offMsg;
        
            /**
             * Events
             */
            toggleOptions.addEventListener('click', function(e) {
                e.preventDefault();
                if(options.className === 'on') {
                    options.className = '';
                    toggleOptions.innerHTML = offMsg;
                } else {
                    options.className = 'on';
                    toggleOptions.innerHTML = onMsg;
                }
            }, false);
        
            quiverBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(quiverBtn.className === 'on') {
                    quiverBtn.className = '';
                    quiver = false;
                } else {
                    quiverBtn.className = 'on';
                    quiver = true;
                }
            }, false);
        
            gradientBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(gradientBtn.className === 'on') {
                    gradientBtn.className = '';
                    bgGrad = false;
                } else {
                    gradientBtn.className = 'on';
                    bgGrad = true;
                }
            }, false);
        
            valentineifyBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(valentineifyBtn.className === 'on') {
                    valentineifyBtn.className = '';
                    valentine = false;
                    msgTime = 0;
                } else {
                    valentineifyBtn.className = 'on';
                    msgTime = 60;
                    valentine = true;
                }
            }, false);
        
            colorBtn.addEventListener('click', function(e) {
                e.preventDefault();
                if(colorBtn.className === 'on') {
                    colorBtn.className = '';
                    updateColor = false;
                } else {
                    colorBtn.className = 'on';
                    updateColor = true;
                }
            }, false);
        
            document.addEventListener('keydown', function(e) {
                switch(e.keyCode ) {
                    case 16: // shift
                        key.shift = true;
                        e.preventDefault();
                        break;
                    case 38: // up key
                        key.up = true;
                        e.preventDefault();
                        break;
                }
            }, false);
        
            document.addEventListener('keyup', function(e) {
                switch(e.keyCode ) {
                    case 16: // shift
                        key.shift = false;
                        e.preventDefault();
                        break;
                    case 38: // space
                        key.up = false;
                        e.preventDefault();
                        break;
                }
            }, false);

            window.addEventListener('resize', function(e){
                setDimensions();
            }, false);
        
            canvas.addEventListener('mousedown', function(e){
                press = true;
            }, false);

            document.addEventListener('mouseup', function(e){
                press = false;
            }, false);
        
            canvas.addEventListener('mousemove', function(e) {
                if(press) {
                    mouse = setCoordinates(e);
                }
            }, false);
        
            // @TODO: add touch events
        
        }
      
    };
    
  })();
  
  // Create new particles
  var Particle = function(canvas) {
  
        var range = Math.random() * 180 / Math.PI, // random starting point
            spread = canvas.height, // how far away from text should the particles begin?
            size = Math.random() * 7; // random size of particle
    
        this.delta = 0.25;
        this.x = 0;
        this.y = 0;
    
        // starting positions
        this.px = (canvas.width / 2) + (Math.cos(range) * spread);
        this.py = (canvas.height / 2) + (Math.sin(range) * spread);
    
        this.velocityX = Math.floor(Math.random() * 10) - 5;
        this.velocityY = Math.floor(Math.random() * 10) - 5;
    
        this.size  = size;
        this.origSize = size;
    
        this.inText = false;
    
  };
  
  var FPS = {

    // defaults
    delta: 0,
    lastTime: 0,
    frames: 0,
    totalTime: 0,
    updateTime: 0,
    updateFrames: 0,

    initialize: function(canvasID, fpsID) {
        this.lastTime = (new Date()).getTime();
        if(!document.getElementById(fpsID) && document.getElementById(canvasID)) {
            this.createFPS(canvasID, fpsID);
        }
    },

    // create FPS div if needed
    createFPS: function(canvasID, fpsID) {
        var div = document.createElement('div');
        div.setAttribute('id', fpsID);
        var canvas = document.getElementById(canvasID);
        var parent = canvas.parentNode;
        div.innerHTML = "FPS AVG: 0 CURRENT: 0";
        parent.appendChild(div);
    },

    // update FPS count
    update: function(fpsID) {    
        var now = (new Date()).getTime();
        this.delta = now - this.lastTime;
        this.lastTime = now;
        this.updateTime += this.delta;
        this.totalTime += this.delta;
        this.frames++;
        this.updateFrames++;
        document.getElementById(fpsID).innerHTML = "FPS Average: " + Math.round(1000 * this.frames / this.  totalTime) + " Current: " + Math.round(1000 * this.updateFrames / this.updateTime);
        this.updateTime = 0; // reset time
        this.updateFrames = 0; // reset frames
    }

};

Clock.init('canvas');
              
            
!
999px

Console