JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>Ball of string</title>
</head>
<style>
body {
background-color: #39474F;
}
</style>
<body>
<canvas id="content"></canvas>
</body>
JavaScript
var context = document.getElementById('content').getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
var size = d3.min([width, height]);
d3.select('#content')
.attr('width', width + 'px')
.attr('height', height + 'px');
context.lineWidth = 1;
context.strokeStyle = '#FFFFFF';
var projection = d3.geoOrthographic()
.scale(0.45 * size)
.translate([0.5 * width, 0.5 * height]);
var geoGenerator = d3.geoPath()
.projection(projection)
.context(context)
.pointRadius(.8);
var geojson = {
type: 'Feature',
geometry: {
type: 'MultiLineString',
coordinates: []
},
};
const lngPoints = 48;
const lngMinus = lngPoints / 2;
const lngDistance = 360 / lngPoints;
const latPoints = 36;
const latMinus = latPoints / 2;
const latDistance = 180 / latPoints;
const lngs = [...Array(lngPoints + 1).keys()].map(x => (x - lngMinus) * lngDistance);
const lats = [...Array(latPoints + 1).keys()].map(x => (x - latMinus) * latDistance);
lngs.forEach(lng => {
let lngShift = lng;
let lngV = 0;
let lngA = (Math.random()-0.5) / 10;
if (lng > 120) {
lngShift += Math.random() * 1 + 0.5;
lngV = (Math.random()) / 10;
}
const lngs = [];
lats.forEach(lat => {
if (lat >= 0) {
lngV += lngA;
lngShift += lngV;
} else {
lngV += (lngShift - lng) / (-lat) + lngA;
lngShift += lngV;
}
lngs.push([lngShift, lat]);
})
geojson.geometry.coordinates.push(lngs);
})
function update(t) {
projection.rotate([t / 15, -40]);
context.setLineDash([2, 15]);
context.clearRect(0, 0, width, height);
context.beginPath();
geoGenerator(geojson);
context.stroke();
window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);