canvas实现的太阳系动画思路总结

canvas实现的太阳系动画思路总结

效果

源码

点击查看代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>太阳系动画</title>
    <style>
      canvas {
        margin: 50px auto;
        display: block;
        box-shadow: 0 0 10px rgb(0 0 0 / 50%);
      }
    </style>
  </head>
  <body>
    <canvas width="800" height="500"></canvas>

    <script>
      const sun = new Image()
      const moon = new Image()
      const earth = new Image()
      // 地球轨道半径
      const orbitRadius = 105
      const sunSize = 300
      const earthSize = 24

      init()

      function init() {
        sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png'
        moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png'
        earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png'

        // 充当一次,图片load事件
        window.requestAnimationFrame(draw)
      }
      function draw() {
        const canvas = document.querySelector('canvas')
        const ctx = canvas.getContext('2d')

        // 清空图像(为动画做准备)
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        // 保存绘制的默认状态
        ctx.save()
        // 移动原点(可有可无)
        ctx.translate(100, 100)

        drawSun(ctx)

        // 调整绘制原点,使地球以太阳圆心为旋转中线旋转 => 地球沿着轨道走(重点)
        ctx.translate(sunSize / 2, sunSize / 2)
        drawEarth(ctx)

        // 保存绘制原点在轨道上的状态即旋转状态
        ctx.save()

        drawMoon(ctx)
        drawShadow(ctx)

        // 恢复画笔状态(绘制原点在轨道上)
        ctx.restore()

        // 恢复默认状态,避免影响下次绘制
        ctx.restore()

        window.requestAnimationFrame(draw)
      }

      function drawSun(ctx) {
        ctx.save()
        // 绘制太阳
        ctx.drawImage(sun, 0, 0, sunSize, sunSize)
        // 设置描边颜色
        ctx.strokeStyle = 'rgba(0,153,255,0.4)'
        ctx.beginPath()
        // 绘制地球的轨道(太阳图片大小是300*300 => 太阳中心的位置(150, 150))
        ctx.arc(sunSize / 2, sunSize / 2, orbitRadius, 0, Math.PI * 2)
        ctx.stroke()
        ctx.restore()
      }

      function drawEarth(ctx) {
        const time = new Date()
        /*
          绘制地球 这个6和6000是什么意思?
          按照钟表上的旋转角度:1s转6度

          // 每秒转60度(月亮转,比地球转的快)
          当前度数 = (360 / 6) * 当前秒数
          // 每秒转6度 (地球转)
          当前度数 = (360 / 60) * 当前秒数

          => 效果上月亮绕地球的转速比地球绕太阳的转速快10倍
        */
        ctx.rotate(
          ((2 * Math.PI) / 60) * time.getSeconds() +
            ((2 * Math.PI) / 60000) * time.getMilliseconds()
        )

        // 在原点在太阳中心的基础上,移动绘制原点到地球中心
        ctx.translate(orbitRadius, 0)
        // 怎么沿着轨道走?先以太阳中心为旋转中心旋转,再调整距离
        ctx.drawImage(
          earth,
          -earthSize / 2, // 使地球中心的x坐标在轨道上
          -earthSize / 2, // 使地球中心的y坐标在轨道上
          earthSize,
          earthSize
        )
      }

      function drawMoon(ctx) {
        // 保存画笔状态,接下里有原点位移、旋转效果修改
        ctx.save()
        const time = new Date()
        ctx.rotate(
          ((2 * Math.PI) / 6) * time.getSeconds() +
            ((2 * Math.PI) / 6000) * time.getMilliseconds()
        )

        // 20: 地球轨道与月亮的间距
        ctx.translate(20, 0)
        // 绘制月亮
        ctx.drawImage(moon, 0, 0)

        // 恢复状态,避免影响接下来的操作
        ctx.restore()
      }

      function drawShadow(ctx) {
        ctx.save()
        ctx.fillStyle = 'rgba(0,0,0,0.4)'
        ctx.fillRect(
          0,
          -earthSize / 2, // 让矩形纵向中心与地球的纵向中心持平
          40, // 能遮着一般地球和月亮就行
          earthSize
        )
        ctx.restore()
      }
    </script>
  </body>
</html>

思路

绘制地球、月亮、阴影的时候,将他们看作与太阳中心在一水平线上。

  1. 保存原始画笔状态
  2. 绘制太阳
  3. 绘制地球轨道
    1. 以太阳中心为绘制原点画圆弧
  4. 绘制地球
    1. 以太阳中心为旋转中心,旋转地球。旋转角度对照钟表旋转,以秒来计算
    2. 以与太阳中心纵向持平的轨道位置(A点)为绘制原点绘制地球。
  5. 绘制月亮
    1. 月亮的绘制不影响其他图形的绘制,所有绘制月亮前后要保存和恢复画笔状态
    2. 以A点为中心旋转
    3. 设置月亮与A点的距离(效果上是设置地球与月亮的距离)
  6. 绘制地球遮挡月亮太阳光照射的效果
    1. 阴影的绘制不影响其他图形的绘制,所有绘制阴影前后要保存和恢复画笔状态
    2. 以A点画矩形,高为地球的高度,宽以能遮着月亮为主,要遮着一半的地球(矩形纵向中心要与地球纵向中心持平)
  7. 恢复原始画笔状态,避免影响下次绘制

总结

beginPath

画圆弧的时候,一定要调用beginPath()方法,否则地球轨道的颜色就很亮。至于为什么,现在还不知道。

translate

每次移动是以上次的原点为基准点移动绘制原点。初始绘制原点坐标为(0,0)

rotate

每次都是以上一次的旋转角度为基准,以原点为中心旋转。初始角度为

translate与rotate的前后顺序

假设,初始坐标原点为(0, 0);位移坐标为(100, 100);旋转角度为45度

  • 先translate再rotate
    • 先移动坐标原点到(100, 100)
    • 会以(100,100)为中心旋转45度
  • 先rotate再translate
    • 先以(0, 0)为中心旋转45度
    • 再移动原点到(100, 100)为下次绘画做准备

参考

https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_animations

posted @ 2022-01-03 14:31  酉云良  阅读(458)  评论(0)    收藏  举报