Canvas - 04

이강민·2022년 1월 4일
0

[혼공]Canvas

목록 보기
4/12
post-thumbnail

스타일과 색 적용하기

색상

도형에 색을 적용하고자 하면, fillStyle과 strokeStyle 두가지 중요한 속성을 사용 할 수 있다.

  • fillStyle = color
    • 도형을 채우는 색을 설정한다.
  • strokeStyle = color
    • 도형의 윤곽선 색을 설정한다.

여기서의 color는 CSS의 <color>, 그라디언트 객체, 패턴 객체를 뜻한다. 윤곽선과 채움 색의 초기 설정값은 검은색이다.
(#000000)

참고
strokeStyle 또는 fillStyle 속성을 설정하면, 새로 설정된 값이 앞으로 그려질 도형의 기본 값이 된다.
각 도형의 다른 색을 적용하려면 다시 속성을 적용해야 한다.

오렌지 색의 여러 표현

// fillStyle에 적용되는 색은 모두 '오렌지'

ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255, 165, 0)";
ctx.fillStyle = "rgba(255, 165, 0, 1)";

fillStyle의 예제

반복문을 활용하여 아래와 같은 그림을 만들어 보자

 //canvas를 가져옵니다.
    let canvas = document.getElementById('canvas');
    //canvas를 지원하면 다음 함수를 실행합니다.
    if(canvas.getContext){
        draw();    
    }
    //canvas를 그리는 함수
    function draw(){
        //getContext를 가져오고
        let ctx  = canvas.getContext('2d');
        //for문을 중첩반복을 하여 6x6개의 사각형을 표현한다.
        for(let i = 0; i < 6; i++){
            for(let j = 0; j < 6; j++){
                //rgb를 사용하면서 green값을 서서히 어둡게 만든다.
                //마찬가지로 j문이 끝날때마다 red값을 서서히 어둡게 만든다.
                ctx.fillStyle = `rgb(${Math.floor(255-42.5*i)}, ${Math.floor(255-42.5*j)},0)`;
                //25,25사이즈의 사각형을 배치
                ctx.fillRect(j*25, i*25, 25,25);
            }
        }
    }

strokeStyle의 예제

위의 예제와 비슷하지만 strokeStyle속성으로 사용하여 윤곽선의 색상을 적용한다.

    //canvas를 가져옵니다.
    let canvas = document.getElementById('canvas');
    //canvas를 지원하면 다음 함수를 실행합니다.
    if(canvas.getContext){
        draw();    
    }
    //canvas를 그리는 함수
    function draw(){
        //getContext를 가져오고
        let ctx  = canvas.getContext('2d');
       for(let i = 0; i < 6; i ++){
           for(let j = 0; j < 6; j ++){
               ctx.strokeStyle = `rgb(0,${Math.floor(255-42.5*i)}, ${Math.floor(255-42.5*j)})`;

               ctx.beginPath();
               ctx.arc(12.5+j*25, 12.5+i*25, 10, 0 , Math.PI * 2, true);
               ctx.stroke();
           }
       }
        
    }

투명도

캔버스에는 불투명한 도형을 그릴 수도 있고, 반투명한 도형도 그릴 수 있다. globalAlpha 값을 설정하거나 윤곽선 또는 채움 스타일에 반투명 색을 적용하면 된다.

  • globalAlpha = transparencyValue
    • 투명도 값이 설정되면 이후 캔버스에 그려지는 모든 도형들의 투명도가 바뀐다. 0.0(완전투명), 1.0(완전불투명) 사이에 있어야하며, 초기값은 1.0(완전불투명)이다.

globalAlpha는 모두 같은 투명도로 캔버스에 많은 도형을 그릴 때 유용하다. 하지만, 보통은 각각의 도형마다 투명도를 설정하는 것이 더 유용할 것이다.

strokeStyle과 fillStyle 값에 CSS rgba 색상값을 적용할 수 있으므로, 투명한 색을 적용하려면 아래와 같이 표기한다.

// 외곽선과 채움 스타일에 투명 적용

ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';

globalAlpha 예제

그 위에, 반투명한 원을 여러 개 그린다. globalAlpha 값을 0.2로 설정하면 이후 그려질 도형은 이 값을 사용한다.
for 반복문을 사용하여 점점 큰 반지름의 원을 그린다.
최종 결과물은 원형 그레디언트가 된다. 원이 겹쳐지면서 점점 불투명해지는 것을 볼 수 있으며, 최종적으로 한 가운데에 있는 있는 원에서는 뒷 배경이 거의 보이지 않게 된다.

   const canvas = document.getElementById('canvas');
    if(canvas.getContext){
        draw();
    }
   function draw(){
        const ctx = canvas.getContext('2d');

        ctx.fillStyle = '#fd0';
        ctx.fillRect(0,0,75,75);
        ctx.fillStyle = '#6c0';
        ctx.fillRect(75,0,75,75);
        ctx.fillStyle = '#09f';
        ctx.fillRect(0,75,75,75);
        ctx.fillStyle = '#f30';
        ctx.fillRect(75,75,75,75);
        ctx.fillStyle = '#fff'

        ctx.globalAlpha = 0.2;

        // 원을 그린다.
        for(let i = 0; i < 7; i ++){
            ctx.beginPath();
            //i가 커질 수 록 10만큼 큰 원이 생성됨.
            ctx.arc(75,75,10*i + 10, 0, Math.PI * 2, true);
            ctx.fill();
        }
   }

rgba()를 사용한 예제

글로벌알파와 달리 각각의 도형별로 투명스타일을 설정할 수 있기때문에 제어가 쉽고 융통성이 높다.

 const canvas = document.getElementById('canvas');
    if(canvas.getContext){
        draw();
    }
   function draw(){
        const ctx = canvas.getContext('2d');

        ctx.fillStyle = 'rgb(255,221,0)';
        ctx.fillRect(0,0,150,37.5);
        ctx.fillStyle = 'rgb(102,204,0)';
        ctx.fillRect(0,37.5,150,37.5);
        ctx.fillStyle = 'rgb(0,153,255)';
        ctx.fillRect(0,75,150,37.5);
        ctx.fillStyle = 'rgb(255,51,0)';
        ctx.fillRect(0,112.5,150,37.5);


        // 원을 그린다.
        for(let i = 0; i < 10; i ++){
            //i가 증가할수록 x축이 증가할수록 투명도가 진해짐.
            ctx.fillStyle = `rgba(255,255,255,${(i+1)/10})`;
            console.log(ctx.fillStyle)
            //i가 커질 수 록 10만큼 큰 원이 생성됨.
           for(let j = 0; j < 4; j++){
               //세로로 먼저 4번씩 배치되면서 x축으로 i가 증가될때마다 옮겨짐
               ctx.fillRect(i*14+5, j*37.5+5,14,27.5)
           }
        }
   }

선 모양

선의 스타일을 바꾸는 법

  • lineWidth = value;
    • 이후 그려질 선의 두께를 설정
  • lineCap = type;
    • 선의 끝 모양을 설정
  • lineJoin = type;
    • 선들이 만나는 모서리 모양을 설정
  • miterLimit = value;
    • 두 선이 예각으로 만날 때 접합점의 두께를 제어할 수 있도록 연결부위의 크기를 제한하는 값을 설정
  • getLineDash()
    • 음수가 아닌 짝수를 포함하는 현재 선의 대시 패턴 배열을 반환
  • setlineDash(segments)
    • 현재 선의 대시 패턴을 설정
  • lineDashOffset = value;
    • 선의 대시 배열이 어디서 시작될지 지정한다.

lineWidth 예제

현재 선의 두께를 설정한다. 설정값은 반드시 양수여야 하며, 초기값은 1.0단위이다.

선의 두께는 지정된 경로의 가운데 있는 획의 두께이다. 경로의 좌우로 설정된 두께의 반만큼 폭 영역이 그려진다.
캔버스 좌표는 픽셀을 직접 참조하지 않으므로 선명한 수평 및 수직 선을 얻기 위해 특별히 주의를 기울여야 한다.

const canvas = document.getElementById('canvas');
    if(canvas.getContext){
        draw();
    }
   function draw(){
        const ctx = canvas.getContext('2d');

        for(let i=0; i < 10; i++){
            ctx.lineWidth = 1 + i;
            ctx.beginPath();
            ctx.moveTo(i * 14 + 5, 5);
            ctx.lineTo(i * 14 + 5, 140);
            ctx.stroke();
        }
   }

선명한 선을 얻으려면 경로에 획을 어떻게 그려지는지 이해해야 한다. 아래의 이미지를 보면, 격자는 캔보스의 좌표 격자를 나타낸다. 격자선 사이에 있는 사각형은 실제 픽셀과 딱 맞아 떨어지고 아래의 첫번째 이미지를 보면 (2,1)에서 (5,5)로 사각형이 채워져 있다. 사각형의 전체 영역은 픽셀 경계선에 딱맞아 떨어지기 때문에 채워진 사각형은 선명한 가장자리를 갖는다.

만일 (3,1)에서 (3,5)로 그리는 직선의 두께가 1.0이라면, 두번째 이미지와 같은 상황이 됩니다. 채워진 실제 영역 (진한 파란색)은 패스의 양쪽에있는 픽셀의 절반까지만 확장됩니다. 이것은 1 픽셀을 채우는 것이 아니므로 근사값으로 화면에 그려지게 됩니다. 그래서 양옆의 영역(연한 파란색과 짙은 파란 색)으로, 실제 설정한 색과는 다른 흐릿한 색으로 채워지는 것입니다. 이전 예제에서 보듯이 선 두께가 1.0인 선에서 일어난 일입니다.

이렇게 되는 것을 막으려면, 경로를 아주 정밀하게 생성해야 합니다. 선의 두께가 1.0이면 경로의 양쪽으로 0.5 단위만큼이라는 것을 알고 있으니, (3.5,1)에서 (3.5,5)로 그리는 경로를 생성하는 세번째 이미지의 결과는 완벽히 정밀하게 1 픽셀 두께의 수직선이 됩니다.

참고
위에 나온 수직선 그리기 예제를 살펴보면, Y 위치는 정수로 된 격자선 위치를 참조하고 있습니다. 그렇지 않았다면, 끝점에서 픽셀의 반을 차지한 상태로 보였을 것입니다. (초기값이 butt인 lineCap 스타일의 설정값에 따라 다르게 보입니다. 홀수 두께 선들의 좌표를 0.5 픽셀씩 조정하여 다시 계산하고 싶을지도 모릅니다. lineCap 스타일을 square로 설정함으로써, 끝점에서 선의 외곽 경계선은 픽셀에 딱 맞게 자동적으로 확장될 것입니다.)
경로의 시작 지점과 종료 지점의 끝점만이 영향을 받는다는 것에 주목하세요. 만약 closePath()로 경로가 닫힌다면, 시작 지점과 종료 지점은 없는 것입니다. 그 대신, 경로 안에 있는 모든 끝점들은, 초기 설정값이 miter인 lineJoin 스타일의 설정값을 사용하여 이전 부분 및 다음 부분과 이어지는데, 교차되는 점들로 이어진 부분들의 외곽 경계선을 자동적으로 확장하는 효과가 생깁니다. 그렇기 때문에 만약 이들 이어진 부분들이 수직 또는 수평이라면, 그려지는 선들은 각 끝점의 중심에 놓인 픽셀을 가득 채우게 될 것입니다. 이들 선 스타일에 대한 예제는 아래에 나옵니다.

짝수 두께의 선들은 반으로 나누어도 각각의 반은 정수의 양만큼이기 때문에 픽셀을 조정할 필요가 없습니다.

비트맵이 아닌 벡터 2D 그래픽으로 작업할 때, 작업을 시작할 때는 약간 힘들겠지만, 격자와 경로의 위치에 주의를 기울인다면, 크기를 키우거나 줄이거나 또는 어떠한 변형을 하더라도 그리려는 이미지는 똑바로 보일 것입니다. 1.0 두께의 수직선은 2배로 크기를 키웠을 때, 정확히 2 픽셀 두께의 선이 되며, 올바른 위치에 나타날 것입니다.

lineCap 예제

lineCap 속성은 그리는 모든 선의 끝점을 결정한다.

  • butt
    • 선의 끝이 좌표에 딱 맞게 잘린다.
  • round
    • 선의 끝이 동그랗다.
  • square
    • 선 끝에, 선 두께 반만큼의 사각형 영역이 더해진다.

    const canvas = document.getElementById('canvas');
    const lineCap = ['butt', 'round', 'square'];

    if(canvas.getContext){
        draw();
    }
   function draw(){
        const ctx = canvas.getContext('2d');

            //안내선
        ctx.strokeStyle = '#09f';
        ctx.beginPath();
        ctx.moveTo(10, 10);
        ctx.lineTo(140,10);
        ctx.moveTo(10, 140);
        ctx.lineTo(140,140);
        ctx.stroke();
        //선을 그림
        ctx.strokeStyle ='#000';
        for(let i =0; i < lineCap.length; i++){
            ctx.lineWidth = 15;
            ctx.lineCap = lineCap[i];
            ctx.beginPath();
            ctx.moveTo(i*50+25, 10)
            ctx.lineTo(i*50+25, 140);
            ctx.stroke()
        }
   }

lineJoin 예제

lineJoin 속성은, 도형을 이루는 선이나 호나 곡선들이 연결되는 지점의 모양을 결정한다. 끝점과 제어점이 정확히 같은 위치인, 길이가 0인 부분들은 제외된다.

이 속성에는 세 가지 값이 있는데, round, bevel, miter이며, 초기 설정값은 miter이다. 두 부분들이 같은 방향으로 연결되어 있는 경우에는, lineJoin을 설정해도 아무런 효과가 나타나지 않는다.

  • round

    • 도형의 모서리를 연결되는 부분들의 공통끝점을 중심으로 하는 원 모양으로 만든다. 이때 원의 반지름은 선의 두께와 같다.
  • bevel

    • 도형의 모서리를 연결되는 부분들의 공통 끝점에서 세모 모양으로 만든다.
  • miter

    • 도형의 모서리를 두 부분의 바깥쪽 테두리 선을 각각 연장하여 교차된 점으로 생긴 마름모꼴로 만든다. miterLimit 속성값에 따라 달라진다.

    const canvas = document.getElementById('canvas');
    const lineJoin = ['round', 'bevel', 'miter'];
    const ctx = canvas.getContext('2d');
    ctx.lineWidth = 10;
    if(canvas.getContext){
        draw();
    }
   function draw(){

    for(let i = 0; i<lineJoin.length; i++){
        ctx.lineJoin = lineJoin[i];
        ctx.beginPath();
        ctx.moveTo(-5, i*40 + 5);
        ctx.lineTo(35, i*40+45);
        ctx.lineTo(75, i*40+5);
        ctx.lineTo(115, i*40+45);
        ctx.lineTo(155, i*40+5);
        ctx.stroke();
    }   
   }

miterLimit 예제

위의 예제에서 볼 수 있듯이, 속성값을 miter로 하여 두 선이 연결되면, 연결되는 두 선의 바깥쪽 테두리는 연장선이 만나는 지점까지 확장됩니다. 연결된 두 선이 이루는 각도가 크면, 확장되는 영역이 크지 않지만, 각도가 작아짐(끝이 뾰족해짐)에 따라서 이 영역이 기하급수적으로 커질 수도 있습니다.

miterLimit 속성은 끝점이 만나는 지점과 테두리 연장선이 만나는 지점이 얼마나 떨어져 있을지를 결정합니다. 두 선이 이 값을 넘게 되면, lineJoin 속성의 bevel 값 모양이 적용됩니다. miterLimit 속성값(HTML <canvas>에서, 초기 설정값은 10.0)에 의해, 현재 좌표 방식 안에서, 선의 두께에 따라, 어느 정도까지 뾰족해질 수 있는지가 계산됩니다. 그래서 miterLimit은 현재 디스플레이 비율이나 경로의 변형 같은 것으로 각각 설정될 수 있습니다. 그렇게 하여 선의 모서리에만 영향을 줍니다.

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    if(canvas.getContext){
        draw();
    }

   function draw(){

    ctx.clearRect(0,0,150,150);

    ctx.strokeStyle = '#09f';
    ctx.lineWidth = 2;
    ctx.strokeRect(-5, 50, 160, 50);

    ctx.strokeStyle = '#000';
    ctx.lineWidth = 10;

    if(document.getElementById('miterLimit').value.match(/\d+(\.\d+)?/)){
        ctx.miterLimit = parseFloat(document.getElementById('miterLimit').value);
    } else{
        alert('Value must be a positive number');
    }

    // 선을 그린다.
    ctx.beginPath();
    ctx.moveTo(0,100);
    for(let i =0; i<24; i++){
        let dy = i%2 == 0 ? 25 : -25;
        ctx.lineTo(Math.pow(i, 1.5) * 2, 75 + dy);
    }
    ctx.stroke();

    return false;
   }

대시라인 적용하기

setLineDash 메소드와 lineDashOffset 속성은 선의 대시 패턴을 지정한다. setLineDash 메소드는 거리를 지정하는 숫자 목록을 받아서 선과 틈을 교대로 그리며 lineDashOffset 속성은 패턴을 시작할 위치를 오프셋으로 설정한다.

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    let offset = 0;

    if(canvas.getContext){
        //움직이는 함수를 만들꺼다.
        function march(){
            //시작위치를 1씩증가시킨고
            offset++;
            //16보다 크면 다시 0으로 초기화
            if(offset > 16){
                offset = 0;
            }
            //draw()함수를 호출하고
            draw();
            //본인을 20밀리초마다 호출한다.
            setTimeout(march, 20)
        }
        march();
    }
   function draw(){
       //캔버스의 크기만큼 흰색으로 초기화한다.
    ctx.clearRect(0,0,canvas.width, canvas.height);
    //setLineDash의 거리지정 목록을 배열로 만든다.
    ctx.setLineDash([4,2]);
    ctx.lineDashOffset = -offset;
    //사각형을 만든다. 대시속성으로 인하여 선은 대시로 표현된다.
    ctx.strokeRect(10,10,100,100);
   }

그라디언트(Gradient)

다른 그래픽 프로그램들과 마찬가지로, 선형 및 원형의 그레이디언트를 사용할 수 있다. 다음 메소드 중 하나를 사용하여 CanvasGradient 객체를 생성하고 다음 이 객체를 fillStyle 또는 strokeStyle 속성에 할당 할 수 있다.

  • createLinearGradient(x1,y1,x2,y2)
    • 시작점 좌표를 (x1, y1)로 하고, 종료점 좌표를 (x2, y2)로 하는 선형 그라디언트 오브젝트를 생성
  • createRadialGradient(x1,y1,r1,x2,y2,r2)
    • 반지름이 r1이고 좌표 (x1, y1)을 중심으로 하는 원과, 반지름이 r2이고 좌표 (x2, y2)를 중심으로 하는 원을 사용하여 그라디언트가 생성
var lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
var radialgradient = ctx.createRadialGradient(75, 75, 0, 75, 75, 100);

CanvasGradient 객체를 만들었다면, addColorStop() 메소드를 사용하여, 오브젝트에 색을 적용

  • gradient.addColorStop(position, color)
    • gradient 오브젝트에 새로운 색 중단점(color stop)을 생성
    • position은 0.0에서 1.0 사이의 숫자이고 그라디언트에서 색상의 상대적인 위치를 정의
    • color 인자는 CSS <color>를 나타내는 문자열이어야하고, 그라디언트가 (전환효과로) 진행되면서 도달한 지점의 색상을 의미

색 중단점은 원하는 만큼 마음대로 추가할 수 있다
흰 색에서 검은 색으로 변하는 선형 그레이디언트를 만들려면 아래와 같이 한다.

var lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
lineargradient.addColorStop(0, 'white');
lineargradient.addColorStop(1, 'black');

createLinearGradient 예제

 const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    if(canvas.getContext){
    
            draw();
    }

   function draw(){
        //그레디언트 생성, 이 시작과 종료에 따라 방향을 결정한다.
        let lingrad = ctx.createLinearGradient(0,10,0,150);
        //중단점의 상대위치와 색상설정
        lingrad.addColorStop(0, '#00abeb');
        lingrad.addColorStop(0.5, '#fff');
        lingrad.addColorStop(0.8, '#26c000');
        lingrad.addColorStop(1, '#fff');
        
        let lingrad2 = ctx.createLinearGradient(0,50,0,95);

        lingrad2.addColorStop(0.5,'#fff');
        lingrad2.addColorStop(1,'rgba(0,0,0,1)');

        ctx.fillStyle = lingrad;
        ctx.strokeStyle = lingrad2;

        ctx.fillRect(10,10,130,130);
        ctx.strokeRect(50,50,50,50)
   }

createRadialGradient 예제

아래의 원형 그레디언트 만들기를 움직이는 원형 그레디언트로 바꾸었다.

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    let x = 0; 
    let y = 0;

    if(canvas.getContext){
        function move(){
            draw();
            setTimeout(move,10);
        }
        move();
    }

   function draw(){
       x++; 
       y++;
       //그라디언트로 색상을 칠한다고 생각해야함. 
       const redgrad = ctx.createRadialGradient(45,y,10,52,y,30);
       //0은 중심에서 1은 외곽이다.
       redgrad.addColorStop(0,'#a7d30d');
       redgrad.addColorStop(0.8,'#019f62');
       redgrad.addColorStop(1,'rgba(1,159,98,0)');

       const radgrad2 = ctx.createRadialGradient(x,y,20,x,y,50);
        radgrad2.addColorStop(0, '#FF5F98');
        radgrad2.addColorStop(0.75, '#FF0188');
        radgrad2.addColorStop(1, 'rgba(255,1,136,0)');

        const radgrad3 = ctx.createRadialGradient(x*2,y*2,15,x*2,y*2,40);
        radgrad3.addColorStop(0, '#00C9FF');
        radgrad3.addColorStop(0.8, '#00B5E2');
        radgrad3.addColorStop(1, 'rgba(0,201,255,0)');

        const radgrad4 = ctx.createRadialGradient(x,150,50,x,140,90);
        radgrad4.addColorStop(0, '#F4F201');
        radgrad4.addColorStop(0.8, '#E4C700');
        radgrad4.addColorStop(1, 'rgba(228,199,0,0)');

      
       //원이 더크기 때문에 잘려서 안보인다.
       ctx.fillStyle = radgrad4;
       ctx.clearRect(0,0,500,500)
       ctx.fillRect(0,0,500,500);
        ctx.fillStyle = radgrad3;
        ctx.fillRect(0,0,500,500);
        ctx.fillStyle = radgrad2;
        ctx.fillRect(0,0,500,500);
        ctx.fillStyle = redgrad;
       // 그레디언트보다 크게 설정해야 보인다. 
       ctx.fillRect(0,0,500,500);
       
   }
profile
배움은 끝없이

0개의 댓글