CSSで月の満ち欠け

新月から新月へ。

月の満ち欠けは球体に光を当てた結果のもので、円を円でカットするようなものではない。CSSでは立体を立体としては扱うのは難しいので、それを平面として捉え、半円を円で膨らませたり押し出したりすることで近いものにすることが可能になりそうだ。

Demo: Lunar Phase

CSSグラデーションで作った半円に、border-radiusで作った半楕円を組み合わせることで実現している。月齢15以上の場合は半円を逆転させる。地球と太陽と月の位置関係から平面で捉えると、半円を基本にするということであっている(と思う)。

例えば月齢5の場合は以下の様なCSSで構成されている。

.moon {
  border-radius: 51%;
  height: 3rem;
  overflow: hidden;
  position: relative;
  width: 3rem;
}

.phase5 {
  background-image: linear-gradient(to right, #ff0 0, #ff0 50%, #000 50%, #000 100%);
}

.moonが基本の月で、それに前述のとおりCSSグラデーションで半円になるように色を塗っておく。左から明るくなっていく。

.moon::before,
.moon::after {
  content: '';
  display: block;
  height: 100%;
  position: absolute;
}

.moon::before {
  border-top-left-radius: 3rem 200%;
  border-bottom-left-radius: 3rem 200%;
  right: 50%;
}

.moon::after {
  border-top-right-radius: 3rem 200%;
  border-bottom-right-radius: 3rem 200%;
  left: 50%;
}

.moonの擬似要素が影と光になる。影も光も半楕円にする必要があるので、border-*-radiusプロパティーでちゃんと半円かつ幅を変えた場合もきれいな半楕円にスケールするように、全体単位と%を組み合わせて調節してやる。

.phase5::before {
  background-color: #000;
  width: 33.333%;
}

月齢5だと擬似要素は影として扱われるので、#000で塗る。そして月齢15で満月なので、その1/3ほど影が後退することになることから幅を33.333%に減らしてやっている。

こういう風に、形を実現するだけでなく、現実世界の状態をある程度反映させた形でCSSによってエミュレートするのは頭の体操になる。


Sassをつかうとミックスイン化できるが二度と使わないので書くのはやめた。そして記事を書いてから気づいたけれど、北半球だと月は右から明るくなるんだった……。頭の体操になっていない。