개인 노트 정리) Canvas - 1-07. dat GUI 활용하기

2023. 6. 1. 19:13개인노트-인강

인터랙티브 웹 개발 Canvas 인강 정리

 

# dat GUI 활용하기

- 수기로 하나하나 값을 변경해가며 마음에 드는 수치를 찾지 못하는 단점을 보완하기 위해서, dat GUI 라이브러리를 사용하여 값을 쉽게 테스트할 수 있는 환경을 구성해보겠습니다.

- dat GUI 를 통하여 SVG Blur 값과 alpha, contrast 값 뿐만 아니라 엑셀러레이션 값도 함께 바꿔주면서 우리가 원하는 이상적인 애니메이션 효과를 함께 찾아보도록 하겠습니다.

 

## cdn을 이용하여 라이브러리 가져오기

1. https://cdnjs.com/libraries/dat-gui

2. 최신버전 & min 버전으로 가져와서 html 상단 head 태그 안에 넣기.

3. index.js에서 dat.GUI 가져오기.

 

여기까지하면 html 화면에 작은 패널이 한 개 생깁니다.

index.html

<!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" />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.9/dat.gui.min.js"
      integrity="sha512-WoO4Ih0CDOSLYafy22wZD/mcJ7k0ESLqtQsFa6zFKnEUrbtuGU+GkLtVhgt93xa2qewG5gKEC6CWlN8OaCTSVg=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    ></script>
    <title>Document</title>
    <style>
      html,
      body {
        margin: 0;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      canvas {
        filter: url("#gooey");
        /* filter: blur(10px) contrast(10);
        background-color: #f1f1f1; */
      }
    </style>
  </head>
...

index.js

// ...
ctx.scale(dpr, dpr); // 3. 그리려는 객체에도 가로와 세로에 dpr값을 각각 곱해줍니다.



// 1. datGUI 컨트롤 패널안에서 변화를 주기 위한 값들을 먼저 정의를 해주어야합니다.
// 함수의 인스턴스 값에 담아서 만들어주도록 합니다.
const controls = new (function () {
  // dat GUI 메소드에서 사용되어야할 값들을 여기서 this로 가져오기 때문에 변수명 앞에 this를 붙입니다.
  this.blurValue = 40;
  this.alphaChannel = 100;
  this.alphaOffset = -23;
})();

// 2. datGUI 가져오기
let gui = new dat.GUI();



class Particle {
// ...

 

## datGUI 패널에 옵션 추가하기

화면에 생긴 패널 안에 옵션을 추가하려면 index.js에 add로 추가해 줄 수 있습니다.

const feGaussianBlur = document.querySelector("feGaussianBlur");

// 1. datGUI 컨트롤 패널안에서 변화를 주기 위한 값들을 먼저 정의를 해주어야합니다.
// 함수의 인스턴스 값에 담아서 만들어주도록 합니다.
const controls = new (function () {
  // dat GUI 메소드에서 사용되어야할 값들을 여기서 this로 가져오기 때문에 변수명 앞에 this를 붙입니다.
  this.blurValue = 40;
  this.alphaChannel = 100;
  this.alphaOffset = -23;
})();

// 2. datGUI 가져오기
let gui = new dat.GUI();

// 첫번째 인자 : controls
// 두번째 인자 : controls에서 사용한 변수 이름의 string 형태
// 세번째 인자 : 최소값 범위
// 네번째 인자 : 최대값 범위
// onChange : 값이 바뀌었을 때 어떻게 동작시킬지 정의
// value : 바뀐 값을 return 해줍니다.
gui.add(controls, "blurValue", 0, 100).onChange((value) => {
  feGaussianBlur.setAttribute("stdDeviation", value);
});

Blur 값이 간단하게 Test 될 수 있는 환경이 되었습니다.

 

## Alpha 채널 바꾸기

// ...
const feColorMatrix = document.querySelector("feColorMatrix");

// ...

gui.add(controls, 'alphaChannel', 1, 200).onChange(value => {
  feColorMatrix.setAttribute('values', `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${value} 0`);
});

// ...

 

## Alpha offset 바꾸기

// ...
gui.add(controls, "alphaOffset", -40, 40).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 ${value}`
  );
});
// ...

 

## 여기까지 주의할 점

values라는 하나의 attribute 안에서 두 값을 동시에 바꾸고 있기 때문에 아래처럼 하게 되면..

// 잘못된 동작
gui.add(controls, "alphaChannel", 1, 200).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${value} 0`
  );
});
gui.add(controls, "alphaOffset", -40, 40).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 ${value}`
  );
});

한 값은 다시 원래의 값으로 초기화가 되어버립니다.

그래서 아래처럼 바꿔주면..

gui.add(controls, "alphaChannel", 1, 200).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${value} ${controls.alphaOffset}`
  );
});
gui.add(controls, "alphaOffset", -40, 40).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${controls.alphaChannel} ${value}`
  );
});

 

여기까지 gooey 관련된 변수들을 테스트를 해봤습니다.

 

## js안의 다른 값들도 바꿔보기

이제 파티클 class 내의 자체적인 속성값들도 바꿔보겠습니다.

class Particle {
  constructor(x, y, radius, vy) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.vy = vy;
    this.acc = 1.03;
  }
  update() {
    this.vy *= this.acc;
    this.y += this.vy;
  }
  // ...

예를 들어 this.acc = 1.03; 을 바꿔보겠습니다.

 

1. 먼저 controls 안에 원래 쓰던 값을 담아줍니다.

const controls = new (function () {
  // dat GUI 메소드에서 사용되어야할 값들을 여기서 this로 가져오기 때문에 변수명 앞에 this를 붙입니다.
  this.blurValue = 40;
  this.alphaChannel = 100;
  this.alphaOffset = -23;
  this.acc = 1.03;
})();

 

2. gui로 정의.

// 5번째 인자는 소수점 두번째 자리까지 출력하기 때문에 0.01로 세팅
gui.add(controls, 'acc', 1, 1.5, 0.01).onChange(value => {
  //현재 생성되어 있는 파티클들 모두 값을 세팅해주어야 하기 때문에 particle들을 foreach로 돌아야 합니다.
  particles.forEach(particle => particle.acc = value)
});

 

## 폴더로 관련있는 컨트롤끼리 나누기

위 3개는 gooey 관련 패널입니다. 폴더로 관련있는 컨트롤끼리 묶을 수 있습니다.

gui.addfolder 메소드를 사용하면 됩니다.

// ...

let gui = new dat.GUI();

const f1 = gui.addFolder('gooey Effect');

// gui 대신에 f1로 바꿔줍니다.
f1.add(controls, "blurValue", 0, 100).onChange((value) => {
  feGaussianBlur.setAttribute("stdDeviation", value);
});
f1.add(controls, "alphaChannel", 1, 200).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${value} ${controls.alphaOffset}`
  );
});
f1.add(controls, "alphaOffset", -40, 40).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${controls.alphaChannel} ${value}`
  );
});

const f2 = gui.addFolder("Particle Property");

f2.add(controls, "acc", 1, 1.5, 0.01).onChange((value) => {
  particles.forEach((particle) => (particle.acc = value));
});

// ...

결과

 

폴더가 열린채로 화면에 보이게 하려면 아래처럼 open() 메소드를 사용합니다.

const f1 = gui.addFolder("gooey Effect");
f1.open();

 

## 공부한 코드(전체)

index.js

const canvas = document.querySelector("canvas");

const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio; // 1. devicePixelRatio 값을 구한 뒤,

// const canvasWidth = 300;
// const canvasHeight = 300;
const canvasWidth = innerWidth;
const canvasHeight = innerHeight;

canvas.style.width = canvasWidth + "px";
canvas.style.height = canvasHeight + "px";

// 2. dpr 값을 canvas의 width와 height에 곱해줍니다.
canvas.width = canvasWidth * dpr;
canvas.height = canvasHeight * dpr;

ctx.scale(dpr, dpr); // 3. 그리려는 객체에도 가로와 세로에 dpr값을 각각 곱해줍니다.

// ctx.fillRect(10, 10, 50, 50);

/*
ctx.beginPath();
ctx.arc(100, 100, 50, 0, (Math.PI / 180) * 360);
ctx.fillStyle = "red";
ctx.fill(); // 안에 색상을 채워줍니다.
// ctx.stroke(); // 선을 그립니다.
ctx.closePath(); // 100, 100 위치에 반지름이 50인 원이 그려지게 됩니다.
*/

const feGaussianBlur = document.querySelector("feGaussianBlur");
const feColorMatrix = document.querySelector("feColorMatrix");

// 1. datGUI 컨트롤 패널안에서 변화를 주기 위한 값들을 먼저 정의를 해주어야합니다.
// 함수의 인스턴스 값에 담아서 만들어주도록 합니다.
const controls = new (function () {
  // dat GUI 메소드에서 사용되어야할 값들을 여기서 this로 가져오기 때문에 변수명 앞에 this를 붙입니다.
  this.blurValue = 40;
  this.alphaChannel = 100;
  this.alphaOffset = -23;
  this.acc = 1.03;
})();

// 2. datGUI 가져오기
let gui = new dat.GUI();

const f1 = gui.addFolder("gooey Effect");
f1.open();
// 첫번째 인자 : controls
// 두번째 인자 : controls에서 사용한 변수 이름의 string 형태
// 세번째 인자 : 최소값 범위
// 네번째 인자 : 최대값 범위
// onChange : 값이 바뀌었을 때 어떻게 동작시킬지 정의
// value : 바뀐 값을 return 해줍니다.
f1.add(controls, "blurValue", 0, 100).onChange((value) => {
  feGaussianBlur.setAttribute("stdDeviation", value);
});
f1.add(controls, "alphaChannel", 1, 200).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${value} ${controls.alphaOffset}`
  );
});
f1.add(controls, "alphaOffset", -40, 40).onChange((value) => {
  feColorMatrix.setAttribute(
    "values",
    `1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${controls.alphaChannel} ${value}`
  );
});
const f2 = gui.addFolder("Particle Property");
// 5번째 인자는 소수점 두번째 자리까지 출력하기 때문에 0.01로 세팅
f2.add(controls, "acc", 1, 1.5, 0.01).onChange((value) => {
  // 현재 생성되어 있는 파티클들 모두 값을 세팅해주어야 하기 때문에 particle들을 foreach로 돌아야 합니다.
  particles.forEach((particle) => (particle.acc = value));
});

class Particle {
  constructor(x, y, radius, vy) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.vy = vy;
    this.acc = 1.03;
  }
  update() {
    this.vy *= this.acc;
    this.y += this.vy;
  }
  draw() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, (Math.PI / 180) * 360);
    ctx.fillStyle = "orange";
    ctx.fill();
    ctx.closePath();
  }
}

const TOTAL = 20;
const randomNumBetween = (min, max) => {
  return Math.random() * (max - min + 1) + min;
};
let particles = [];

for (let i = 0; i < TOTAL; i++) {
  const x = randomNumBetween(0, canvasWidth);
  const y = randomNumBetween(0, canvasHeight);
  const radius = randomNumBetween(50, 100);
  const vy = randomNumBetween(1, 5);
  const particle = new Particle(x, y, radius, vy);
  particles.push(particle);
}

console.log(particles);

// 1초에 60frame으로 동작
let interval = 1000 / 60;
let now, delta;
let then = Date.now();

function animate() {
  window.requestAnimationFrame(animate);
  now = Date.now();
  delta = now - then;

  if (delta < interval) return;

  ctx.clearRect(0, 0, canvasWidth, canvasHeight);

  particles.forEach((particle) => {
    particle.update();
    particle.draw();

    if (particle.y - particle.radius > canvasHeight) {
      particle.y = -particle.radius;
      particle.x = randomNumBetween(0, canvasWidth);
      particle.radius = randomNumBetween(50, 100);
      particle.vy = randomNumBetween(1, 5);
    }
  });

  then = now - (delta % interval);
}

animate();