Interactiv Developer - Wave
Updated:
HTML5 Canvas Tutorial : 움직이는 웨이브 만들기
시작은 항상 html에서 시작한다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" type="text/css" src="style.css">
</head>
<body>
<script type="module" src="app.js"></script>
</body>
</html>
css파일은 엘리먼트의 넓이와 높이만 지정해주는 역할을 한다.
<!-- style.css -->
*{
user-select: none;
-ms-user-select: none;
outline: 0;
margin: 0;
padding: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html{
width: 100%;
height: 100%;
}
body{
width: 100%;
height: 100%;
overflow: hidden;
background-color: #ffffff;
}
canvas{
width: 100%;
height: 100%;
}
import{
WaveGroup
} from "./wavegroup.js";
class App{
constructor(){
this.canvas = document.createElement('canvas'); // canvas 생성
this.ctx = this.canvas.getContext('2d');
document.body.appendChild(this.canvas);
this.waveGroup = new waveGroup();
window.addEventListener('resize', this.resize.bind(this), false);
this.resize();
requestAnimationFrame(this.animate.bind(this)); // 애니메이션 시작
}
resize(){
this.stageWidth = document.body.clientWidth;
this.stageHeight = document.body.clientHeight;
this.canvas.width = this.stageWdith * 2;
this.canvas.height = this.stageHeight * 2;
this.ctx.scale(2, 2);
// 캔버스를 더블 사이즈로 지정해서 레티나 디스플레이에서도 잘 보일 수 있게 해준다.
this.waveGroup.resize(this.stageWidth, this.stageHeight);
}
animate(t){
this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight); // canvas 클리어
this.waveGroup.draw(this.ctx``);
``
requestAnimationFrame(this.animate.bind(this));
}
}
window.onload = () = > { // window가 로드되면 App을 생성
new App();
};
export class Point{
// wave를 그리기보단, 각각의 간격을 가진 좌표를 하나씩 만들어서
// 그 좌표 값의 Y값을 아래 위로 이동시키고 각각의 좌표를 하나의 선으로 연결하는 것이다.
constructor(x, y){
this.x = x;
this.y = y;
this.fixedY = y;
this.speed = 0.1;
this.cur = index; // point에 index 값을 넘겨줘서 현재 point가 몇번째 point인지 정의
this.max = Math.random() * 100 + 150; // 얼마만큼 움직일 것인가에 대한 max값
}
update(){
this.cur += this.speed; // 현재값을 speed만큼 증가
this.y = this.fixedY + (Math.sin(this.cur) * this.max); // sine 함수를 사용하여 아래위로 움직일 수 있도록 했다.
}
}
import{
Point
} from "./point.js";
export class Wave{
// point가 동시에 아래위로 움직이면 wave로 안보이고 하나의 선으로 보이기 때문의
// 고유의 index 넘버를 넘겨줘서 wave가 약간의 차이를 뒀다.
constructor(index, totalPoints, color){
this.index = index;
this.totalPoints = totalPoints;
this.color = color;
this.points = [];
}
resize(stageWidth, stageHeight){
// 애니메이션을 할 때 가장 중요한 것은 내가 그리고자 하는 애니메이션의 좌표값을 가지고 오는 것이다.
// 그러기 위해선 애니메이션의 크기를 알아야 한다. -> 스테이지의 넓이와 높이를 가져오는 게 중요
this.stageWidth = stageWidth;
this.stageHeight = stageHeight;
// wave는 화면의 중간에 그려질 것이기 때문에 centerX와 centerY를 정한다.
this.centerX = stageWidth / 2;
this.centerY = stageHeight / 2;
// point의 간격은 스테이지 넓이에서 totalPoints만큼을 나눈 값이다. -> pointGap(간격)
this.pointGap = this.stageWidth / (this.totalPoints - 1);
this.init();
}
init(){
// resize 이벤트에서 정의했던 centerX와 centerY를 넘겨줘서 각각의 포인트가
// 화면 중간에서 그려지도록 정의해준다.
this.points = [];
// 정해진 간격만큼 화면에 그려준다.
for(let i = 0; i < this.totalPoints; i++){
const point = new Point(
this.index + i,
this.pointGap * i,
this.centerY,
);
this.points[i] = point;
}
}
// 실제로 canvas를 그리는 함수
// update된 point 개수에 맞춰서 draw()함수도 업데이트
draw(ctx){
ctx.beginPath();
ctx.fillStyle = this.color; // wave의 color 값
// 처음 point와 마지막 point는 움직이지 않고, 가운데 point들만 아래위로 움직여서
// wave의 움직임을 만든다.
let prevX = this.points[0].x;
let prevY = this.points[0].y;
ctx.moveTo(prevX, prevY);
// index가 0이거나 tatl -1 즉, 마지막 index면 update 함수를 실행시키지 않고
// 그 외의 경우에만 update만 실행하여 아래 위로 움직임을 만든다.
for(let i = 1 ; i < this.totalPoints; i++){
if(i < this.totalPoinst - 1){
this.points[i].update();
}
const cx = (prevX + this.points[i].x) /2;
const cy = (prevY + this.points[i].y) /2;
// ctx.lineTo(cx, cy); // 직선으로 긋기
ctx.quadraticCurveTo(prevX, prevY, cx, cy); // 곡선
// 우리가 만들려는 것은 곡선형 wave이기 때문에 현재 point의 x, y 좌표를
// 그대로 적어주는 것이 아니라 이전 point의 x, y 좌표에 현재 point의 x, y 좌표를
// 반으로 나눈 값 즉, 그 중간값을 lineTo에 적어준다.
// 나중에 곡선 함수로 바꿨을 때 그렇게 하지 않고 곡선 함수를 사용하는데
// 이전값과 현재값을 그대로 적게 되면 곡선이 아니라 직선이 그려지기 때문에
// 이전값과 현재값의 중간 포인트로 잡아서 부드러운 곡선이 되게 한다.
prevX = this.points[i].x;
prevY = this.points[i].y;
}
ctx.lineTo(prevX, prevY);
ctx.lineTo(this.stageWidth, this.stageHeight);
ctx.lineTo(this.points[0].x, this.stageHeight);
ctx.fill();
ctx.closePath();
}
}
import{
Wave
} from './wave.js';
export class WaveGroup{
constructor(){
this.totalWaves = 3; // total wave 수
this.totalPoints = 6; // total point 수
this.color = ['rgba(0, 199, 235, 0.4)', 'rgba(0, 146, 199, 0.4)', 'rgba(0, 87, 158, 0.4)']; // 각각의 wave에 다른 색을 넣기 위해 정의
this.waves = [];
for(let i = 0; i < this.totalWaves; i++){
const wave = new Wave(
i,
this.totalPoints,
this.color[i],
);
this.waves[i] = wave;
}
}
// 각각의 함수가 실행이 되면 WaveGroup 안에 있는 total wave 수 만큼 함수를 실행시켜준다.
resize(stageWidth, stageHeight){
for(let i = 0; i < this.totalWaves; i++){
const wave = this.wave[i];
wave.resize(stageWidth, stageHeight);
}
}
draw(ctx){
for(let i = 0; i < this.totalWaves; i++){
const wave = this.waves[i];
wave.draw(ctx);
}
}
}
Leave a comment