首页 > 解决方案 > 带有时钟图形的 Javascript 计时器

问题描述

我一直在研究一个计时器,它根据用户的选择显示 3 个预设时间。我决定包含一个时钟形状的倒计时图形,它只有一只手并且在分配给它的整个时间长度内旋转 360 度。

我遇到的问题是将原始计时器和新显示器结合起来。我有用画布绘制的“时钟”元素,我有一个工作计时器,但我需要知道如何利用这些时间并利用它来让手四处走动。就目前而言,当我让它运行时,指针会画出来,但主要是因为我没有为时钟设置间隔,但我确实为原始计时器设置了一个间隔。

完整的 JS

((d) => {
  let btn = d.getElementById("btn");
  let reset = d.getElementById("reset");
  let countdown = d.getElementById("countdown");
  let btnLength = d.getElementById("btnLength");

  // 25 Minutes
  let short = 1500;
  // 45 Minutes
  let med = 2700;
  // 90 Minutes
  let long = 5400;

  let counter;
  let startTime = short;

  let timerFormat = (s) => {
    return (s - (s %= 60)) / 60 + (9 < s ? ":" : ":0") + s;
  };

  countdown.textContent = timerFormat(startTime);

  let timer = () => {
    startTime--;
    countdown.textContent = timerFormat(startTime);
    if (startTime === 0) clearInterval(counter);
  };

  let start = () => {
    counter = counter || setInterval(timer, 950);
  };

  let stop = () => {
    clearInterval(counter);
    counter = undefined;
  };

  // Changes between Start and Stop button labelling.

  btn.addEventListener("click", () => {
    if (counter) {
      stop();
      btn.textContent = "Start";
    } else {
      start();
      btn.textContent = "Stop";
    }
  });

  // Stops counter, resets to original start time and
  // resets button label to Start.

  reset.onclick = () => {
    stop();
    startTime = short;
    if (btn.textContent === "Stop") btn.textContent = "Start";
    btnLength.textContent = "Short";
    countdown.textContent = timerFormat(startTime);
  };

  // Changes timer legnth and button labelling

  btnLength.addEventListener("click", () => {
    if (startTime === short) {
      startTime = med;
      btnLength.textContent = "Medium";
    } else if (startTime === med) {
      startTime = long;
      btnLength.textContent = "Long";
    } else if (startTime === long) {
      startTime = short;
      btnLength.textContent = "Short";
    }
    countdown.textContent = timerFormat(startTime);
  });

  //Clock

  let c = d.getElementById("canvas");
  let ctx = c.getContext("2d");
  let radius = c.height / 2;
  ctx.translate(radius, radius);
  radius = radius * 0.9;

  showTime = (ctx, radius) => {
    let startTime =
      (startTime * Math.PI) / 30 + (startTime * Math.PI) / (30 * 60);
    hand(ctx, startTime, radius * 0.8, radius * 0.07);
  };

  hand = (ctx, position, length, width) => {
    ctx.beginPath();
    ctx.lineWidth = width;
    ctx.lineCap = "round";
    ctx.moveTo(0, 0);
    ctx.rotate(position);
    ctx.lineTo(0, -length);
    ctx.stroke();
    ctx.rotate(-position);
  };

  clock = () => {
    ctx.arc(0, 0, radius, 0, 2 * Math.PI, true);
    ctx.fillStyle = "#f5afaf";
    ctx.fill();
    showTime();
  };

  clock();
})(document);

html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="../styles/styles.css" />
    <script defer src="../JS/timer.js"></script>
  </head>
  <body>
    <div class="btn__container">
      <div id="countdown"></div>
      <button class="btn" id="btn">Start</button>
      <button class="btn" id="btnLength">Short</button>
      <button class="btn" id="reset">Reset</button>
<div class="clock">
<canvas  id="canvas" width="300" height="300" style="background-color:none"></canvas>
</div>
  </body>
</html>


标签: javascript

解决方案


您遇到的第一个问题是您只调用clock()一次 - 在脚本结束时对其进行初始化。将其添加到timer()函数中:

let timer = () => {
  startTime--;
  countdown.textContent = timerFormat(startTime);
  clock(); // <-- here
  if (startTime === 0) clearInterval(counter);
};

第二个问题是你没有通过ctxradius当你打电话时showTime()

clock = () => {
  ctx.arc(0, 0, radius, 0, 2 * Math.PI, true);
  ctx.fillStyle = "#f5afaf";
  ctx.fill();
  showTime(ctx, radius); // <-- here
};

您现在将遇到的第三个问题是Cannot access 'startTime' before initialization. startTime这是因为你在函数中重新声明了showTime()。将此函数更新为:

showTime = (ctx, radius) => {
  let time = // <-- here
    (startTime * Math.PI) / 30 + (startTime * Math.PI) / (30 * 60);
  hand(ctx, time, radius * 0.8, radius * 0.07);
};

您需要做的最后一件事是在画手时调用完成路径:

hand = (ctx, position, length, width) => {
  ctx.beginPath();
  ctx.lineWidth = width;
  ctx.lineCap = "round";
  ctx.moveTo(0, 0);
  ctx.rotate(position);
  ctx.lineTo(0, -length);
  ctx.stroke();
  ctx.rotate(-position);
  ctx.closePath(); // <-- here
};

现在指针随着计时器移动。但是,如果可以的话,我想提出一些改进建议。目前,您移动画布以使时钟的中心位于坐标 处(0,0),然后每次绘制手时再移动画布两次。我建议你不要管画布,而是指定一个offset告诉你时钟中心在哪里的。

现在您需要指定手尖的精确坐标,这可以通过一些快速的三角函数来完成(参见下面的完整示例)。

最后一件事,你的手的起始位置在右下角的某个地方。也许时间的好位置0会在上面。如果你想改变它,你可以PIangle计算中添加一些比率showTime()(例如添加到底部+ Math.PI有时间0

请参阅下面的完整示例:

((d) => {
    let btn = d.getElementById("btn");
    let reset = d.getElementById("reset");
    let countdown = d.getElementById("countdown");
    let btnLength = d.getElementById("btnLength");
  
    // 25 Minutes
    let short = 1500;
    // 45 Minutes
    let med = 2700;
    // 90 Minutes
    let long = 5400;
  
    let counter;
    let startTime = short;
  
    let timerFormat = (s) => {
      return (s - (s %= 60)) / 60 + (9 < s ? ":" : ":0") + s;
    };
  
    countdown.textContent = timerFormat(startTime);
  
    let timer = () => {
      startTime--;
      countdown.textContent = timerFormat(startTime);
      clock(); // <-- here
      if (startTime === 0) clearInterval(counter);
    };
  
    let start = () => {
      counter = counter || setInterval(timer, 950);
    };
  
    let stop = () => {
      clearInterval(counter);
      counter = undefined;
    };
  
    // Changes between Start and Stop button labelling.
  
    btn.addEventListener("click", () => {
      if (counter) {
        stop();
        btn.textContent = "Start";
      } else {
        start();
        btn.textContent = "Stop";
      }
    });
  
    // Stops counter, resets to original start time and
    // resets button label to Start.
  
    reset.onclick = () => {
      stop();
      startTime = short;
      if (btn.textContent === "Stop") btn.textContent = "Start";
      btnLength.textContent = "Short";
      countdown.textContent = timerFormat(startTime);
    };
  
    // Changes timer length and button labelling
  
    btnLength.addEventListener("click", () => {
      if (startTime === short) {
        startTime = med;
        btnLength.textContent = "Medium";
      } else if (startTime === med) {
        startTime = long;
        btnLength.textContent = "Long";
      } else if (startTime === long) {
        startTime = short;
        btnLength.textContent = "Short";
      }
      countdown.textContent = timerFormat(startTime);
    });
  
    //Clock
  
    let c = d.getElementById("canvas");
    let ctx = c.getContext("2d");
    let offset = c.height / 2;
    let radius = offset * 0.9;
  
    showTime = (ctx, radius) => {
      let second = startTime % 60;
      let angle = (2*Math.PI) - (2 * Math.PI * second) / 60; // Add + Math.PI; to put time 0 in the bottom

      hand(ctx, angle, radius * 0.8, radius * 0.07); 
    };
  
    hand = (ctx, angle, length, width) => {
      // Calculate coordinates of the tip of the hand.
      x = offset + Math.round(- length * Math.sin(angle));
      y = offset + Math.round(- length * Math.cos(angle));

      ctx.beginPath();
      ctx.lineWidth = width;
      ctx.lineCap = "round";
      ctx.moveTo(offset, offset);
      ctx.lineTo(x, y);
      ctx.stroke();
      ctx.closePath();
    };
  
    clock = () => {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.arc(offset, offset, radius, 0, 2 * Math.PI, true);
      ctx.fillStyle = "#f5afaf";
      ctx.fill();
      showTime(ctx, radius); 
    };
  
    clock();
  })(document);
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
    <script defer src="../Desktop/timer.js"></script>
  </head>
  <body>
    <div class="btn__container">
      <div id="countdown"></div>
      <button class="btn" id="btn">Start</button>
      <button class="btn" id="btnLength">Short</button>
      <button class="btn" id="reset">Reset</button>
      </div>
<div class="clock">
<canvas  id="canvas" width="300" height="300" style="background-color:none"></canvas>
</div>
  </body>
</html>


推荐阅读