I am participating in the Mid-Autumn Festival Creative Submission contest, please see: Mid-Autumn Festival Creative Submission Contest for details

Achieve a moon

The moon is achieved through the following steps:

  1. Fill the canvas with black for a dark background
  2. Draw a circle and fill it with colors to represent the moon
<canvas id="c1" width="600" height="500"></canvas>

<script>
  let canvas=document.getElementById("c1");
  let ctx=canvas.getContext("2d");
  
  let width=canvas.width,height=canvas.height;
  / / dot
  let origin={
      x:width/2.y:height/2
  }

  // Fill the entire canvas
  ctx.fillStyle="# 000";
  ctx.fillRect(0.0,canvas.width,canvas.height);
  / / the moon
  let moon_radius=80;
  let moon_origin={
      x:origin.x-60.y:origin.y
  }
  
  ctx.beginPath();
  ctx.arc(moon_origin.x,moon_origin.y,moon_radius,0.2*Math.PI);
  ctx.fillStyle="yellow";
  ctx.fill();
  ctx.stroke();
</script>
Copy the code

Add a sphere to block the moon

Due to factors such as revolution, the relative distance between the earth and the moon will cause different occlusion effects on the moon.

As follows, draw a shadow ball slightly larger than the moon to realize the occlusion of the moon!

let earth_radius=100;
ctx.beginPath();
ctx.arc(origin.x, origin.y, earth_radius, 0.2 * Math.PI);
ctx.fillStyle="# 000";
ctx.fill();
ctx.stroke();
Copy the code

Animate the occlusion effect of the moon

A ball drawn on a canvas

class Ball {
   constructor(props) {
       // ball coordinates default 0,0 radius 20
       this.x = 0;
       this.y = 0;
       this.r = 20;
       // Horizontal and vertical scaling multiple
       this.scaleX = 1;
       this.scaleY = 1;
       // Draw the beginning and end radian
       this.startArc=0;
       this.endArc=2 * Math.PI;

       // Stroke and fill colors
       this.strokeStyle = "Rgba (0,0,0,0)"; // Transparent black, no stroke
       this.fillStyle = "red"
       this.alpha = 1;

       Object.assign(this, props); // Initialize the props configuration parameters
       return this;
   }
   render(ctx) {
       let {
           x,
           y,
           r,
           scaleX,
           scaleY,
           fillStyle,
           strokeStyle,
           alpha,
           startArc,
           endArc
       } = this;
       ctx.save();
       // Move the origin of coordinates to draw coordinates
       ctx.translate(x, y);
       ctx.scale(scaleX, scaleY);
       ctx.strokeStyle = strokeStyle,
           ctx.fillStyle = fillStyle;
       ctx.globalAlpha = alpha;
       ctx.beginPath();
       ctx.arc(0.0, r, startArc, endArc);
       ctx.fill();
       ctx.stroke();

       ctx.restore();
       return this; }}Copy the code

Modify the moon occlusion code to the Ball Class implementation

let width=canvas.width,height=canvas.height;
ctx.fillStyle="# 000";
ctx.fillRect(0.0,canvas.width,canvas.height);

/ / the moon
let moon=new Ball({
   r:80,
   x:width/2- 60,
   y:height/2,
   fillStyle:"yellow"
});

moon.render(ctx);

let earth=new Ball({
   r:100,
   x:width/2,
   y:height/2,
   fillStyle:"# 000"
});

earth.render(ctx);
Copy the code

Keep out of animation

function initNight(ctx){
   ctx.save();
   ctx.fillStyle="# 000";
   ctx.fillRect(0.0,width,height);
   ctx.restore();
}
initNight(ctx);

/ / the moon
let moon=new Ball({
   r:80.x:width/2-50.y:height/2.fillStyle:"yellow"
});
let earth=new Ball({
   r:90.x:moon.x+moon.r+90.y:height/2.fillStyle:"# 000"
});

moon.render(ctx);
earth.render(ctx);

// Start position
let startX=earth.x;
let endX=earth.x-earth.r*2-moon.r*2;
let delta=endX-startX;
let speed=0.1,nowStep = 0, step = 100;

function maskMove(){
   nowStep+=speed;
   if(nowStep<step){ 
       let currX=startX+ delta*(nowStep/step);
       earth.x=currX;

       ctx.clearRect(0.0, width, height);
       initNight(ctx);
       moon.render(ctx);
       earth.render(ctx);
   }
   else{
       nowStep=0; }} (function move() {
   window.requestAnimationFrame(move); maskMove(); }) ();Copy the code

Below, is the moon from the new moon, minyue, to the upper moon, the full moon, the lower moon, etc.

The moon changes

Phases of the moon

The order of lunar phase changes is: crescent moon — waxing moon — waxing gibbous moon — full moon — waning gibbous moon — waning moon — crescent moon. The lunar phase changes are periodic and the period is about the month.

The lunar phase alternates every 29.53 days and is called a lunar month.

The new moon is also called the new moon or the schonn.

Here is a diagram of the phases of the moon:

Phases and angles of the moon

Angles of different phases of the moon:

1, new Moon (the first day of the lunar calendar, that is, the Schori) : 0 degrees;

2. Mount Emei (usually around the second night of the first lunar month ——- on the seventh day) : 0 degrees —-90 degrees;

The first quarter moon (around the eighth day of the lunar calendar) : 90 degrees;

4. Gibbous moon (around the ninth day of the lunar calendar —– around the 14th day of the lunar calendar) : 90 degrees —-180 degrees;

5, the full moon (full moon, the night of the 15th or 16th lunar month) : 180 degrees;

6. Gibbous moon (about the 16th lunar month —– about the 23rd lunar month) : 180 degrees —-270 degrees;

The last quarter moon (around the 23rd lunar month) : 270 degrees;

The next Emei Moon (lunar calendar around the 24th —- end of the month) : 270 degrees —–360 degrees; In addition, the last day of the lunar month is called huili moon, that is, not to see.

Modify the effect of real moon phase change

From the point of view of the phases of the moon, the change of the entire moon is not the effect of a sphere moving past. More like a semicircle opening and closing.

Use arcTo to achieve approximate effect

/ / starting point
let phaseMoonStartX=width/2;
let phaseMoonStartY=height/2;
let phaseMoon_r=80;

ctx.save();
// Move the origin of coordinates to draw coordinates
ctx.translate(phaseMoonStartX, phaseMoonStartY);
ctx.beginPath();
ctx.moveTo(0.0);
ctx.arcTo(200.80.0.160, phaseMoon_r);
ctx.lineTo(0.160);

ctx.fillStyle="yellow";

ctx.stroke();
ctx.fill();

ctx.restore();

ctx.save();
// Move the origin of coordinates to draw coordinates
ctx.translate(phaseMoonStartX, phaseMoonStartY);
ctx.beginPath();
ctx.moveTo(0.0);
// Control point 1 x is 100
// ctx.arcTo(100, 80, 0, 160, phaseMoon_r);
// Control point 1 x is 20
ctx.arcTo(20.80.0.160, phaseMoon_r*6);
ctx.lineTo(0.160);

ctx.fillStyle="# 000";
ctx.stroke();
ctx.fill();

ctx.restore();
Copy the code

But as you can see, when you use the arcTo function, the lines that you get are a little bit rough, a little bit stiff, and the point is that the radius of the arcTo is also hard to control.

Two arcs intersect to achieve the moon effect

As follows, if a large circle and a small circle, intersection generated moon effect map, their radius relationship can be well calculated.

However, with the increase of DIST, the radius of the great circle also increases exponentially until it reaches infinity.

With respect to the change and calculation of plus and minus infinity, is a problem…

The second great circle, or arc, is changed to arcTo, which also faces the problem of infinity, or the problem of rigid line change.

So, in addition is to use bezierCurveTo()) to achieve the intersection of the vertical center line of a small circle, as the starting point and end point of the Bezier curve, for drawing, I do not know whether the line will be stiff?

Finally, the semicircle is scaled in the X-axis to achieve a similar effect.

Use zoom to simulate a real lunar phase shift

As follows, a circle (as the silver-white moon) and two phases as semicircle and block the silver moon are used to simulate the effect of lunar phase changes based on the principle of scaling.

  1. First, draw the moon, with two semicircular phases on the left and right to cover the moon.
  2. The right half of the circle zooms in to show the right half of the moon below. (Upper Moon)
  3. Scale the right semicircle to a negative value, turn to the left, and change the color to silver-white as a display of the left half of the moon. Waxing gibbous (gibbous moon)
  4. The right semicircle lies entirely to the left. Scale the left semicircle directly to the right, making it silver white, and the right semicircle (now on the left) to form the moon, which turns black. (full moon)
  5. Zoom in to the left semicircle and the silver moon becomes less and less. Deficient gibbous (gibbous moon)
  6. The left semicircle scales from the right to the left, turning black and gradually covering the left silvery white. (Lower Moon)
  7. The left semicircle is completely to the left, and the moon is not visible. (Dark moon)
  8. Repeat from 1.

The cyclic process of phaseXScale from 1 to -1 and from -1 to 1 is utilized.

/ / the moon
let moon1 = new Ball({
   r: 80.x: width / 2.y: height / 2.fillStyle: "#c0c0c0"./ / strokeStyle: "rgba (192192192.6)"
});
let phase1 = new Ball({
   r: moon1.r,
   x: moon1.x,
   y: moon1.y,
   fillStyle: "# 111".startArc: -Math.PI/2.endArc:Math.PI/2
});
let phase2 = new Ball({
   r: moon1.r,
   x: moon1.x,
   y: moon1.y,
   fillStyle: "# 111".startArc:Math.PI/2.endArc:Math.PI*3/2
});

moon1.render(ctx);
phase1.render(ctx);
phase2.render(ctx);

let phaseXScale = 1; / / - 1 ~ 1
let pSpeed = 0.01;
(function phaseMove() {
   window.requestAnimationFrame(phaseMove);
   ctx.clearRect(0.0, width, height);
   initNight(ctx);

   if(phaseXScale>=1){
       phaseSpeed = - pSpeed;
   }
   if(phaseXScale<=-1){
       phaseSpeed =  pSpeed;
       
   }

   phaseXScale += phaseSpeed;
   
   phase1.fillStyle=phaseXScale<0?"#c0c0c0":"# 111";

   moon1.render(ctx);
   // From right to left
   if(phaseSpeed<0){
       moon1.fillStyle= "#c0c0c0";
       phase1.scaleX = phaseXScale;
       phase2.fillStyle="# 111";
       // Left half is #c0c0c0
       phase1.fillStyle=phaseXScale<0?"#c0c0c0":"# 111";

       phase2.render(ctx);
       phase1.render(ctx);
       
   }
   else{
       moon1.fillStyle= "# 111";
       phase2.scaleX = phaseXScale;
       phase1.fillStyle="#c0c0c0";
       // The left half is 111
       phase2.fillStyle=phaseXScale<0?"#c0c0c0":"# 111";
    
       phase1.render(ctx);
       phase2.render(ctx);
   }
})();
Copy the code

Relatively speaking, the lines are still more supple, not so stiff.