[인터랙티브 디벨로퍼] Moving Gradient 코드 분석

Grace Nho
22 min readMay 1, 2021

--

김종민 인터랙티브 디벨롭퍼의 무빙 그라디언트 코드 분석을 들어가겠슴당

​영상에서 보듯이 4개의 파일을 만든다

첫번째 분석할 파일은 index.html

제일 쉽기 때문 ^^

<!DOCTYPE HTML> <html lang=”en”> <head> <meta charset=”UTF-8"> <meta http-equiv = “X-UA-Compatible” content=”IE=edge, chrome=1"/> <meta name=”viewport” content=”width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> <title></title> <link rel=”stylesheet” href=”style.css”> </head> <body> <script type=”module” src=”./app.js”></script> </body> </html>

여기서 특별히 주목해야하는 것은 딱 두가지인데

link rel = “stylesheet” href = “style.css 와 script type=”module” src=”./app.js” 이다

첫번째 코드는 style.css 이라는 css 파일을 적용시키겠다는 의미이고

두번째는 자바스크립트 모듈을 페이지에 로딩하게 해주는 코드인데 여기서는 app.js 라는 자바스크립트 파일을 적용 시킴

자바스크립트 모듈이란? (script type =”module”)

자바스크립트 파일이라고 생각하면 된다. 모듈들을 서로 다른 파일들, 다른 모듈들을 로딩을 시킬 수 있고

export, import 과 같은 코드를 사용해서 다른 모듈이 자신의 코드를 사용할 수 있게 또는 다른 모듈의 코드를 빌려와 쓸 수 있게 해준다.

두번째 분석할 파일은 style.css

그 다음으로 쉽기 때문 .. ^^

*{ outline:0; margin: 0; padding: 0; } html{ width:100%; height:100%; } body{ width: 100%; height: 100%; background-color: #ffffff; } canvas{ width:100%; height:100%; }

* : 모든 element 들을 선택한다는 의미

html 파일의 모든 요소들의 margin, padding 그리고 outline 값을 0으로 세팅하겠다는 의미.

html 파일의 크기는 윈도우 크기와 넓이의 100%,

body 도 마찬가지로, 근데 여기서 바디의 배경색깔은 흰색으로 지정

canvas 는 캔버스의 의미로 여기에 그림 그릴 수 있음. 그래픽을 이 위에 얹을 수 있다는 의미.

canvas 의 크기도 width, height 100%

// style.css의 코드가 이렇게 간략할줄은 몰랐다.

세번째 분석할 파일은 app.js

import { GlowParticle } from “./glowparticle.js”; const COLORS = [ {r:45, g:74, b:227}, //blue {r:250, g:255, b:89}, //yellow {r:255, g:104, b: 248}, //purple {r:44, g:209, b:252}, //skyblue {r:54, g:233, b:84}, //green ]; class App { constructor(){ this.canvas = document.createElement(‘canvas’); document.body.appendChild(this.canvas); this.ctx= this.canvas.getContext(‘2d’); this.pixelRatio = (window.devicePixelRatio > 1) ? 2 : 1; this.totalParticles = 15; this.particles= []; this.maxRadius = 900; this.minRadius = 400; window.addEventListener(‘resize’, this.resize.bind(this), false); this.resize(); window.requestAnimationFrame(this.animate.bind(this)); } resize(){ this.stageWidth = document.body.clientWidth; this.stageHeight = document.body.clientHeight; this.canvas.width = this.stageWidth * this.pixelRatio; this.canvas.height = this.stageHeight * this.pixelRatio; this.ctx.scale(this.pixelRatio, this.pixelRatio); this.ctx.globalCompositeOperation = ‘saturation’; this.createParticles(); } createParticles(){ let curColor = 0; this.particles = []; for (let i =0; i<this.totalParticles; i++){ const item = new GlowParticle( Math.random() * this.stageWidth, Math.random() * this.stageHeight, Math.random() * (this.maxRadius — this.minRadius) + this.minRadius, COLORS[curColor] ); if (++curColor >= COLORS.length){ curColor =0; } this.particles[i] = item; } } animate(){ window.requestAnimationFrame(this.animate.bind(this)); this.ctx.clearRect(0,0, this.stageWidth, this.stageHeight); for (let i=0; i<this.totalParticles; i++) { const item = this.particles[i]; item.animate(this.ctx, this.stageWidth, this.stageHeight); } } } window.onload = () => { new App(); }

전체 코드

import { GlowParticle } from “./glowparticle.js”; const COLORS = [ {r:45, g:74, b:227}, //blue {r:250, g:255, b:89}, //yellow {r:255, g:104, b: 248}, //purple {r:44, g:209, b:252}, //skyblue {r:54, g:233, b:84}, //green ];

glowparticle.js 의 코드에서 GlowParticle 라는 클라스의 코드를 이용하겠다는 의미.

const COLORS 는 여러개의 옵젝트를 담은 어레이.

옵젝트의 name 과 value 들을 살펴 보면 rgb 의 값들을 담았다.

class App { constructor(){ this.canvas = document.createElement(‘canvas’); document.body.appendChild(this.canvas); this.ctx= this.canvas.getContext(‘2d’); this.pixelRatio = (window.devicePixelRatio > 1) ? 2 : 1; this.totalParticles = 15; this.particles= []; this.maxRadius = 900; this.minRadius = 400; window.addEventListener(‘resize’, this.resize.bind(this), false); this.resize(); window.requestAnimationFrame(this.animate.bind(this)); }

앱이라는 클라스가 있는데

이 클라스의 코드 일부분만 뽑았음

constructor() 메소드는 해당 클라스가 생성될때 다른 옵젝트를 생성하고 initialize 해줌.

음, 쉽게 생각하자면 클라스가 생성될 때 그냥 자동으로 실행되는 메소드?

일단 캔버스를 하나 만들어준다 .

그리고 이 캔버스를 html body 에 append, 추가해줌

ctx 는 컨텍스트의 줄인말

캔버스의 컨텍스트는 캔버스 element 안에 사용할 수 있는 여러 메소드와 properties 들을 제공함. 이 메소드와 properties 로 그래픽스를 ㄹ렌더링 할 수 있다. 2d 혹은 webgl 3d 의 값을 가질 수 있는데 하나의 캔버스에는 하나의 컨텍스트를 가질 수 있다.

우리는 2d를 다룰거기 때문에 ctx를 2d 값으로 세팅해준다

this.pixelRatio = (window.devicePixelRatio > 1) ? 2 : 1;

이 코드는 윈도우의 devicePixelRatio , 즉 physical pixel resolution : CSS pixel resolution 의 비율을 나타낸 값,

이 window.devicePixelRatio 의 값이 1 보다 크면 pixel 값은 2,

작으면 pixel 값은 1을 가지게 된다.

totalParticles 는 15개

totalParticles 는 이 영상에서 볼 수 있듯이, 저 공의 개수를 말하는 듯 하다 .

this.maxRadius 그리고 this.minRadius 는 이 particle 의 반지름 최대 최소 값을 말한다

window.addEventListener(‘resize’, this.resize.bind(this), false);

document.addEventListener(event, function, useCapture)

여기서 event 는 ‘resize’ 즉, 사용자가 윈도우 화면의 크기를 바꾸면

function은 resize.bind 라는 함수를 부르고

useCapture 는 boolean 값을 담는데, true : capturing phase 때 event handler 가 실행 된다.

false: default 값, bubbling phase 때 event handler가 실행 된다.

Capturing phase — the event goes down to the element.

Target phase — the event reached the target element.

Bubbling phase — the event bubbles up from the element.

capturing phase 는 거의 사용 안됨

this.resize 함수를 부른다

커스텀 resize 함수의 코드는 이러하다

resize(){ this.stageWidth = document.body.clientWidth; this.stageHeight = document.body.clientHeight; this.canvas.width = this.stageWidth * this.pixelRatio; this.canvas.height = this.stageHeight * this.pixelRatio; this.ctx.scale(this.pixelRatio, this.pixelRatio); this.ctx.globalCompositeOperation = ‘saturation’; this.createParticles(); }

clientHeight 는 css height + css padding 을 더한거고

clientWidth 는 viewport 의 넓이, scrollbar 제외

stageWidth 와 stageHeight 는 사용자의 윈도우 창의 넓이와 높이를 의미한다.

canvas 의 높이와 넓이는 stageWidth/stageHeight 와 pixelRatio 의 값을 곱해준 값

//왜 그런지는 이해가 잘 안된다..

context.scale(scalewidth,scaleheight);

ctx.scale 는 pixelRatio 만큼 scale 해라는 뜻

scale 은 크기 조절하는 메소드라고 생각하면 됨

그러고 나서 createParticles 라는 메소드를 부른다

createParticles(){ let curColor = 0; this.particles = []; for (let i =0; i<this.totalParticles; i++){ const item = new GlowParticle( Math.random() * this.stageWidth, Math.random() * this.stageHeight, Math.random() * (this.maxRadius — this.minRadius) + this.minRadius, COLORS[curColor] ); if (++curColor >= COLORS.length){ curColor =0; } this.particles[i] = item; } }

createParticles는

curColor의 값을 일단 0으로 지정

particle 의 개수만큼 for 문을 돌린다.

for 문의 body 에는

일단 새로운 “GlowParticle” 를 만든다.

**얘는 난중에 다룰 예정**

그리고 그걸 item 이라는 변수에 저장

GlowParticles 는 4개의 값이 필요하는데, (x,y, radius, rgb)

x 와 y 는 캔버스의 위치를 의미하고

radius 는 particle 의 반지름 값

rgb 는 색깔을 의미

Math.random 은 0~1 사이의 랜덤한 값을 반환해주는데

stageWidth 와 stageHeight 를 곱해주면, 무조건 캔버스 안에 particle가 위치 되어있겠죠..?

색깔은 일단 COLORs 의 어레이에 뽑아서 주는데 , 처음에 curColor 의 값이 0 이니 첫번째 색깔인 파랑색을 뽑아옴!

그 다음에 if 문에 들어가는데

if (++curColor >= COLORS.length){ curColor =0; }

curColor 의 값에 1을 더해줌.

그러고 나서 COLORS 의 어레이의 길이 보다 크거나 같은지 비교를 하는데, 크거나 같으면, 0의 값을 둠

왜 크거나 같으면 다시 reset 하냐면 curColor 가 COLORS 어레이의 인덱스 값인데

인덱스 값은 어레이의 길이와 같거나 크면 안됨.

그러고 나서 item을 particles 어레이에 집어넣음

자 그럼 다시 constructor() 코드로 돌아가면

window.requestAnimationFrame(this.animate.bind(this));

브라우저에게 애니메이션을 실행하고 싶다는 request 를 전달하고

repaint 하기 전에 requestAnimationFrame 에 있는 함수를 호출함

animate(){ window.requestAnimationFrame(this.animate.bind(this)); this.ctx.clearRect(0,0, this.stageWidth, this.stageHeight); for (let i=0; i<this.totalParticles; i++) { const item = this.particles[i]; item.animate(this.ctx, this.stageWidth, this.stageHeight); } }

app.js 안에 있는 animate 함수인데

requestAnimationFrame 을 하고

컨텍스트 안에 rectangle 을 세팅시킨다. 그니까 캔버스를 리셋 하는 느낌?

그리고 나서 totalParticle 의 수 만큼 아이템을 animate 한다.

이 animate 는 GlowParticle 의 파일에 있는 animate 함수를 가르키는거니까 헷갈리면 안됨.

마지막으로 분석할 파일은 glowparticle.js

const PI2 = Math.PI * 2; export class GlowParticle{ constructor(x,y,radius,rgb){ this.x =x; this.y=y; this.radius = radius; this.rgb = rgb; this.vx = Math.random() * 4; this.vy = Math.random() * 4; this.sinValue = Math.random(); } animate(ctx, stageWidth, stageHeight){ this.sinValue += 0.01; this.radius += Math.sin(this.sinValue); this.x += this.vx; this.y += this.vy; if (this.x <0){ this.vx *= -1; this.x += 10; } else if (this.x > stageWidth){ this.vx *= -1; this.x -=10; } if (this.y <0){ this.vy *= -1; this.y += 10; } else if (this.y > stageHeight){ this.vy *= -1; this.y -=10; } ctx.beginPath(); const g = ctx.createRadialGradient(this.x, this.y, this.radius * 0.01, this.x, this.y, this.radius); g.addColorStop(0, `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, 1)`); g.addColorStop(1, `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, 0)`); ctx.fillStyle = g; ctx.arc(this.x, this.y, this.radius, 0, PI2, false); ctx.fill(); } }

이게 전체 코드

자 constructor 는 GlowParticle 이라는 클라스를 생성할 때 값을 initialize 해줘야하는데

constructor(x,y,radius,rgb){ this.x =x; this.y=y; this.radius = radius; this.rgb = rgb; this.vx = Math.random() * 4; this.vy = Math.random() * 4; this.sinValue = Math.random(); }

x,y,radius,rgb 는 앞서 설명한 내용을 참고하면 될 것 같고, 그 값들을 this, 즉 이 클라스에 속한 Properties: x,y,radius,rgb 에 저장해두고.

vx 와 vy 는 4라는 값에 Math.random() 숫자를 곱한 값을 저장하는데

나의 추측으로는 vx, vy 뜻이 vector x, vector y

벡터는 magnitude 와 direction 을 가지고 있으니까 vx 와 vy 는 어떤 particle 의 x,y 속력과 방향을 조절해주는 값인 것 같음

같이 스터디하는 친구 말로는 저 4의 값을 바꾸면 속력이 빨라지거나 느려지거나 한다고 함 !

sinValue 에는 0~1 사이의 숫자 아무거나 랜덤하게 집어넣는데

animate 에서 어떻게 사용되는지 한번 살펴보자

animate(ctx, stageWidth, stageHeight){ this.sinValue += 0.01; this.radius += Math.sin(this.sinValue); this.x += this.vx; this.y += this.vy; if (this.x <0){ this.vx *= -1; this.x += 10; } else if (this.x > stageWidth){ this.vx *= -1; this.x -=10; } if (this.y <0){ this.vy *= -1; this.y += 10; } else if (this.y > stageHeight){ this.vy *= -1; this.y -=10; } ctx.beginPath(); const g = ctx.createRadialGradient(this.x, this.y, this.radius * 0.01, this.x, this.y, this.radius); g.addColorStop(0, `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, 1)`); g.addColorStop(1, `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, 0)`); ctx.fillStyle = g; ctx.arc(this.x, this.y, this.radius, 0, PI2, false); ctx.fill(); } }

sinValue 에 0.01을 더해주고

이 숫자에 Math.sin 함수를 적용함

(이제 왜 변수이름이 sinValue인지 알겠음..)

그리고 이걸 반지름값에 더해주네..?

그리고 나서 x 와 y 에 vx 값을 더해줌.

x 와 y 가 GlowParticle 의 위치를 저장해놓는 값일테니까

위치를 변화시켜주는거 보면 vx와 vy 는 vector x 와 vector y 가 맞군.

암튼 숫자 더해서 x,y 좌표를 바꿔주고

x 와 y 값을 확인하는거죵

if (this.x <0){ this.vx *= -1; this.x += 10; } else if (this.x > stageWidth){ this.vx *= -1; this.x -=10; }

x 가 0보다 작으면,즉 glowParticle 의 위치가 맨 왼쪽에 위치해있으면, 다른 방향으로 다시 보내야하니

vx 를 이제 양수로 바꿔주기 위해 -1을 해줘서 반대 방향으로 튕기는 것처럼,, 하면 되고

x 의 좌표는 +10 해주고

x가 stageWidth 만큼, 그니까 캔버스의 다른 쪽 끝?에 있으면, vx를 음수로 바꿔줘야하니 -1을 곱해줘서 또 반대 방향으로 튕기는 것처럼 하면 된다.

y도 마찬가지이지만 y방향을 기준으로 생각하면 됨

캔버스의 beginPath 메소드는 현재 있는 path 를 resest 하거나 path 를 새로 만든다

보통 그라디언트는 두 종류로 나뉜다

1. Linear Gradient

2. Radial Gradient

우리는 원형 particle 을 다루니 오른쪽을 사용하는게 좋겠죠?

그래서 createRadialGradient 는 이 원형그라디언트를 만들어주는데 필요한 파라미터가 6가지 ,

JavaScript syntax:

context.createRadialGradient(x0,y0,r0,x1,y1,r1);

x0, y0 r0

는 starting circle 의 x,y 좌표와 starting circle 의 반지름

x1,y1,r1 은

ending circle 의 x,y, 좌표와 반지름을 의미함

이 코드에서는 starting Circle 의 반지름을 particle 의 반지름의 0.01 배 로 정했음

g 라는 그라디언트에 colorStop 을 추가해줄건데

addColorStop(offset, color);

colorStop 은 두가지 파라미터를 받아드림

첫번째는 offset. 그니까 위치?라고 생각하면됨.

color 는 당연히 색깔을 의미함

예시를 들자면

g.addColorStop(0, `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, 1)`); g.addColorStop(1, `rgba(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b}, 0)`);

이런식으로 color Stop 을 추가함

여기서는 colorStop 을 두개추가하는데

색깔은 동일하지만, ${this.rgb.b} 옆에있는 파라미터 값이 다르다.

이 값은 알파라는 값인데 (그래서 rgba 의 뜻이 red-green-blue-alpha) opacity 를 정한다

1이면 색깔이 더 쨍하고 0이면 투명에더 가까워짐

이 colorStop 이 살짝 블러?처리하는거라고 생각하면 될 것 같은데

이렇게 colorStop 을 추가한 후 glowParticle 의 컨텍스트의 fillStyle 값을 g 로 지정하는데

그러니까 glowParticle 의 스타일, 색깔을 우리가 만들어준 radial gradient 으로 채워넣겠다는건데

아직 채워넣는 단계는 아님

그냥 페인트를 준비했다 라고 생각하면 됨.

페인트를 준비했으면 페인트를 덮을 도형?이 필요하는데 그게 다음 함수가 하는 역할임

마지막이 ctx.arc 함수인데 이 함수는 호를 그려줌

x,y, 가 원이 위치한 지점

r 가 원의 반지름

sAngle 이 시작 각도

eAngle 이 끝나는 각도

counterClockwise 는 반대로 돌릴지 (기본값은 false로 세팅 되어있음) 결정하는 값.

JavaScript syntax:

context.arc(x,y,r,sAngle,eAngle,counterclockwise);

근데 보면 우리는 sAngle 이 0

eAngle 이 PI2 즉 360도 (180*2) 여서 그냥 원을 그리겠다는 의미

원을 그리고 나서 ctx.fill 로 우리가 채워넣은 페인트, 즉 radial gradient 로 이 glow particle 의 스타일을 바꿔줌.

--

--

No responses yet