发挥你的 i-moon-gination 思维:让我们用 CSS 和 JS 构建一个月相可视化工具!🗓️🌙
封面照片由 Flickr 用户Brendan Keene拍摄
啊,北半球的春天来了!夜晚又变暖了(也更短了!),万里无云,正是赏月的好时节,不是吗?其实,我一直对我们最大的天然卫星——月亮,以及整个夜空都怀有深深的迷恋。
今天让我们更深入地研究月相,并构建我们自己的月相计算器和可视化器!
月相是如何形成的?
我绝对不是轨道力学专家,更别说研究轨道力学所需的大部分数学知识了,但我还是会尽力解释一下。即便我知道轨道力学是什么,我还是感到困惑。
你可能知道,月球围绕地球旋转,地球围绕太阳旋转。[需要引用]
地球大约每12个月绕太阳公转一次,误差在几分钟之内,这就是闰年的意义所在。月球绕地球公转一周大约需要27.3天。在过去的某个时刻,地球引力减缓了月球的自转速度,使月球绕地球的轨道与自身自转速度一致。月球被潮汐锁定。这意味着它总是朝向地球的同一侧。
但这并不意味着月球是静止的。它确实围绕地球旋转,而地球仍然围绕太阳旋转。在极少数情况下,地球、太阳和月球会排成一条直线:这时就会发生日食(月球位于地球和太阳之间)或月食(地球位于太阳和月亮之间)。
如果这种情况没有发生(实际上,大多数情况下如此),月球就会被太阳照亮。随着月球与地球夹角的变化,月球的不同侧面会被照亮。这就形成了不同的月相。
维基百科用户 Andonee 以一种令人惊奇的方式说明了这一点:
你可以很清楚地看到它的工作原理:月亮总是以某种方式被照亮,但照亮的那一侧的角度会发生变化,从而形成我们看到的月相。每个周期大约需要 29.5 天。所以,29.5 天 = 360 度旋转。29.5 不等于 27.3,这正是数学变得复杂的地方。明白了。让我们开始写代码吧。
锅炉镀层!
如果能有一个日期选择器来查看不同的日期就太棒了。应该显示当前选定的日期。而且,我们需要一个月亮。月亮有明暗两半,还有一个分隔线。我们先从 HTML 开始:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<h1 id="date-title">
<!-- Will show the selected date -->
</h1>
<!-- The moon -->
<div class="sphere">
<div class="light hemisphere"></div>
<div class="dark hemisphere"></div>
<div class="divider"></div>
</div>
<!-- The date input -->
<input type="date">
<script src="app.js"></script>
</html>
我还添加了一个空的 JS 文件和一个空的 CSS 文件。
让我们开始造型吧。
让它变得漂亮
我们从背景开始。我们使用 flexbox 来让所有内容居中。标题应该使用漂亮明亮的颜色,以便在深蓝色背景上清晰可见。
html {
background-color: rgba(11,14,58,1);
overflow: hidden;
}
body {
text-align: center;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
color: #F4F6F0;
margin-bottom: 50px;
}
接下来,我们让月亮(注意,前面是糟糕的双关语!)转动:
.sphere {
border-radius: 100%;
width: 300px;
height: 300px;
overflow: hidden;
display: flex;
align-items: center;
position: relative;
margin-bottom: 50px;
}
你可能注意到我们这里也用到了 flexbox。我们需要两个半球彼此相邻,这样分隔线才能正常工作。
.hemisphere {
width: 50%;
height: 100%;
}
.light {
background-color: #F4F6F0;
}
.dark {
background-color: #575851;
}
最后,我们需要一个分隔线。为了模拟真实的球体,我们将分隔线设计成圆形,并在 3D 空间中旋转它。由于月亮可以 360 度旋转,所以分隔线也应该能够 360 度旋转。因此,分隔线需要两个面:一个亮面和一个暗面。我们将使用分隔线的:after
伪元素来实现这一点,并将其沿 Y 轴旋转 180 度,作为分隔线的背面:
.divider,
.divider:after {
top: 0;
left: 0;
width: 300px;
height: 300px;
position: absolute;
border-radius: 100%;
transform-style: preserve-3d;
backface-visibility: hidden;
}
.divider {
background-color: #575851; /* Dark */
}
.divider:after {
content: '';
background-color: #F4F6F0; /* Light */
transform: rotateY(180deg);
}
让它显示月相
要知道月亮目前处于多远的月相,我们需要知道新月(也就是完全黑暗的月)之前的某个时间点。一次这样的事件发生在2022年3月2日下午6:34(UTC+1)。
一个月相大约需要 29.5 天,我们需要将分隔线旋转 0-360 度。因此,为了获得给定日期的旋转,我们可以取所选日期与 3 月 2 日之间的差值,计算天数,减去 29.5 的任意倍数,将余数除以 29.5,再乘以 360。然后,我们需要从 360 度中减去这个差值,以适应分隔线并符合 CSS 旋转的原理:
const getMoonPhaseRotation = date => {
const cycleLength = 29.5 // days
const knownNewMoon = new Date('2022-03-02 18:34:00')
const secondsSinceKnownNewMoon = (date - knownNewMoon) / 1000
const daysSinceKnownNewMoon = secondsSinceKnownNewMoon / 60 / 60 / 24
const currentMoonPhasePercentage = (daysSinceKnownNewMoon % cycleLength) / cycleLength
return 360 - Math.floor(currentMoonPhasePercentage * 360)
}
现在,由于圆盘的旋转不会自动与正确的半球重叠(它们仍然是明暗的),我们需要将明暗半球互换位置,这样看起来被照亮的部分实际上就像在移动一样。为此,我们根据计算出的旋转值切换了一些类。然后,我们还应用了分隔线旋转的样式,瞧,一个可以工作的月相可视化工具就完成了。
const setMoonRotation = deg => {
document.querySelector('.divider').style.transform = `rotate3d(0, 1, 0, ${deg}deg)`
const hemispheres = document.querySelectorAll('.hemisphere')
if (deg < 180) {
// Left
hemispheres[0].classList.remove('dark')
hemispheres[0].classList.add('light')
// Right
hemispheres[1].classList.add('dark')
hemispheres[1].classList.remove('light')
} else {
// Left
hemispheres[0].classList.add('dark')
hemispheres[0].classList.remove('light')
// Right
hemispheres[1].classList.remove('dark')
hemispheres[1].classList.add('light')
}
}
最后,我们添加一个更新标题的函数:
const setMoonTitle = date => {
document.querySelector('#date-title').innerHTML = `Moon phase for ${date.toUTCString()}`
}
把事情联系在一起
现在,让我们让这些函数相互配合:
const today = new Date()
const dateSelect = document.querySelector('input')
dateSelect.addEventListener('input', e => {
const selectedDate = new Date(e.target.value)
setMoonTitle(selectedDate)
setMoonRotation(getMoonPhaseRotation(selectedDate))
})
dateSelect.value = today.toISOString().slice(0, 10)
setMoonTitle(today)
setMoonRotation(getMoonPhaseRotation(today))
惊人的!
附加内容:一闪一闪小星星
在我们的月亮周围放一些星星也不错,不是吗?是吗?酷。让我们使用大量的线性渐变。每个线性渐变都会有一个亮点,它会逐渐淡入 HTML 的背景色,然后变得透明。这样,它们就不会互相“重叠”,我们也不需要大量的额外元素。
让我们看看我用一个函数来生成单个星星的渐变是什么意思:
const getStar = () => {
const x = Math.round(Math.random() * 100)
const y = Math.round(Math.random() * 100)
return `
radial-gradient(circle at ${x}% ${y}%,
rgba(255,255,255,1) 0%,
rgba(11,14,58,1) 3px,
rgba(11,14,58,0) 5px,
rgba(11,14,58,0) 100%) no-repeat border-box
`
}
如你所见,星星本身从 到#ffffff
渐变rgba(11,14,58,1)
了 3 个像素,之后 2 个像素变为透明。渐变的其余部分都是透明的。当你组合多个渐变时,最后添加的渐变将“获胜”,并与所有其他渐变重叠。如果渐变的一部分是透明的,背景就可以“透出来”(哈哈)。通过使线性渐变的大部分透明,我们可以将许多渐变洒落在我们想要的任何位置。
现在我们需要实际生成很多星星并将它们添加到主体中:
document.body.style.background = [...Array(100)].map(() => getStar()).join(', ')
完成了!
演示时间!
(单击“结果”查看实际效果)
迫不及待地想看看计算是否正确!希望今晚天气晴朗!
希望你喜欢阅读这篇文章,就像我喜欢写它一样!如果喜欢,请留下❤️或🦄 !我空闲时间会写科技文章,偶尔也喜欢喝咖啡。
如果你想支持我的努力, 可以请我喝杯咖啡☕ ,或者 在推特上关注我🐦 ! 你也可以直接通过PayPal支持我!
文章来源:https://dev.to/thormeier/use-your-i-moon-gination-lets-build-a-moon-phase-visualizer-with-css-and-js-aih