본문으로 건너뛰기

Canvas 로 원을 따라 이동하는 타이머 만들기

O-h-y-o약 1 분

Canvas 로 원을 따라 이동하는 타이머 만들기




<template>
  <canvas ref="canvas" :width="width" :height="height"></canvas>

  <div class="chart-start-info absolute-center">
    <div class="chart-start-text-area">Arrived in {{ timeRemaining }}</div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";

const timeRemaining = ref("");
const canvas = ref<HTMLCanvasElement>();
const width = ref(0);
const height = ref(0);

const RADIAN = Math.PI * 2;
const DEGREE = RADIAN / 360;

const START_ANGLE = -35 * DEGREE;
const END_ANGLE = 215 * DEGREE;
const CIRCLE_START = -60 * DEGREE;
const CIRCLE_END = 240 * DEGREE;
const intervalTimeSet = 1000 / 10;

const duration = 60 * 1000;

let ctx: CanvasRenderingContext2D | null | undefined = null;
let CIRCLE_SIZE = 0;
let CIRCLE_X = 0;
let CIRCLE_Y = 0;

const moveImg = new Image();
const earth = new Image();

const setTimeRemaining = (tick: number) => {
  timeRemaining.value = ((duration - tick) / 1000).toFixed(2).replace(".", ":");
};

const startDraw = () => {
  setInterval(() => {
    draw();
  }, intervalTimeSet);
};

const draw = () => {
  const time = new Date();
  const tick = time.getSeconds() * 1000 + time.getMilliseconds();
  setTimeRemaining(tick);

  if (!ctx) {
    return;
  }

  const t = (tick % duration) / duration;

  ctx.clearRect(0, 0, width.value, height.value);

  ctx.save();
  ctx.beginPath();
  ctx.fillStyle = "rgba(32, 37, 49, 0.5)";
  ctx.strokeStyle = "#e6af1c";
  ctx.arc(CIRCLE_X, CIRCLE_Y, CIRCLE_SIZE, 0, RADIAN);
  ctx.fill();
  ctx.stroke();
  ctx.closePath();
  ctx.restore();

  ctx.save();
  ctx.translate(CIRCLE_X, CIRCLE_Y);
  ctx.rotate(END_ANGLE);
  ctx.translate(CIRCLE_SIZE, 0);
  ctx.drawImage(earth, -7, -7);
  ctx.restore();

  ctx.save();
  ctx.translate(CIRCLE_X, CIRCLE_Y);
  ctx.rotate(START_ANGLE + (END_ANGLE - START_ANGLE) * t);
  ctx.translate(CIRCLE_SIZE, 0);
  ctx.drawImage(moveImg, -13, -30, 50, 50);
  ctx.restore();
};

onMounted(() => {
  ctx = canvas.value?.getContext("2d");
  if (ctx == null) {
    return;
  }

  width.value = canvas.value?.clientWidth ?? 0;
  height.value = canvas.value?.clientHeight ?? 0;

  ctx.globalCompositeOperation = "destination-over";

  moveImg.src = "img-path";
  earth.src = "img-path";

  const padding = 50;
  const CIRCLE_WIDTH = width.value / 2 - padding;
  const CIRCLE_HEIGHT = height.value / 2 - padding;
  CIRCLE_SIZE = Math.min(CIRCLE_WIDTH, CIRCLE_HEIGHT);
  CIRCLE_X = width.value / 2;
  CIRCLE_Y = height.value / 2;

  startDraw();
});
</script>

<style scoped lang="scss">
canvas {
  width: 100%;
  height: 100%;
}

.chart-start-info {
  background-color: #e6af1c;
  border-radius: 0.3rem;
  padding: 3px 4px;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  height: 30px;
  width: 150px;

  .chart-start-text-area {
    line-height: 1.8;
    font-size: 1rem;
    padding: 0 0.5em;
    background-color: #de9c05;
    box-shadow: inset 0px -3px 1px -2px rgba(244, 223, 151, 0.8), inset 0px 2px
        1px -1px #d57c0f;
    border-radius: 0.3rem;
    text-align: center;
    font-weight: 500;
    color: #ffffff;
    text-shadow: -0.5px 0.5px 1px #cd680b;
  }
}
</style>

전체 코드입니다.

원하는대로 스타일을 수정하면 됩니다.

더 부드러운 효과를 원한다면 intervalTimeSet 주기를 더 빠르게 조절하면 됩니다.

1분이 아닌 다른 시간을 타이머로 지정하고싶으면 durationTime 을 조절하면 됩니다.

위에서 부터 시작하고 한바퀴를 다 돌리고 싶다면 START_ANGLE-90 * DEGREE로 바꾸고,

END_ANGLE270 * DEGREE 로 수정하면 됩니다.