Canvas를 이용한 persentage Circle 만들기

Jaewoong2·2020년 12월 21일
0

Canvas

목록 보기
2/2
post-thumbnail

Persentage-Circle

원의 둘레를 돌면서 내가 원하는 만큼만 원의둘레를 색칠해주는 것을 만들고 싶었다.

다른 자료를 찾아보지 않고 만들었다.
(글을 쓰기위해서 찾아보았을 때, svg를 사용하는 것을 보았는데, 이는 해보지 않아서 어차피 처음부터 만들었어야 했을 것 같다.)

canvas

canvas를 사용할 때, 고려해야했던 점이 canvas를 window 크기만큼 지정해서 원의 위치를 받아서 원의 반지름 만큼 그림을 그려줬어야 하는게 맞는지,

canvas를 원의 자식 노드로 넣어서 원마다 canvas를 넣어줘야 하는게 맞는지 잘 모르겠어 서 일단 후자로 선택하였다.
(위치 잡기가 편할 것 같아서)

오류사항

canvas의 넓이와 높이를 원의 width와 heigth를 동일하게하니, canvas의 lineWidth가 커지면서 canvas의 크기를 넘어서는 부분이 사라지는 것을 확인햇다.

해결방법

일단 원에 css를 지정해서 자손노드가 가로세로 가운데 정렬이 되도록 하고,

cavnas arc(x, y, radius, from, to) 함수의 x, y 값을 내가 canvas 크기를 늘려준만큼의 1/2 을 증가시켜줬다.


소스코드

type styleType = {
    [key in keyof CSSStyleDeclaration]?: string | null;
};

function sleep(ms: number = 500) {
    return new Promise<void>(resolve => {
        setTimeout(() => {
            resolve();
        }, (ms));
    })
}

function settingCssProperty(ref: HTMLElement, cssProperty?: styleType) {
    if (cssProperty !== null && cssProperty !== undefined) {
        const keys = Object.keys(cssProperty);
        const values = Object.values(cssProperty);
        for (let i = 0; i < keys.length; i++) {
          const index = keys[i]
            .split("")
            .findIndex((value) => value.toLocaleLowerCase() !== value);
          if (index > -1) {
            const upperWord = keys[i][index];
            const key = keys[i].replace(
              upperWord,
              `-${upperWord.toLocaleLowerCase()}`
            );
            const value = values[i];
            if(value) {
                ref.style.setProperty(key, value);
            }
          } else {
            const value = values[i];
            if(value) {
                ref.style.setProperty(keys[i], value);
            }
          }
        }
      }
}

class NewElement {
    element: HTMLElement;
    constructor(elem: keyof HTMLElementTagNameMap) {
        this.element = document.createElement(elem);
    }
}

class CircleElement extends NewElement{    
    constructor(elem: keyof HTMLElementTagNameMap = "div") {
        super(elem);
    }

    create(ref: HTMLElement, styleOptions: styleType) {
        settingCssProperty(this.element, styleOptions);
        ref.appendChild(this.element);

        return this.element;
    }
}

class CircleCanvas {
    canvas: HTMLCanvasElement;
    context: CanvasRenderingContext2D | null;
    width: number;
    heigth: number;
    ref: HTMLElement;
    radius: number;
    position: {x: number, y: number};

    ARCfrom: number = (1.5 * Math.PI);
    addedWidth: number = 20;

    constructor(ref: HTMLElement) {
        this.ref = ref;
        const clinetRect = this.ref.getBoundingClientRect();
        this.width = clinetRect.width;
        this.heigth = clinetRect.height;
        this.position = {
            x: (this.width / 2) + this.addedWidth / 2,
            y: (this.heigth / 2) + this.addedWidth / 2,
        }
        this.radius = this.width / 2;

        this.canvas = document.createElement('canvas');
        this.canvas.width = this.width + this.addedWidth;
        this.canvas.height = this.heigth + this.addedWidth;
        this.canvas.style.position = "absolute";

        this.context = this.canvas.getContext('2d');
        this.ref.appendChild(this.canvas);
    }

    create() {        
        return this.canvas;
    }

    update(to: number) {
        this.context?.clearRect(0, 0, this.canvas.width, this.canvas.height);
        if(this.context) {
            this.context?.beginPath();
            this.context.lineCap = "round";
            this.context.strokeStyle = "rgba(250, 50, 44)";
            this.context.lineWidth = 5.5;
            this.context?.arc(this.position.x, this.position.y, this.radius, this.ARCfrom, (1.5 + to / 50) * Math.PI);
            this.context?.stroke();
        }
    }
}

const createElement = async () => {
    const parentStyleOptions: styleType = {
        width: "100vw",
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        position: "relative",
    }
    const parentElement = new CircleElement().create(document.body, parentStyleOptions);

    const styleOptions: styleType = {
        width: "150px",
        height: "150px",
        borderRadius: "50%",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        position: "relative",
        backgroundColor: "white"
    }
    const circleElement = new CircleElement().create(parentElement, styleOptions);

    const circleLine = new CircleCanvas(circleElement);
    circleLine.create();
    
    /** update / 10ms */
    const per = 75;
    for(let i = 0; i <= per; i++ ) {
        await sleep(10);
        circleLine.update(i);
    }
    
    /** 숫자 표시 */
    const span = new NewElement("span").element;
    span.innerText = `${per}%`;
    circleElement.appendChild(span);
}

createElement();
profile
DFF (Development For Fun)

0개의 댓글