I've been working on a large-ish Javascript project lately (still in stealth mode). One that could be described as a Javascript application. And since I've been up to my elbows in Javascript, I have found my contempt for the language waning. Not entirely, of course – I'm still a Python fan-boy. But enough for me to knock out a 3D(ish) spinning texture-mapped globe, using nothing but Javascript and HTML, as a way of honing my JS skills.

Without further ado, I present you with a 3D Javascript Globe.

An explanation

This effect is off course a hack in the sense that it produces a novel result, but it still only makes use of standard browser capabilities.

One of the few geometry formulas that I have committed to memory is the relationship of the points on a circle and the radius: x^2 + y^2 = r^2

The ‘sphere’ is composed of a sequence of divs, one for each row of pixels. The width and position of the rows are calculated by the Javascript with a little math. Inside of each of the divs is an img tag that is position so that it shows a particular row of the source image (see below). The img tags also have their size set according to there position in the sphere so that the image is scaled more at the top and bottom of the sphere to simulate foreshortening. This gives some illusion of perspective – on one axis at least.

Texture map of the earth for JS Globe

The unwrapped globe image

Shading the sphere

Sphere light map for JS Globe

The translucent PNG used to simulate lighting.

The shading is simply a transparent PNG that is absolute positioned on top of the circle. I rendered this image in Inkscape – it's just a circle with radial shading applied, but it looks convincingly sphere like.

Animating the sphere

This sphere is animated by moving the img tags in each div. The divs have the overflow: hidden; style set, so that only the required number of pixels are shown. The rate at which each row moves is based on the width of the row in the sphere. Rows at the top and bottom of the sphere scroll more slowly than the middle because they have been scaled down.

Alas, its not very fast (at least on my machine). Scaling img tags is processor intensive and wasn't really meant to be used dynamically like this. It could possibly be optimized by pre-processing the map so that the browser needn't do any scaling of its own, but frankly I have satisfied my desire to find out if it is possible at all!

The code

Here's the code. As you will see, its not designed to be re-usable and frankly it uses up too much processor power to be a practical effect on a web-page, but you are welcome do do whatever you want with it! It uses the excellent JQuery library.

<script type="text/javascript">

var anim=0;
function animate_globe(globe)
    $globe = $(globe);

    var x=( anim*5 ) % 600 - 600;
    $globe.find('.globe-span').each(function(i, span){
        $(span).css({left:x * scales[i]});


function make_globe(globe_select, globe_image_select, size)
    var span_html_template = '<div style="position:relative;overflow:hidden;width:[WIDTH]px;height:1px;margin-left:[MARGIN]px;"></div>"';
    var span_img_template = '<img class="globe-span" src="[SRC]" width="[WIDTH]" height="[HEIGHT]" style="border:0;padding:0;margin:0;position:relative;width:[WIDTH]px;height:[HEIGHT]px;top:[TOP]px;">'

    function render_template(template, td)
        return template.replace(/\[(.*?)\]/g, function(m, n) {
            return td[n];

    var $globe = $(globe_select);
    var $globe_image = $(globe_image_select);
    var globe_image_src = $globe_image.attr('src');
    var w = $globe_image.width();
    var h = $globe_image.height();

    var r = size/2;
    var max_c = Math.PI * size;
    scales = []
    for(var y = -r; y < r; y++)
        var i = ((y+r)/size);
        var width = Math.sqrt(r*r - y*y)*2.0 + .5;

        var c = Math.PI * width;

        var scale = width / size;


        var img_width = Math.round(scale * w*2);
        var img_height = Math.round(scale * h);
        var img_y = Math.floor(i*img_height);

        var span_html = render_template(span_html_template, {'WIDTH': Math.ceil(width),
                                                             'MARGIN' : Math.round((size-width)/2)})

        var $span = $(span_html);

        var img_html = render_template(span_img_template, { 'SRC': globe_image_src,
                                                        'WIDTH' : img_width,
                                                        'WIDTH2' : img_width * 2,
                                                        'HEIGHT' : img_height,
                                                        'TOP' : -img_y})

        var $span_img = $(img_html);



    setInterval("animate_globe('#jsglobe')", 50);


$(function(){make_globe('#jsglobe', '#jsglobe-image', 300);});


    <img id="jsglobe-image" style="display:none;" src="/media/uploads/images/earthmap.jpg" />
    <div style="margin-left:80px;width:100%;padding:20px;float:left;width:300px;height:300px;">
    <div id="jsglobe" style="float:left;position:absolute;">
    <div style="float:left;width:300px;height:300px;position:absolute;background-image:url(/media/uploads/images/spherelight.png);">
<div style="clear:both;">&nbsp;</div>
This blog post was posted to It's All Geek to Me on Saturday May 30th, 2009 at 12:36PM

2 Responses to "A 3D Texture-Mapped Globe Rendered with Javascript and HTML"

  • Dave
    May 30th, 2009, 8:39 p.m.

    Have you played with O3D yet?

  • May 30th, 2009, 8:52 p.m.

    Dave, haven't look at it. Have you done anything with it?

Leave a Comment

You can use bbcode in the comment: e.g. [b]This is bold[/b], [url]http://www.willmcgugan.com[/url], [code python]import this[/code]
Preview Posting...
Previewing comment, please wait a moment...

My Tweets

Will McGugan

My name is Will McGugan. I am an unabashed geek, an author, a hacker and a Python expert – amongst other things!

Search for Posts
Possibly related posts
Popular Tags
Recent Comments
def thousands_with_commas(number): new_number = [] number = str(number) mod_value = len(number) % 3 counter = 3 if len(number) 4: return ...
don't know why this was tempting.. (#1)import re from collections import Counter, OrderedDict cnt=Counter() with open(./t) as f: #--- strip ...
- Mike on Python Coder Test
Hello! I've seen this test and tried to do them. Result added bellow. First path: def thousands_with_commas(i): i = str(i) ...
Why another framework? what wrong with django, pyramid, flask?will be have answer for this question in the docs)
Hi! Really great code, good work! But trying to use it on a responsive site, it didn't resize images. So, ...
© 2008 Will McGugan.

A technoblog blog, design by Will McGugan