Change location using the south/north buttons to see latitude's effect on sun's path in the sky.
Rewind, pause or fast-forward using the buttons below.
A Pen by Mustafa Enes on CodePen.
<canvas id="zdog-canvas" width="800" height="640"></canvas> | |
<div class="place-controls"> | |
<button id="south">« South</button> | |
<p id="place">Current Place</p> | |
<button id="north">North »</button> | |
</div> | |
<div class="time-controls"> | |
<p id="month">Current Month</p> | |
<div class="buttons"> | |
<button id="rewind">«</button> | |
<button id="pause">||</button> | |
<button id="play" class="hidden">></button> | |
<button id="forward">»</button> | |
</div> | |
</div> |
const { cos, PI, ceil, random } = Math | |
const deg = degrees => { | |
return PI / 180 * degrees | |
} | |
const maybeNegative = number => { | |
return random() >= .5 ? -number : number | |
} | |
const times = (howMany, what) => { | |
return [...Array(howMany).keys()].map(what) | |
} | |
const query = selector => | |
selector[0] === '#' | |
? document.getElementById(selector.slice(1)) | |
: document.querySelectorAll(selector) | |
const getPlace = (placeList, index) => { | |
const { name, country, lat } = placeList[index] | |
const nameAndCountry = [ name, country ].filter(Boolean) | |
const hemisphere = lat > 0 ? 'N' : lat < 0 ? 'S' : '' | |
return { | |
name: `${nameAndCountry.join(', ')} (${lat}°${hemisphere})`, | |
lat | |
} | |
} | |
const calculateMonth = (monthList, time) => { | |
const monthLength = 1 / 6 | |
const monthIndex = time / monthLength | |
return monthList[ceil(monthIndex) % 12] | |
} | |
const places = [ | |
{ name: 'South Pole', country: null, lat: -90 }, | |
{ name: 'Vostok', country: 'Antarctica', lat: -75 }, | |
{ name: 'Falkland Islands', country: 'UK', lat: -51 }, | |
{ name: 'Christchurch', country: 'New Zealand', lat: -43 }, | |
{ name: 'São Paulo', country: 'Brazil', lat: -23 }, | |
{ name: 'Kampala', country: 'Uganda', lat: 0 }, | |
{ name: 'Dubai', country: 'UAE', lat: 23 }, | |
{ name: 'Istanbul', country: 'Turkey', lat: 41 }, | |
{ name: 'Helsinki', country: 'Finland', lat: 60 }, | |
{ name: 'Tromsø', country: 'Norway', lat: 69 }, | |
{ name: 'Alert', country: 'Canada', lat: 82 }, | |
{ name: 'North Pole', country: null, lat: 90 }, | |
] | |
const months = [ | |
'June', | |
'July', | |
'August', | |
'September', | |
'October', | |
'November', | |
'December', | |
'January', | |
'February', | |
'March', | |
'April', | |
'May' | |
] | |
const daySpeeds = { | |
paused: 0, | |
playing: 0.01, | |
rewinding: -0.01, | |
forwarding: 0.1 | |
} | |
const maxAscension = .23 | |
const state = { | |
place: 8, | |
mode: 'playing', | |
season: 0 // 0 = june, 1 = december | |
} | |
const drawing = new Zdog.Illustration({ | |
element: '#zdog-canvas', | |
dragRotate: true, | |
zoom: 500, | |
rotate: { x: -deg(25) } | |
}) | |
const ground = new Zdog.Cylinder({ | |
addTo: drawing, | |
diameter: 1, | |
length: .05, | |
stroke: false, | |
color: 'forestgreen', | |
frontFace: 'yellowgreen', | |
backface: 'rgb(30, 100, 10)', | |
rotate: { x: deg(90) } | |
}) | |
const clouds = [] | |
const addRandomCloud = (parent, x) => { | |
const color = 'rgba(230, 240, 255, .2)' | |
const z = maybeNegative(random() / 2) | |
clouds.push(new Zdog.Box({ | |
addTo: parent, | |
width: .001, | |
height: .0025, | |
depth: .001, | |
stroke: .1, | |
color, | |
translate: { | |
x, | |
y: -.3, | |
z | |
} | |
})) | |
clouds.push(new Zdog.Box({ | |
addTo: parent, | |
width: .001, | |
height: .003, | |
depth: .001, | |
stroke: .15, | |
color, | |
translate: { | |
x: x + .1, | |
y: -.3, | |
z | |
} | |
})) | |
if (random() > .75) { | |
clouds.push(new Zdog.Box({ | |
addTo: parent, | |
width: .001, | |
height: .0025, | |
depth: .001, | |
stroke: .1, | |
color, | |
translate: { | |
x: x + .2, | |
y: -.3, | |
z | |
} | |
})) | |
if (random() > .5) { | |
clouds.push(new Zdog.Box({ | |
addTo: parent, | |
width: .0005, | |
height: .0005, | |
depth: .0005, | |
stroke: .07, | |
color, | |
translate: { | |
x: x + .27, | |
y: -.3, | |
z | |
} | |
})) | |
} | |
} | |
} | |
const sunTiltPlane = new Zdog.Anchor({ | |
addTo: drawing, | |
}) | |
const sunOrbitPlane = new Zdog.Anchor({ | |
addTo: sunTiltPlane | |
}) | |
const sunAscensionPlane = new Zdog.Anchor({ | |
addTo: sunOrbitPlane, | |
}) | |
const sun = new Zdog.Shape({ | |
addTo: sunAscensionPlane, | |
stroke: .1, | |
color: '#fd5', | |
translate: { y: -.5 } | |
}) | |
let cloudX = 0 | |
let cloudBoost = 2.5 | |
const loop = () => { | |
const speed = daySpeeds[state.mode] | |
const place = getPlace(places, state.place) | |
const tilt = deg(-place.lat) | |
if (sunTiltPlane.rotate.z !== tilt) { | |
sunTiltPlane.rotate.z += ((tilt - sunTiltPlane.rotate.z) / 5) | |
} | |
sunOrbitPlane.rotate.x += speed | |
sunAscensionPlane.translate.x = ( | |
cos(PI * state.season) * maxAscension | |
) | |
state.season += (speed / 180) | |
if (random() < speed * cloudBoost) { | |
cloudX += ((.6 - cloudX) / 10) | |
cloudBoost -= ((cloudBoost - .5) / 10) | |
addRandomCloud(drawing, cloudX) | |
} | |
clouds.forEach(cloud => { | |
if (cloud) { | |
cloud.translate.x -= (speed / 10) | |
if (cloud.translate.x < -3) { | |
cloud.remove() | |
cloud = null | |
} | |
} | |
}) | |
query('#place').innerText = place.name | |
const month = calculateMonth(months, state.season) | |
query('#month').innerText = month | |
drawing.updateRenderGraph() | |
requestAnimationFrame(loop) | |
} | |
loop() | |
const onClickSouth = () => { | |
state.place-- | |
query('#north').removeAttribute('disabled') | |
if (!places[state.place - 1]) { | |
query('#south').setAttribute('disabled', true) | |
} | |
} | |
const onClickNorth = () => { | |
state.place++ | |
query('#south').removeAttribute('disabled') | |
if (!places[state.place + 1]) { | |
query('#north').setAttribute('disabled', true) | |
} | |
} | |
const playPause = () => { | |
if (state.mode !== 'paused') { | |
state.savedMode = state.mode | |
state.mode = 'paused' | |
} else { | |
state.mode = state.savedMode | |
} | |
query('#play').classList.toggle('hidden') | |
query('#pause').classList.toggle('hidden') | |
} | |
const toggleRewind = () => { | |
state.mode = state.mode !== 'rewinding' | |
? 'rewinding' | |
: 'playing' | |
} | |
const toggleForward = () => { | |
state.mode = state.mode !== 'forwarding' | |
? 'forwarding' | |
: 'playing' | |
} | |
query('#south').addEventListener('click', onClickSouth) | |
query('#north').addEventListener('click', onClickNorth) | |
query('#play').addEventListener('click', playPause) | |
query('#pause').addEventListener('click', playPause) | |
query('#rewind').addEventListener('click', toggleRewind) | |
query('#forward').addEventListener('click', toggleForward) |
<script src="https://unpkg.com/zdog@1/dist/zdog.dist.min.js"></script> |
html, body { | |
height: 100vh; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
body { | |
font: normal 100%/1 "Operator Mono", monospace; | |
} | |
button { | |
appearance: none; | |
border: none; | |
background: #000; | |
color: #fff; | |
padding: .3em; | |
min-width: 32px; | |
font: inherit; | |
&:disabled { | |
background: gray; | |
} | |
} | |
.hidden { | |
display: none; | |
} | |
#zdog-canvas { | |
cursor: move; | |
} | |
@media (max-width: 800px) { | |
#zdog-canvas { | |
width: 100%; | |
} | |
} | |
.place-controls { | |
position: absolute; | |
top: 30px; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
width: 100%; | |
max-width: 800px; | |
button { | |
padding: .5em; | |
} | |
#place { | |
font-size: 1.2em; | |
} | |
} | |
@media (max-height: 640px) { | |
.place-controls { | |
top: 0; | |
} | |
} | |
.time-controls { | |
position: absolute; | |
bottom: 30px; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
flex-direction: column; | |
.buttons { | |
display: flex; | |
} | |
button:not(:last-child) { | |
margin-right: 1em; | |
} | |
#month { | |
font-size: 1.2em; | |
padding-bottom: .25em; | |
text-align: center; | |
} | |
} | |
@media (max-height: 640px) { | |
.time-controls { | |
bottom: 0; | |
} | |
} |
Change location using the south/north buttons to see latitude's effect on sun's path in the sky.
Rewind, pause or fast-forward using the buttons below.
A Pen by Mustafa Enes on CodePen.