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.

Editor Settings

Code Indentation

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

Visit your global Editor Settings.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

            
              <div id="header">
            <h1 id="header-title">jSphere</h1>
            <h2 id="header-subtitle">Just to see what I could do.</h2>
        </div>
        <div id="controls">
            <h3 id="controls-title">Controls</h3>
            <div id="controls-body">
                <p id="c-1">Rotate:  Click and drag</p>
                <p id="c-2">Pan:     Hold shift, click and drag</p>
                <p id="c-3">Zoom:    Hold ctrl, click and drag</p>
            </div>
        </div>
        <div id="content">
            <canvas id="canvas" width="800" height="700"></canvas>
        </div>
            
          
!

CSS

            
              @font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 100;
  src: local('Lato Hairline'), local('Lato-Hairline'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/boeCNmOCCh-EWFLSfVffDg.woff) format('woff');
  /* */
}
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 400;
  src: local('Lato Regular'), local('Lato-Regular'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/9k-RPmcnxYEPm8CNFsH2gg.woff) format('woff');
  /* */
}
@font-face {
  font-family: 'Lato';
  font-style: italic;
  font-weight: 300;
  src: local('Lato Light Italic'), local('Lato-LightItalic'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/2HG_tEPiQ4Z6795cGfdivD8E0i7KZn-EPnyo3HZu7kw.woff) format('woff');
}

body {
	background-color: #333;
	font-family: Lato;
	color: #DDD;	
}

h1 {
        font-size: 64px;
}

h1,h2,h3,h4 {
	font-weight: 100;
	position: relative;
	margin: 30px auto;
	text-align: center;
}

p {
	line-height: 30px;
}

#header {
	position: relative;
	width: 100%;
}

#header-title, #header-subtitle {
	width: 500px;
}
#header-subtitle {
	font-style: italic;
}


#content {
	padding: 0px;
	width: 810px;
	height: 900px;
	margin: 0px auto;
	position: relative;
	top: -50px;
}

#canvas {
	border: 2px solid black;
	z-index: 2;
}

#controls {
	width: 810;
	height: 200px;
	margin-left: auto;
	margin-right: auto;
	margin-top: 60px;
	margin-bottom: -50px;
	z-index:1;
	position: relative;
}

#controls-title {
	width: 75px;
	margin-bottom: 15px;
}

#controls-body {
	width: 100%;
	position: relative;
	margin: 0px;
}

#c-1 { /* Rotate */
	position: absolute;
	left: 0px;
	top: 0px;
	margin: 0px;
}

#c-2 { /* Pan */
	position: relative;
	width: 200px; 
	text-align: center;
	margin: 0px auto;
	right: 10px;
}

#c-3 { /* Zoom */
	position: absolute;
	right: 10px;
	top: 0px;
	margin: 0px;
}

#psst {
	font-style: italic;
}
            
          
!

JS

            
              /**
 * Class Dot
 * =========
 * 
 * Members:
 * --------
 * 
 * x, y, z:     The cartesian coordinates of the dot in the space of the sphere.
 * fg:          A flag indicating whether the dot is in the foreground.
 * fgcolor:     The color to use if fg == true.
 * bgcolor:     The color to use if fg == false.
 * 
 * 
 * About:
 * ------
 * 
 * The Dot class represents one point on the surface of sphere. 
 * Each Dot is displayed as a smaller sphere attached to its point.
 * 
 * When drawn to the canvas, each dot is considered to be either in
 * the foreground or the background. This is determined by the Sphere
 * object, but basically, if its Z value is less than that of the
 * sphere's origin (center) point, it is in the background. Otherwise,
 * it is in the foreground.
 *
 * If in the foreground, the dot has a 10 pixel radius and is drawn with
 * a dark color. If in the background, the dot has a 5 pixel radius and
 * is drawn with a very light color, with those furthest back being almost
 * invisible. This gives the illusion of depth.
 * 
 */
function Dot(x,y,z,fg,fgColor,bgColor) {
    // Default Values:  { x: 0, y: 0, z: 0, fg: true, fgcolor: #7EE37E, bgcolor: "#787878" }
    this.x = typeof x !== 'undefined' ? x : 0;
    this.y = typeof y !== 'undefined' ? y : 0;
    this.z = typeof z !== 'undefined' ? z : 0;
    this.fg = typeof fg !== 'undefined' ? fg : true;
    this.fgColor = typeof fgColor !== 'undefined' ? fgColor : "#7EE37E";
    this.bgColor = typeof bgColor !== 'undefined' ? bgColor : "#787878";
    
    this.Draw = function(ctx) {
        // Store the context's fillStyle
        var tmpStyle = ctx.fillStyle;
        // Set the fillStyle for the dot
        ctx.fillStyle = (this.fg ? this.fgColor : this.bgColor);
        // Draw the dot
        ctx.fillCircle(x,y,this.fg?10:5);
        // Restore the previous fillStyle
        ctx.fillStyle = tmpStyle;
    }
}


/**
 * Class Sphere
 * =========
 * 
 * Members:
 * --------
 * 
 * x, y, z:     Cartesian coordinates of the origin (center) point of the sphere.
 * r:           Radius of the sphere.
 * ctx:         Canvas context with which to draw.
 * drawSpheres: Flag indicating whether surface points (Dots) should be drawn as spheres or points.
 * circleSize:  Radius of the dots to be drawn.
 * points:      Array of surface points to keep track of.
 * 
 * 
 * About:
 * ------
 * 
 * The Spehre class represents the sphere object itself. (Go figure.)
 * It is basically comprised of a set of points on its surface and various
 * numbers to keep track of how it should be drawn next.
 * 
 * When drawn to the canvas, the dot list is dynamically sorted by Z-value 
 * and drawn back-to-front, so that the foremost dots are consistently drawn 
 * on top of those behind them. The further back a dot is (i.e. the lower its
 * z-value) the lighter it is colored. This way, those in back blend into the 
 * background, becoming almost invisible.
 * 
 */
function Sphere(x,y,z,r,ctx,drawSpheres) {
    // Default Values:  { x: 200, y: 200, z: 0, r: 90, ctx: null, drawSpheres: false }
    this.x = (typeof x !== 'undefined') ? x : 200;
    this.y = (typeof y !== 'undefined') ? y : 200;
    this.z = (typeof z !== 'undefined') ? z : 0;
    this.r = (typeof r !== 'undefined') ? r : 90;
    this.ctx = (typeof ctx !== 'undefined') ? ctx : null;
    this.drawSpheres = (typeof drawSpheres !== 'undefined') ? drawSpheres : false;
    
    // 10 pixel dots
    this.circleSize = 10;
    this.points = new Array();
    
    // The angle delta to use when calculating the surface point positions;
    // a larger angstep means fewer points on the surface
    var angstep = Math.PI/10;
    var i = 0;
    // Loop from 0 to 2*pi, creating one row of points at each step
    for (var angxy=0; angxy<2*Math.PI; angxy+=angstep){
        this.points[i] = new Array();
        var j=0;
        for (var angyz=0; angyz<2*Math.PI; angyz+=angstep) {
            // Loop from 0 to 2*pi, creating one point at each step
            var px  = r * Math.cos(angxy) * Math.sin(angyz) + x,
                py  = r * Math.sin(angxy) * Math.sin(angyz) + y,
                pz  = r * Math.cos(angyz) + z,
                pfg = pz > z;
            this.points[i][j] = new Dot(px,py,pz,pfg);
            j++;
        }
        i++;
    }

    // Draw to the canvas
    this.Draw = function() {
        // Store the context's fillStyle
        var tmpStyle = this.ctx.fillStyle;


        // Clear the canvas
        canvas.width -= 1;
        canvas.width += 1;
        
        // Set the canvas background color
        this.ctx.fillStyle = '#FFF';
        // Fill the canvas with the selected background color
        this.ctx.fillRect (0, 0, canvas.width, canvas.height);
        
        // Sort the points by z-value
        
        // Empty array
        var z_sorted = new Array();
        // Add each surface point first
        for (var i=0; i<this.points.length; i++) {
            for (var j=0; j<this.points[i].length; j++) {
                z_sorted[this.points.length*i+j] = this.points[i][j];
            }
        }
        // Add the origin point of the sphere
        z_sorted[z_sorted.length] = new Dot(this.x, this.y, this.z);
        z_sorted.sort(function(a,b){return (b.z-a.z)});
        
        
        for (var p=0; p<z_sorted.length; p++) {
            var color;
            // If drawing the origin point, draw it blue
            if (z_sorted[p].x == this.x && z_sorted[p].y == this.y && z_sorted[p].z == this.z) {
                color = "#27F";
            } 
            // Else, draw the point a shade of gray relative to the z-value,
            // with darker pixels in the front and lighter pixels farther back
            else {
                var n = Math.round(((z_sorted[p].z + this.r)/(this.r*2)) * 250);
            
                color = "rgb(" + n + "," + n + "," + n + ")";
            }
            if (this.drawSpheres) {
                this.ctx.fillCircleGradient(z_sorted[p].x, z_sorted[p].y, this.circleSize, "#FFFFFF", color);
            } else {
                this.ctx.fillCircle(z_sorted[p].x, z_sorted[p].y, this.circleSize, color);
            }
        }
        
        // Restore the context's fillStyle
        this.ctx.fillStyle = tmpStyle;
    }
    
    // Zoom in or out (ctrl drag)
    this.Zoom = function(x,y) {
        var length = Math.round(Math.sqrt(x*x + y*y)),
            scale = (r + (x>0?length:-length))/r;
                    
                
        this.x *= scale;
        this.y *= scale;
        this.z *= scale;

        this.r *= scale;
        this.circleSize *= scale;
        
        // Scale each point
        for (var i in this.points) {
            for (var j in this.points[i]) {
                this.points[i][j].x *= scale;
                this.points[i][j].y *= scale;
                this.points[i][j].z *= scale;
            }
        }
        // Redraw in new positions
        this.Draw();
    }
    
    // Split pan!  (Hidden function, alt+shift drag)
    this.HiddenFun1 = function(x,y) {
        this.r += Math.round(Math.sqrt(x*x + y*y));
        // Scale each point
        for (var i in this.points) {
            for (var j in this.points[i]) {
                this.points[i][j].x += (this.points[i][j].x > this.x ? x : -x);
                this.points[i][j].y += (this.points[i][j].y > this.y ? y : -y);
            }
        }
        // Redraw in new positions
        this.Draw();
    }
    
    // Cigar Zoom!  (Hidden function, alt+ctrl drag)
    this.HiddenFun2 = function(x,y) {
        var length = Math.round(Math.sqrt(x*x + y*y)),
            scale = (r + (x>0?length:-length))/r;
        
        this.r += length;
        
        this.x *= scale;
        this.y *= scale;
        //this.z *= scale;

        
        // Scale each point
        for (var i in this.points) {
            for (var j in this.points[i]) {
                this.points[i][j].x *= scale;
                this.points[i][j].y *= scale;
            }
        }
        // Redraw in new positions
        this.Draw();
    }
        
    
    // Pan  (shift drag)
    this.Pan = function(x,y) {
        // Move each point
        for (var i in this.points) {
            for (var j in this.points[i]) {
                this.points[i][j].x += x;
                this.points[i][j].y += y;
            }
        }
        this.x += x;
        this.y += y;
        // Redraw in new positions
        this.Draw();
    }
    
    // Rotate the sphere (drag with no modifier keys)
    this.Rotate = function(x,y) {
        // Vertical rotation
        this.rotateXZ(((Math.PI/2)/this.r)*x);
        // Horizontal rotation
        this.rotateYZ(((Math.PI/2)/this.r)*y);
        
        // Redraw in new positions
        this.Draw();
    }
    
    // Rotate around the z-axis
    this.rotateXY = function(ang) {
        //console.log("XY test: ",ang);
        for (var i in this.points) {
            for (var j in this.points[i]) {
                var px = this.points[i][j].x - this.x,
                    py = this.points[i][j].y - this.y;
                
                var newx = px*Math.cos(ang)-py*Math.sin(ang),
                    newy = px*Math.sin(ang)+py*Math.cos(ang);
                    
                this.points[i][j].x = newx+this.x;
                this.points[i][j].y = newy+this.y;
            }
        }
    };
    
    // Rotate around the y-axis
    this.rotateXZ = function(ang) {
        //console.log("XZ test: ",ang);
        for (var i in this.points) {
            for (var j in this.points[i]) {
                var px = this.points[i][j].x - this.x,
                    pz = this.points[i][j].z - this.z;
                
                var newx = px*Math.cos(ang)-pz*Math.sin(ang),
                    newz = px*Math.sin(ang)+pz*Math.cos(ang);
                    
                this.points[i][j].x = newx+this.x;
                this.points[i][j].z = newz+this.z;
            }
        }
    };
    
    // Rotate around the x-axis
    this.rotateYZ = function(ang) {
        //console.log("YZ test: ",ang);
        
        for (var i in this.points) {
            for (var j in this.points[i]) {
                //console.log("old y: ", this.points[i][j].y,"\nold z: ", this.points[i][j].z);
                var py = this.points[i][j].y - this.y,
                    pz = this.points[i][j].z - this.z;
                
                var newy = py*Math.cos(ang)-pz*Math.sin(ang),
                    newz = py*Math.sin(ang)+pz*Math.cos(ang);
                    
                this.points[i][j].y = newy+this.y;
                this.points[i][j].z = newz+this.z;
                //console.log("new y: ", this.points[i][j].y,"\nnew z: ", this.points[i][j].z);
            }
        }
    };
}


function ctxFillCircle(x,y,r,color) {
    var oldFS = this.fillStyle;
    this.fillStyle = color;
    this.beginPath();
    this.arc(x, y, r, 0, 2 * Math.PI, false);
    this.closePath();
    this.fill();
    this.fillStyle = oldFS;
}
function ctxStrokeCircle(x,y,r) {
    this.beginPath();
    this.arc(x, y, r, 0, 2 * Math.PI, false);
    this.closePath();
    this.stroke();
}
function ctxFillCircleGradient(x,y,r,color1,color2) {
    var oldFS = this.fillStyle;
    this.beginPath();
    this.arc(x, y, r, 0, 2 * Math.PI, false);
    this.closePath();
    // create radial gradient
    var grd = this.createRadialGradient(x-r/5, y-r/5, 0, x-r/5, y-r/5, r*0.8);
    // light blue
    grd.addColorStop(0, color1);
    // dark blue
    grd.addColorStop(1, color2);
    this.fillStyle = grd;
    this.fill();
    this.fillStyle = oldFS;
}

var sphere;

$(document).ready(function(){
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    Object.getPrototypeOf(ctx).fillCircle = ctxFillCircle;
    Object.getPrototypeOf(ctx).strokeCircle = ctxStrokeCircle;
    Object.getPrototypeOf(ctx).fillCircleGradient = ctxFillCircleGradient;
    
    sphere = new Sphere(parseInt($("#canvas").css("width"))/2,parseInt($("#canvas").css("height"))/2,0,275,ctx,true);
    sphere.Draw();
    
    
    var startX, startY;
    $("#canvas")
    .drag("init", function(e){
        startX = e.clientX;
        startY = e.clientY;
    })
    .drag(function(e){
        if (e.ctrlKey) {
            if (e.altKey) sphere.HiddenFun2(e.clientX-startX,e.clientY-startY);
            else sphere.Zoom(e.clientX-startX, e.clientY-startY, -1);
        } else if (e.shiftKey) {
            if (e.altKey) sphere.HiddenFun1(e.clientX-startX,e.clientY-startY);
            else sphere.Pan(e.clientX-startX,e.clientY-startY);
        } else {        
            sphere.Rotate(e.clientX-startX,e.clientY-startY);
        }
        startX = e.clientX;
        startY = e.clientY;
    });
});

            
          
!
999px

Console