Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

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

Behavior

Save Automatically?

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 id="canvas">Your browser doesn't support canvas</canvas>
              
            
!

CSS

              
                html, body{
    margin:0;
    padding:0;
    background-color:hsl(195, 100%, 7%);
}

#canvas{
    margin:0 auto;
    display:block;
}
              
            
!

JS

              
                /**
 * Constants
 */
const TWO_PI = Math.PI * 2;

/**
 * Returns a random integer between a given minimum and maximum value
 * @param min - The minimum value, can be negative
 * @param max - The maximum value, can be negative
 */
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

/**
 * Application Class
 */
class Application {
    /**
     * Application constructor
     */
    constructor() {
        this.canvas = document.getElementById("canvas");
        this.context = this.canvas.getContext("2d");
        this.width = this.canvas.width = window.innerWidth;
        this.height = this.canvas.height = window.innerHeight;

        //Set an initial mouse position so the eyes have something to look at
        this.mousePosition = {
            x: this.width / 2,
            y: this.height / 2
        };

        this.ghosts = [];
        this.numberOfGhosts = Math.round((this.width + this.height) / 200);

        //Resize listener for the canvas to fill browser window dynamically
        window.addEventListener('resize', () => this.resizeCanvas(), false);
        window.addEventListener('mousemove', (e) => this.mouseMove(e), false);
        window.addEventListener('touchmove', (e) => this.touchMove(e), false);
    }

    /**
     * Simple resize function. Reinitializing everything on the canvas while changing the width/height
     */
    resizeCanvas() {
        this.width = this.canvas.width = window.innerWidth;
        this.height = this.canvas.height = window.innerHeight;
        this.numberOfGhosts = Math.round((this.width + this.height) / 200);

        //Empty the previous container and fill it again with new Ghost objects
        this.ghosts = [];
        this.initializeGhosts();
    }

    /**
     * Create a number of Ghost objects based on the width and height of the screen
     * @return void
     */
    initializeGhosts() {
        for (let i = 0; i < this.numberOfGhosts; i++) {
            //Initialize a new instance of the Ghost class
            let ghost = new Ghost(
                getRandomInt(0, this.width),
                getRandomInt(0, this.height),
                this.context
            );

            //Initialize the Ghost object so it can create it's Eye objects
            ghost.initialize();

            //Add the Ghost object to our array of Ghost objects
            this.ghosts.push(ghost);
        }
    }

    /**
     * Updates the application and every child of the application
     * @return void
     */
    update() {
        for (let i = 0; i < this.ghosts.length; i++) {
            this.ghosts[i].update(this.mousePosition);
        }
    }

    /**
     * Renders the application and every child of the application
     * @return void
     */
    render() {
        //Clear the entire canvas every render
        this.context.clearRect(0, 0, this.width, this.height);

        //Trigger the render function on every child element
        for (let i = 0; i < this.ghosts.length; i++) {
            this.ghosts[i].render(this.context);
        }
    }

    /**
     * Update and render the application at least 60 times a second
     * @return void
     */
    loop() {
        this.update();
        this.render();

        window.requestAnimationFrame(() => this.loop());
    }

    /**
     * Triggered every time the user moves his mouse, updates the mousePosition variable
     * @param event - The browsers mousemove event object 
     */
    mouseMove(event) {
        this.mousePosition = {
            x: event.clientX,
            y: event.clientY
        };
    }
    
    /**
     * Triggered every time the user touches the screen and moves his finger, updates the mousePosition variable
     * @param event - The browsers touchmove event object 
     */
    touchMove(event) {
        event.preventDefault();
        this.mousePosition = {
            x: event.touches[0].clientX,
            y: event.touches[0].clientY
        };
    }
}

/**
 * Ghost Class
 */
class Ghost {
    /**
     * Ghost constructor
     * @param x - The horizontal position of this Ghost
     * @param y - The vertical position of this Ghost
     * @param context - The context from the canvas object of the Application
     */
    constructor(x, y, context) {
        this.position = new Vector2D(x, y);
        this.handPosition = new Vector2D(x, y);
        this.context = context;

        this.radius = 50;
        this.eyeDistance = 10;
        this.eyes = [];
        this.bodyBounceAngle = getRandomInt(0, 100);
        this.bounceDistance = 0.5;
        this.bounceSpeed = 0.05;

        this.velocity = new Vector2D(0, 0);
        this.velocity.setLength(Math.random() * 2 + 1);
        this.velocity.setAngle(Math.random() * TWO_PI);
    }

    /**
     * Create two eyes for this ghost! Ghosts can't see without their eyes
     * @return void
     */
    initialize() {
        this.eyes.push(new Eye(this.position.getX() - this.eyeDistance, this.position.getY() - 10));
        this.eyes.push(new Eye(this.position.getX() + this.eyeDistance, this.position.getY() - 10));
    }

    /**
     * Update the position of this object
     * @param mousePosition - The current position of the users mouse, in x,y format
     * @return void
     */
    update(mousePosition) {
        //Randomize the velocity of this ghost
        if (Math.random() < 0.01) {
            this.velocity.setLength(Math.random() * 2 + 1);
            this.velocity.setAngle(Math.random() * TWO_PI);
        }

        //Update the position of the ghost
        //this.position.addTo(this.velocity);

        //Calculate the amount of velocity for the way the body and the hands should bounce
        let bodyBounce = new Vector2D(0, Math.sin(this.bodyBounceAngle) * this.bounceDistance);
        let handBounce = new Vector2D(0, Math.sin(this.bodyBounceAngle + 10) * this.bounceDistance / 2);
        this.position.addTo(bodyBounce);
        this.handPosition.subtractFrom(handBounce);

        //We want to position both eyes at the same angle, so we calculate the angle once and then pass it to every eye
        let dx = mousePosition.x - this.position.getX();
        let dy = mousePosition.y - this.position.getY();
        let angle = Math.atan2(dy, dx);

        //Trigger the update function on every child element
        for (let i = 0; i < this.eyes.length; i++) {
            this.eyes[i].update(bodyBounce, angle);
        }

        //Update the bounce angle by adding the speed
        this.bodyBounceAngle += this.bounceSpeed;
    }

    /**
     * Renders this Ghost object on the canvas
     * @return void
     */
    render() {
        //Draw the body of the ghost
        this.context.fillStyle = "#ffffff";
        this.context.beginPath();
        this.context.arc(this.position.getX(), this.position.getY(), this.radius, 0, TWO_PI);
        this.context.fill();

        //Draw the left hand of the ghost
        this.context.fillStyle = "#ffffff";
        this.context.beginPath();
        this.context.arc(this.handPosition.getX() - this.radius + 5, this.handPosition.getY() + 10, 10, 0, TWO_PI);
        this.context.fill();

        //Draw the right hand of the ghost
        this.context.fillStyle = "#ffffff";
        this.context.beginPath();
        this.context.arc(this.handPosition.getX() + this.radius - 5, this.handPosition.getY() + 10, 10, 0, TWO_PI);
        this.context.fill();

        //Trigger the render function on every child element
        for (let i = 0; i < this.eyes.length; i++) {
            this.eyes[i].render(this.context);
        }
    }
}

/**
 * Eye Class
 */
class Eye {
    /**
     * Eye constructor
     * @param x - The horizontal position of this Eye
     * @param y - The vertical position of this Eye
     */
    constructor(x, y) {
        this.position = new Vector2D(x, y);
        this.irisPosition = new Vector2D(x, y);
        this.moveRadius = 20;
        this.sizeRadius = 5;
    }

    /**
     * Update the position of this object
     * @param velocity - The velocity of which this eye is currently moving
     * @param angle - The new angle directed at the users mouse position
     * @return void
     */
    update(velocity, angle) {
        this.position.addTo(velocity);

        this.irisPosition.setX(this.position.getX() + Math.cos(angle) * this.moveRadius);
        this.irisPosition.setY(this.position.getY() + Math.sin(angle) * this.moveRadius);
    }

    /**
     * Renders this Eye object on the canvas
     * @param context - The context from the canvas object of the Application
     * @return void
     */
    render(context) {
        //Draw the iris of the eye
        context.fillStyle = "#000000";
        context.beginPath();
        context.arc(this.irisPosition.getX(), this.irisPosition.getY(), this.sizeRadius, 0, TWO_PI);
        context.fill();
    }
}

/**
 * Vector2D class
 */
class Vector2D {
    /**
     * Vector constructor
     */
    constructor(x, y) {
        this._x = x;
        this._y = y;
    }

    /**
     * @param x
     * @return void
     */
    setX(x) {
        this._x = x;
    }

    /**
     * @param y
     * @return void
     */
    setY(y) {
        this._y = y;
    }

    /**
     * @return {number}
     */
    getX() {
        return this._x;
    }

    /**
     * @return {number}
     */
    getY() {
        return this._y;
    }

    /**
     * @param angle
     * @return void
     */
    setAngle(angle) {
        let length = this.getLength();
        this._x = Math.cos(angle) * length;
        this._y = Math.sin(angle) * length;
    }

    /**
     * @return {number}
     */
    getAngle() {
        return Math.atan2(this._y, this._x);
    }

    /**
     * @param length
     * @return void
     */
    setLength(length) {
        let angle = this.getAngle();
        this._x = Math.cos(angle) * length;
        this._y = Math.sin(angle) * length;
    }

    /**
     * @return {number}
     */
    getLength() {
        return Math.sqrt(this._x * this._x + this._y * this._y);
    }

    /**
     * @param {Vector} v2
     * @return {Vector2D}
     */
    add(v2) {
        return new Vector2D(this._x + v2.getX(), this._y + v2.getY());
    }

    /**
     * @param {Vector} v2
     * @return {Vector2D}
     */
    subtract(v2) {
        return new Vector2D(this._x - v2.getX(), this._y - v2.getY());
    }

    /**
     * @param value
     * @return {Vector2D}
     */
    multiply(value) {
        return new Vector2D(this._x * value, this._y * value);
    }

    /**
     * @param value
     * @return {Vector2D}
     */
    divide(value) {
        return new Vector2D(this._x / value, this._y / value);
    }

    /**
     * @param v2
     * @return void
     */
    addTo(v2) {
        this._x += v2.getX();
        this._y += v2.getY();
    }

    /**
     * @param v2
     * @return void
     */
    subtractFrom(v2) {
        this._x -= v2.getX();
        this._y -= v2.getY();
    }

    /**
     * @param value
     * @return void
     */
    multiplyBy(value) {
        this._x *= value;
        this._y *= value;
    }

    /**
     * @param value
     * @return void
     */
    divideBy(value) {
        this._x /= value;
        this._y /= value;
    }
}

/**
 * Onload function is executed whenever the page is done loading, initializes the application
 */
window.onload = function () {
    //Create a new instance of the application
    const application = new Application();

    //Initialize the Ghosts objects
    application.initializeGhosts();

    //Start the initial loop function for the first time
    application.loop();
};
              
            
!
999px

Console