procedural sky morning screenshot

Creating dynamic, environment-aware UI elements adds a layer of polish to web applications. By combining React, realtime weather APIs, and SVG filters, we can build a procedural sky that automatically reflects the user's local time of day and cloud cover.

Here is a breakdown of how to build a realtime procedural sky without relying on heavy WebGL libraries or prompting the user for intrusive location permissions.

  1. Privacy-Friendly Location & Weather Fetching

Browsers have a native Geolocation API, but triggering a location permission popup immediately upon page load is a terrible user experience. Instead, we can approximate the user's location using their IP address. It's less precise than GPS, but perfectly adequate for fetching general weather data like cloud cover.

Using ipapi.co, we fetch the rough latitude and longitude, then pass those coordinates to the free Open-Meteo API to retrieve the current cloud_cover percentage. To prevent rate-limiting and unnecessary network requests, this data is cached in localStorage for one hour.

// Fetching rough location via IP (No permission popup required) const ipRes = await fetch("https://ipapi.co/json/"); const ipData = await ipRes.json(); const latitude = ipData.latitude || 19.076; const longitude = ipData.longitude || 72.8777; // Fetching cloud cover based on coordinates const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=cloud_cover`; const weatherRes = await fetch(weatherUrl); const weatherData = await weatherRes.json(); const cloudDensity = weatherData.current.cloud_cover; 
  1. Painting the Sky Gradient

The background sky is driven by the user's local time. By extracting the current hour (new Date().getHours()), we can map it to an array of predefined CSS gradients.

Night hours return pure black or deep navy, early mornings transition through sunrise colors, and midday hours use bright blues.

const skyGradientByHour = [ // ... other hours { hour: 6, gradient: `linear-gradient(180deg, #000000 0%, #00274C 50%, #83270080 100%)` }, // Sunrise { hour: 12, gradient: `linear-gradient(180deg, #004381 0%, #FFFFFF 100%)` }, // Noon { hour: 18, gradient: `linear-gradient(180deg, #004381 0%, #B78A78 100%)` }, // Sunset // ... ]; const getSkyGradient = (timeOfDay) => { const match = skyGradientByHour.find(sky => sky.hour === timeOfDay); return match?.gradient || "#000000"; }; 
  1. Sculpting Clouds with CSS and feTurbulence

The most complex part is rendering the clouds. Instead of using static PNGs or expensive canvas rendering, we can use CSS box-shadow coupled with SVG filters to generate organic, fluffy shapes.

First, we generate a massive, comma-separated box-shadow string. Each shadow acts as a single cloud "blob," scattered randomly across the viewport using vw and vh units. The number of blobs is dictated by the cloudDensity fetched from the weather API (capped to maintain performance).

To make these perfect circles look like actual clouds, we apply an SVG filter. generates fractal noise, and uses that noise to warp and distort the smooth edges of our box shadows into organic, chaotic cloud formations.

import { useMemo } from "react"; const CloudSheet = ({ cloudDensity, timeOfDay }) => { const isSunsetOrSunrise = (timeOfDay >= 6 && timeOfDay <= 9) || (timeOfDay >= 18 && timeOfDay <= 20); const memoizeShadows = useMemo(() => { const safeDensity = Math.min(cloudDensity, 30); // Cap density for performance // Generate scattered box-shadows based on density let shadows = []; for (let i = 0; i < safeDensity; ++i) { // Random position (vw/vh), size (vmin), and color tint based on time of day shadows.push(`${rn(1, 100)}vw ${rn(1, 100)}vh ${rn(20, 40)}vmin ${rn(1, 20)}vmin ${isSunsetOrSunrise ? rs("#0385FF00", "#53ACFF", "#CCE6FF") : rs("#0385FF00", "#53ACFF", "#CCE6FF", "#FFFFFF")}`); } return shadows.join(","); }, [cloudDensity, isSunsetOrSunrise]); return ( <div className="bg-transparent absolute w-screen h-screen z-30"> {/* SVG Filter for Cloud Distortion */} <svg width="0" height="0" className="absolute"> <filter id="cloud-filter" x="-50%" y="-50%" width="200%" height="200%"> <feTurbulence type="fractalNoise" baseFrequency=".01" numOctaves="10" result="noise" /> <feDisplacementMap in="SourceGraphic" in2="noise" scale="180" /> </filter> </svg> {/* The base element that holds the shadows and applies the filter */} <div className="fixed -top-1 -left-1 w-1 h-1 bg-white rounded-full filter-[url(#cloud-filter)]" style={{ boxShadow: memoizeShadows, transform: "translateZ(0)", // Force hardware acceleration }} /> </div> ); }; 

By combining these three layers—IP-based weather data, time-mapped gradients, and SVG-distorted box shadows—you get a responsive, performant procedural sky that accurately reflects the physical world outside your user's window.