原生js 案例备份

51 阅读2分钟
1100%原创罗盘时钟
2、设计灵感
   2.1 有一天早上在抖音上看了一个类似的时钟效果,国风,感觉超级炫酷, 决定自己也写一个

3、 案例实现的思路

  3.1 利用canvas 绘制  年月日时分秒的 圆环形数据 
  3.1.1360度除以 步长 得到角度 步长(月=12, 日=28/30/31 时=12 分=60 秒=603.1.2 绘制旋转的文字前后 需要 使用canvs的状态保存和回复  ctx.save() 和 ctx.restore();

  3.2 工具函数
  3.2.1 角度转弧度: 弧度 = 角度 * Math.PI /180
  3.2.2 数字转中文: 通过中文和数字映射 进行转换 

  3.3 封装绘制圆环数据的逻辑
  3.3.1 由于绘制月、日、时、分、秒 的逻辑相同, 只有 每次绘制的个数 和圆环的直径不同, 所以变化的部分提取成 函数参数即可 
  3.3.2 调用函数 通过不同的入参 即可完成 所有单个圆盘的绘制  

  3.4 性能优化: 
  3.4.1 离屏渲染 和 图像合成
    由于 每个圆环绘制完成后,只进行旋转操作,数据不会更新, 所以将每个环的图像 分别绘制在不同的 离屏canvas上, 在每次需要旋转时,只需将图像合成即可, 避免大量的重复绘制 
  3.4.2 减少不必要的计算 
     如、分-60秒更新一次数据、时-3600秒更新一次数据 、天-超级长...  所以动画每次轮循时, 只需检测数据有无更新,没有更新的话,就不用执行对应的逻辑,大大降低执行频率
  3.4.3 在合适的时候调用执行代码 
    使用requestAnimationFrame()来执行动画,浏览器会让动画在浏览器空闲时执行,有助于提升性能

<!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>Document</title>
    <style>

      html,
      body {
        margin: 0;
        padding: 0;
        height: 100%;
        background-color: #000;
        /* transform-style: preserve-3d; */
        overflow: hidden;
      }
      canvas {
        /* background-color: #000; */
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        /* border: 1px solid red; */
        z-index: -1;
        display: none;
      }

      #canvas {
        display: block;
        z-index: 1;
        /* border: 1px solid red; */
        /* transform: translate(-50%, -50%) translateZ(380px) rotateX(61deg) */
        /* rotateZ(55deg); */
      }
      .p-box {
        position: absolute;
        left: 0;
        top: 0;
        width: 100vw;
        height: 100vh;
        transform-style: preserve-3d;
        perspective: 500px;
      }
      .p-box .p {
        position: absolute;
        left: 50%;
        top: 50%;
        margin-left: -75px;
        margin-top: -50px;
        width: 150px;
        height: 100px;
        /* animation: up 2s infinite; */
        display: flex;
        justify-content: center;
        align-items: center;
        color: #cc3f57;
        font-size: 50px;
        transform: scale(0.1);
        transform-origin: center center;
        /* animation-play-state: paused; */
        /* text-shadow: 0px 0px 2px #FFAE57; */
      }

      .upup {
        animation: up 2s;
      }
      
      .zan {
        position: absolute;
        left: 10px;
        top: 20px; 
        font-size: 20px;
        animation: resize 0.5s infinite alternate;
      }

      @keyframes resize {
        0% {}
        100% {
            transform: scale(1.5);
        }
      }

      @keyframes up {
        0% {
          transform: translateZ(0px);
          opacity: 0.5;
          scale: 0.1;
        }
        100% {
          transform: translateZ(600px);
          opacity: 0.1;
          scale: 1;
        }
      }

    </style>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <div class="p-box">
      <div class="p"></div>
      <div class="p"></div>
      <div class="p"></div>
      <div class="p"></div>
    </div>
    <div class="zan">👍</div>
    <script>
         
      let date = new Date();
      let y = date.getFullYear();
      let m = date.getMonth() + 1;
      let d = date.getDate();
      let h = date.getHours();
      let min = date.getMinutes();
      let s = date.getSeconds();
      const colors = ["#daa520", "#FFAE57", "#FF7853", "#EA5151", "#CC3F57"];
      //   const colors = [ 'red' ,'blue','green', 'yellow','#fff'];
      const bgColor = "#2E2733";
      let types = {
        month: {
          length: 12,
          color: colors[0],
          unit: "月",
          w: 300,
        },
        date: {
          length: new Date(
            new Date(`${y}-${m + 1}-1`) - 24 * 3600 * 1000
          ).getDate(),
          color: colors[1],
          unit: "日",
          w: 450,
        },
        hour: {
          length: 24,
          color: colors[2],
          unit: "时",
          w: 600,
        },
        minute: {
          length: 60,
          color: colors[3],
          unit: "分",
          w: 750,
        },
        second: {
          length: 60,
          color: colors[4],
          unit: "秒",
          w: 900,
        },
      };
      let data = {
        m: {
          num: 0, // 上一次的值
          st: 30, // 刻度间距
          cRadain: 0, // 当前的角度
          tRadain: 0, // 目标角度
        },
        d: {
          num: 0,
          st: 360 / types.date.length, // 刻度间距
          cRadain: 0, // 当前的角度
          tRadain: 0, // 目标角度
        },
        h: {
          num: 0,
          st: 360 / 24, // 刻度间距
          cRadain: 0, // 当前的角度
          tRadain: 0, // 目标角度
        },
        min: {
          num: 0,
          st: 6,
          cRadain: 0, // 当前的角度
          tRadain: 0, // 目标角度
        },
        s: {
          num: 0,
          st: 6,
          cRadain: 0, // 当前的角度
          tRadain: 0, // 目标角度
        },
      };
      // 角度转弧度     角度/180 = 弧度/派
      const getRadian = function (angle) {
        return (angle * Math.PI) / 180;
      };
      // 数字转中文
      const nth = (number) => {
        let numReg = /\D/;
        if (numReg.test(number) || number.length >= 14) return;
        let zhArray = [
          "零",
          "一",
          "二",
          "三",
          "四",
          "五",
          "六",
          "七",
          "八",
          "九",
          "十",
        ]; // 数字对应中文
        let baseArray = [
          "",
          "十",
          "百",
          "千",
          "万",
          "十",
          "百",
          "千",
          "亿",
          "十",
          "百",
          "千",
          "万",
        ]; //进位填充字符,第一位是 个位,可省略
        let string = String(number)
          .split("")
          .reverse()
          .map((item, index) => {
            // 把数字切割成数组并倒序排列,然后进行遍历转成中文
            // 如果当前位为0,直接输出数字, 否则输出 数字 + 进位填充字符
            item =
              Number(item) == 0
                ? zhArray[Number(item)]
                : zhArray[Number(item)] + baseArray[index];
            return item;
          })
          .reverse()
          .join(""); // 倒叙回来数组,拼接成字符串
        string = string.replace(/^一十/, "十"); // 如果以 一十 开头,可省略一
        string = string.replace(/零+/, "零"); // 如果有多位相邻的零,只写一个即可
        string = string.replace(/(.+)(零)$/, "$1");
        return string;
      };

      // 动态canvas:
      function createCs(w, h) {
        let cs = document.createElement("canvas");
        cs.width = w;
        cs.height = h;
        const ctx = cs.getContext("2d");
        let center = {
          x: cs.width / 2,
          y: cs.height / 2,
        };
        document.body.appendChild(cs);

        return {
          cs,
          ctx,
          center,
          w,
        };
      }

      function createMark(
        { cs, ctx, center, w },
        { length, unit, color = "#fff" }
      ) {
        ctx.beginPath();
        ctx.strokeStyle = color;
        ctx.arc(center.x, center.y, w / 2, 0, Math.PI * 2);
        ctx.stroke();

        console.log("w", w);
        const agStep = 360 / length;
        let radio = center.x > center.y ? center.y - 10 : center.x - 10;
        ctx.beginPath();
        ctx.font = "14px 'Microsoft Yahei'";
        ctx.textAlign = "right";
        ctx.fillStyle = color;
        // ctx.fillStyle = "#333";
        for (let i = 0; i < length; i++) {
          ctx.save();
          let r = getRadian(i * agStep);
          ctx.translate(center.x, center.y);
          ctx.rotate(r);
          ctx.fillText(`${nth(i + 1)}${unit}`, radio, 0);
          ctx.restore();
        }

        return cs;
      }

      function createImg(type) {
        const { w } = types[type];
        let csInfo = createCs(w, w);

        return createMark(csInfo, types[type]);
      }

      data.m.cs = createImg("month");
      data.d.cs = createImg("date");
      data.h.cs = createImg("hour");
      data.min.cs = createImg("minute");
      data.s.cs = createImg("second");

      let cs = document.querySelector("#canvas");
      let ctx = cs.getContext("2d");
      let baseWidth = 900;
      // 设置样式
      cs.width = baseWidth;
      cs.height = baseWidth;
      getCsWidth(cs);
      let csr = cs.width > cs.height ? cs.height : cs.width;
      let center = {
        x: cs.width / 2,
        y: cs.height / 2,
      };

      let ps = document.querySelectorAll(".p");

      function getCsWidth(cs) {
        const ww = document.body.clientWidth;
        const wh = document.body.clientHeight;
        const w = ww > wh ? wh : ww;
        const scale = w / 900;
        // cs.style.transform = `translate(-50%, -55%) translateZ(400px) rotateX(71deg) rotateZ(55deg) rotateY(15deg) scale(${scale})`;
      }

      function showMark(mark) {
        ctx.save();
        const { width, height } = mark.cs;
        ctx.translate(center.x, center.y);
        if (mark.cRadain < mark.tRadain) {
          mark.cRadain += mark.step;
        }
        ctx.rotate(-getRadian(mark.cRadain));
        const sx = -width / 2;
        const sy = -height / 2;
        ctx.drawImage(mark.cs, 0, 0, width, height, sx, sy, width, height);
        ctx.restore();
      }

      function createTitle() {
        ctx.beginPath();
        ctx.fillStyle = "#FFAE57";
        ctx.font = "20px 'Microsoft Yahei' ";
        ctx.textAlign = "center";
        ctx.fillText(`${y}年`, center.x, center.y);

        ctx.strokeStyle = "#FFAE57";
        ctx.beginPath;
        ctx.lineWidth = 1;
        ctx.moveTo(center.x, center.y + 10);
        ctx.lineTo(center.x + 440, center.y + 10);
        ctx.stroke();
      }
      // 更新的类型   数据   实时数据
      function update(type, data, now) {
        now = now - 1;
        if (now !== data[type].num) {
          let dis = now - data[type].num;
          data[type].tRadain = now * data[type].st;
          data[type].cRadain = data[type].num * data[type].st;
          data[type].step = (dis * data[type].st) / 15;
          data[type].num = now;
        }
      }
      let lastS ;
      function step() {
        // 获取当前时间信息
        date = new Date();
        y = date.getFullYear();
        m = date.getMonth() + 1;
        d = date.getDate();
        h = date.getHours();
        min = date.getMinutes();
        s = date.getSeconds();
        types.date.length = new Date(
          new Date(`${y}-${m + 1}-1`) - 24 * 3600 * 1000
        ).getDate();

        update("s", data, s);
        update("min", data, min);
        update("h", data, h);
        update("d", data, d);
        update("m", data, m);

        ctx.clearRect(0, 0, cs.width, cs.height);
        createTitle();
        showMark(data.s);
        showMark(data.min);
        showMark(data.h);
        showMark(data.d);
        showMark(data.m);

        console.log('ssss->:', s);

        if (!lastS || lastS !== s) {
            lastS = s; 
            let pindex = s % 4;
            ps[pindex].innerHTML = nth(s);
            ps[pindex].classList.remove('upup');
            setTimeout(()=> {
                ps[pindex].classList.add('upup');
            })
          
        }

       

        requestAnimationFrame(step);
      }
      requestAnimationFrame(step);

      window.onresize = function () {
        getCsWidth(cs);
      };

    </script>
  </body>
</html>