How to create a interactive globe in the background of a website

Posted By

How to create a interactive globe in the background of a website

Have you ever wondered about how to create a interactive globe in the background of website with javascript?

How to move it automatically?

How to move it on mouse move?

Recently I encountered a need to create a interactive globe with county flags on it, also the globe needed to move/rotate on mouse move.

Now, my first idea was to find a library to create a globe and play around with its functions and events.

But when I digged into it, then I found there is nothing such available which fulfilling all the needs which I wanted to have in my website.

So I spent lot of time on this thing, and finally came up with a solution.

And I decided to write a post about this so that anyone who is looking for something similar can easily get an idea from this.

Requirements

  • 3D globe
  • Globe which rotates
  • Globe which has images on it (in the example I have country flags)
  • Globe which rotates according to mouse movement
  • Globe to be used as background in a website

Keeping all the requirements in mind, I went on googling free libraries. But there was nothing which was meeting all the requirements.

So, I enhanced the Planetary.js according to my needs.

You can download planetary.js here

Here is the HTML for the solution:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Interactive Globe</title>
    <link rel="pingback" href="https://shaktisinghcheema.com/xmlrpc.php">
    <link rel="icon" href="https://shaktisinghcheema.com/wp-content/uploads/2019/07/cropped-Shakti-Logo1-1-32x32.jpg" sizes="32x32">
    <link rel="icon" href="https://shaktisinghcheema.com/wp-content/uploads/2019/07/cropped-Shakti-Logo1-1-192x192.jpg" sizes="192x192">
    <link rel="apple-touch-icon-precomposed" href="https://shaktisinghcheema.com/wp-content/uploads/2019/07/cropped-Shakti-Logo1-1-180x180.jpg">
    <meta name="msapplication-TileImage" content="https://shaktisinghcheema.com/wp-content/uploads/2019/07/cropped-Shakti-Logo1-1-270x270.jpg">
    <link rel="stylesheet" type="text/css" href="css/mystyle.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <div class="top-section-container">
        <div class="cheema-background-globe-container">
            <canvas id="cheemaglobe" width="700" height="700"></canavs>
        </div>
        <div class="top-section-items">
            <div class="container">
                <div>
                    <h1>Example of rotating globe in background on mouse move</h1>
                    <p>By Shakti Singh Cheema</p>
                </div>
            </div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script type='text/javascript' src='https://d3js.org/d3.v3.min.js'></script>
    <script type='text/javascript' src='https://d3js.org/topojson.v1.min.js'></script>
    <script type='text/javascript' src="scripts/planetaryjs.min.js"></script>
    <script type="text/javascript" src="scripts/planetaryjs-objects.js"></script>
    <script src="scripts/myscript.js"></script>
</body>
</html>

Here is the planetaryjs-object.js code:

planetaryjs.plugins.objects = function (config) {
    var objects = [];
    config = config || {};

    var addObject = function (lng, lat, options) {
        options = options || {};

        options.speed = options.speed || config.speed || 0;
        options.imagesrc = options.imagesrc || config.imagesrc || "";
        options.imagewidth = options.imagewidth || config.imagewidth || 50;
        options.imageheight = options.imageheight || config.imageheight || 50;
        options.fade = options.fade || config.fade || false;

        var ping = { time: new Date(), options: options };
        if (config.latitudeFirst) {
            ping.lat = lng;
            ping.lng = lat;
        } else {
            ping.lng = lng;
            ping.lat = lat;
        }
        objects.push(ping);
    };

    var drawobjects = function (planet, context, now) {
        var newobjects = [];
        for (var i = 0; i < objects.length; i++) {
            var object = objects[i];
            var timechange = now - object.time;
            newobjects.push(object);
            drawobject(planet, context, now, timechange, object);
        }
        objects = newobjects;
    };

    var drawobject = function (planet, context, now, timechange, object) {
        //If Speed is greater than 0 animate object on lng/x axis
        var newlng = 0
        if (object.options.speed > 0) {
            var xmove = (timechange * (object.options.speed)) / 100;
            newlng = (object.lng + xmove)
        } else {
            newlng = object.lng
        }

        //Get Spherical Coords from lat lng
        var coords = planet.projection([newlng, object.lat])
        var img = new Image()
        img.src = object.options.imagesrc;
        var geoangle = d3.geo.distance([newlng, object.lat], [-planet.projection.rotate()[0], -planet.projection.rotate()[1]]);

        //closeness used for fading
        var closeness = 1.57079632679490 - geoangle;

        if (geoangle > 1.57079632679490) {
            //Behind Sphere > 90 degrees
        } else {

            var imagewidth = object.options.imagewidth;
            var imageheight = object.options.imageheight;

            if (object.options.fade == true) {
                if (closeness < 0.1) {
                    context.globalAlpha = closeness * 10;
                }
            }
            context.drawImage(img, (coords[0] - (imagewidth / 2)), (coords[1] - (imageheight / 2)), imagewidth, imageheight)
            context.globalAlpha = 1
            //If fade is true fade out and in
        }
    };

    return function (planet) {
        planet.plugins.objects = {
            add: addObject,
            objectList: objects
        };

        planet.onDraw(function () {
            planet.plugins.objects = {
                add: addObject,
                objectList: objects
            };

            var now = new Date();
            planet.withSavedContext(function (context) {
                drawobjects(planet, context, now);
            });
        });
    };
};

You can also download planetaryjs-objects.js from here

Here is my complete myscript.js :

// JavaScript source code


(function () {
    var flagsLoaded = 0;
    var globe = planetaryjs.planet();
    // Load our custom `autorotate` plugin; see below.
    // The `earth` plugin draws the oceans and the land; it's actually
    // a combination of several separate built-in plugins.
    globe.loadPlugin(autorotate(10));// Comment this line to stop autorotate
    // Note that we're loading a special TopoJSON file
    // (world-110m-withlakes.json) so we can render lakes.
    globe.loadPlugin(planetaryjs.plugins.earth({
        topojson: { file: 'scripts/world-110m-withlakes.json' },
        oceans: { fill: '#000080' },
        land: { fill: '#339966' },
        borders: { stroke: '#008000' }
    }));
    // Load our custom `lakes` plugin to draw lakes; see below.
    globe.loadPlugin(lakes({
        fill: '#000080'
    }));
    // The `pings` plugin draws animated pings on the globe.
    globe.loadPlugin(planetaryjs.plugins.pings());
    globe.loadPlugin(planetaryjs.plugins.objects());
    // The `zoom` and `drag` plugins enable
    // manipulating the globe with the mouse.
    globe.loadPlugin(planetaryjs.plugins.zoom({
        scaleExtent: [100, 300]
    }));
    globe.loadPlugin(planetaryjs.plugins.drag({
        // Dragging the globe should pause the
        // automatic rotation until we release the mouse.
        onDragStart: function () {
            //this.plugins.autorotate.pause();
        },
        onDragEnd: function () {
            //this.plugins.autorotate.resume();
        }
    }));
    // Set up the globe's initial scale, offset, and rotation.
    globe.projection.scale(175).translate([175, 175]).rotate([0, -10, 0]);

    // Every few hundred milliseconds, we'll draw another random ping.
    var colors = ['red', 'yellow', 'white', 'orange', 'green', 'cyan', 'pink'];
    setInterval(function () {
        var lat = Math.random() * 170 - 85;
        var lng = Math.random() * 360 - 180;
        var color = colors[Math.floor(Math.random() * colors.length)];
        globe.plugins.pings.add(lng, lat, { color: color, ttl: 2000, angle: Math.random() * 10 });
        if (flagsLoaded == 0) {
        // add canada flag
        globe.plugins.objects.add(106.3468, 56.1304, {
            imagesrc: "images/canada.jpg",
            imageheight: 25,
            imagewidth: 50
        });
        // add canada flag
        globe.plugins.objects.add(104.1954, 35.8617, {
            imagesrc: "images/china.jpg",
            imageheight: 33,
            imagewidth: 50
        });
        // add india flag
        globe.plugins.objects.add(78.9629, 20.5937, {
            imagesrc: "images/india.jpg",
            imageheight: 33,
            imagewidth: 50
        });
        // add south africa flag
        globe.plugins.objects.add(22.9375, 30.5595, {
            imagesrc: "images/africa.jpg",
            imageheight: 33,
            imagewidth: 50
            });
            flagsLoaded = 1;
        }
    }, 150);   

    var canvas = document.getElementById('cheemaglobe');
    // Special code to handle high-density displays (e.g. retina, some phones)
    // In the future, Planetary.js will handle this by itself (or via a plugin).
    if (window.devicePixelRatio == 2) {
        canvas.width = 800;
        canvas.height = 800;
        context = canvas.getContext('2d');
        context.scale(2, 2);
    }

    // Size of globe and translate to center
    globe.projection.scale(300).translate([350, 350]);
    // Draw that globe!
    globe.draw(canvas);

    // This plugin will automatically rotate the globe around its vertical
    // axis a configured number of degrees every second.
    function autorotate(degPerSec) {
        // Planetary.js plugins are functions that take a `planet` instance
        // as an argument...
        return function (planet) {
            var lastTick = null;
            var paused = false;
            planet.plugins.autorotate = {
                pause: function () { paused = true; },
                resume: function () { paused = false; }
            };
            // ...and configure hooks into certain pieces of its lifecycle.
            planet.onDraw(function () {
                if (paused || !lastTick) {
                    lastTick = new Date();
                } else {
                    var now = new Date();
                    var delta = now - lastTick;
                    // This plugin uses the built-in projection (provided by D3)
                    // to rotate the globe each time we draw it.
                    var rotation = planet.projection.rotate();
                    rotation[0] += degPerSec * delta / 1000;
                    if (rotation[0] >= 180) rotation[0] -= 360;
                    planet.projection.rotate(rotation);
                    lastTick = now;
                }
            });
        };
    };

    // This plugin takes lake data from the special
    // TopoJSON we're loading and draws them on the map.
    function lakes(options) {
        options = options || {};
        var lakes = null;

        return function (planet) {
            planet.onInit(function () {
                // We can access the data loaded from the TopoJSON plugin
                // on its namespace on `planet.plugins`. We're loading a custom
                // TopoJSON file with an object called "ne_110m_lakes".
                var world = planet.plugins.topojson.world;
                lakes = topojson.feature(world, world.objects.ne_110m_lakes);
            });

            planet.onDraw(function () {
                planet.withSavedContext(function (context) {
                    context.beginPath();
                    planet.path.context(context)(lakes);
                    context.fillStyle = options.fill || 'black';
                    context.fill();
                });
            });
        };
    };
    // Rotate Globe On Mouse Move
    $(document).mousemove(function (event) {       
        globe.projection.rotate([event.pageX, event.pageY, 0]);       

    });
})();

Download the full solution here Download

To overcome any issue due to cors policies, run the folder in your localhost or any other server.

In this example I have binded the globe movement on document mouse move, you can customize it according to you need.

Also, you can customize all of the colors and elements, zoom level, size etc, so feel free to create something exciting.

Cheers!

Shakti Singh Cheema