07. IRIX · 1994
Wireframe globe
About
A UV sphere as meridians and parallels, projected with a 1/z divide. Front edges are brighter than the far side, so the globe reads in depth without hidden-line removal. The equator and prime meridian are a touch hotter. Drag to orbit. It keeps a slow auto-spin until you take over.
Drag on the preview to spin the globe. Auto-rotation stops after the first drag.
Browser APIs
- Canvas 2D
- Pointer Events
- requestAnimationFrame
- matchMedia
If WebGL is missing, the demo draws a message on the canvas instead of a blank frame. prefers-reduced-motion freezes the first still.
Source
(function () {
const PARAMS = {
color: "#6ee7b7",
highlight: "#d2ffec",
speed: 1
};
function hexToRgb(hex) {
const n = parseInt(String(hex).replace("#", ""), 16);
if (isNaN(n)) return [110, 231, 183];
return [n >> 16 & 255, n >> 8 & 255, n & 255];
}
function rgbCss(hex, a) {
const r = hexToRgb(hex);
return "rgba(" + r[0] + ", " + r[1] + ", " + r[2] + ", " + a + ")";
}
const canvas = document.getElementById("c");
const fallback = document.getElementById("fallback");
const ctx = canvas.getContext("2d");
if (!ctx) {
fallback.hidden = false;
fallback.textContent = "Canvas 2D is not available in this browser.";
return;
}
const LAT = 13;
const LON = 24;
const verts = [];
function sph(lat, lon) {
const phi = (lat / LAT) * Math.PI;
const th = (lon / LON) * Math.PI * 2;
return {
x: Math.sin(phi) * Math.cos(th),
y: Math.cos(phi),
z: Math.sin(phi) * Math.sin(th),
};
}
for (let i = 0; i <= LAT; i++) {
for (let j = 0; j <= LON; j++) verts.push(sph(i, j));
}
function idx(i, j) { return i * (LON + 1) + (j % (LON + 1)); }
const edges = [];
for (let i = 0; i <= LAT; i++) {
for (let j = 0; j < LON; j++) {
const kind = i === (LAT / 2 | 0) ? 2 : 0;
edges.push(idx(i, j), idx(i, j + 1), kind);
}
}
for (let j = 0; j < LON; j++) {
for (let i = 0; i < LAT; i++) {
const kind = j === 0 ? 2 : 0;
edges.push(idx(i, j), idx(i + 1, j), kind);
}
}
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let yaw = 0.85;
let pitch = 0.35;
let auto = true;
let dragging = false;
let lastX = 0, lastY = 0;
let last = 0;
let raf = 0;
canvas.addEventListener("pointerdown", function (e) {
dragging = true;
auto = false;
lastX = e.clientX;
lastY = e.clientY;
canvas.setPointerCapture(e.pointerId);
});
canvas.addEventListener("pointerup", function () { dragging = false; });
canvas.addEventListener("pointermove", function (e) {
if (!dragging) return;
yaw += (e.clientX - lastX) * 0.008;
pitch += (e.clientY - lastY) * 0.008;
pitch = Math.max(-1.1, Math.min(1.1, pitch));
lastX = e.clientX;
lastY = e.clientY;
});
function rotate(p) {
const cy = Math.cos(yaw), sy = Math.sin(yaw);
const x1 = p.x * cy + p.z * sy;
const z1 = -p.x * sy + p.z * cy;
const cp = Math.cos(pitch), sp = Math.sin(pitch);
const y2 = p.y * cp - z1 * sp;
const z2 = p.y * sp + z1 * cp;
return { x: x1, y: y2, z: z2 };
}
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const w = Math.max(1, Math.floor(canvas.clientWidth * dpr));
const h = Math.max(1, Math.floor(canvas.clientHeight * dpr));
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
}
}
function frame(now) {
const dt = Math.min(0.1, last ? (now - last) * 0.001 : 0.016);
last = now;
if (auto && !reduced) yaw += dt * 0.28 * PARAMS.speed;
resize();
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = "#0b0c0e";
ctx.fillRect(0, 0, w, h);
const cx = w * 0.5;
const cy = h * 0.5;
const cam = 2.85;
const fov = Math.min(w, h) * 0.92;
const proj = verts.map(rotate);
ctx.lineCap = "round";
ctx.lineJoin = "round";
for (let e = 0; e < edges.length; e += 3) {
const a = proj[edges[e]];
const b = proj[edges[e + 1]];
const kind = edges[e + 2];
const za = a.z + cam;
const zb = b.z + cam;
if (za < 0.15 || zb < 0.15) continue;
const depth = 0.5 * (a.z + b.z) * 0.5 + 0.5;
const alpha = 0.22 + depth * (kind ? 0.78 : 0.58);
const x0 = cx + a.x * fov / za;
const y0 = cy - a.y * fov / za;
const x1 = cx + b.x * fov / zb;
const y1 = cy - b.y * fov / zb;
ctx.strokeStyle = rgbCss(PARAMS.color, alpha * 0.28);
ctx.lineWidth = kind ? 3.4 : 2.4;
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
ctx.strokeStyle = rgbCss(kind ? PARAMS.highlight : PARAMS.color, alpha);
ctx.lineWidth = kind ? 1.5 : 1.05;
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
}
if (!reduced) raf = requestAnimationFrame(frame);
}
if (reduced) frame(performance.now());
else raf = requestAnimationFrame(frame);
})();