이번 글에서 다룰 내용
- clip 사용하기
- 캔버스에 이미지 그리기
- 공에 이미지 넣기
- clip된 그림에 애니메이션 적용
- 반복 및 랜덤 함수를 통해 공 여러개 생성
//app.js
class App{
constructor() {
...
this.createBall();
this.animate();
}
createBall() {
this.ball = new Ball();
}
animate() {
this.ball.draw(this.ctx);
}
}
//ball.js
export default class Ball {
draw(ctx) {
// 핑크색 공
ctx.beginPath();
ctx.arc(400, 400, 100, 0, 2 * Math.PI);
ctx.fillStyle = '#faa2c1';
ctx.fill();
ctx.closePath();
// 보라색 상자
ctx.beginPath();
ctx.rect(375, 250, 50, 300);
ctx.fillStyle = '#b197fc';
ctx.fill();
}
}
//ball.js
//핑크색 공
this.ctx.clip()
//보라색 상자
//app.js
...
createBall() {
this.ball = new Ball('image path');
}
animate() {
this.ball.draw(this.ctx);
}
이미지 그리는 방법
//ball.js
export default class Ball {
constructor(src) {
this.img = new Image();
this.src = src;
}
draw(ctx) {
this.img.onload = () => {
ctx.drawImage(this.img, 200, 200, 60, 60);
};
this.img.src = this.src;
}
}
img.addEventListener('load', () => {
ctx.drawImage(img, x, y, width, height);
})
ctx.arc(center_x, center_y, radius, startAngle, endAngle);
ctx.drawImage(img, x, y, width, height);
x + width / 2
, y + height / 2
를 원의 중심 좌표로 설정//ball.js
export default class Ball {
constructor(src) {
this.img = new Image();
this.src = src;
}
draw(ctx) {
//원(200 + 60/2, 200 + 60/2)
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.arc(230, 230, 30, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.clip();
this.img.addEventListener('load', () => {
ctx.drawImage(this.img, 200, 200, 60, 60);
});
this.img.src = this.src;
}
}
draw(ctx){
...
ctx.shadowColor = '#dee2e6';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 3;
}
//ball.js
export default class Ball{
constructor(stageWidth, stageHeight, radius, speed, src) {
this.img = new Image();
this.radius = radius;
this.diameter = this.radius * 2;
this.vx = speed;
this.vy = speed;
this.src = src;
this.x = this.radius + Math.random() * (stageWidth - this.diameter);
this.y = this.radius + Math.random() * (stageHeight - this.diameter);
}
}
//ball.js
export default class Ball {
...
draw(ctx, stageWidth, stageHeight) {
this.x += this.vx;
this.y += this.vy;
this.bounceWindow(stageWidth, stageHeight);
//원
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.shadowColor = '#dee2e6';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 3;
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
//클립
ctx.clip();
//이미지
this.img.addEventListener('load', () => {
ctx.drawImage(
this.img,
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
});
this.img.src = this.src;
}
bounceWindow(stageWidth, stageHeight) {
if (this.x <= this.radius || this.x >= stageWidth - this.radius) {
this.vx *= -1;
this.x += this.vx;
}
if (this.y <= this.radius || this.y >= stageHeight - this.radius) {
this.vy *= -1;
this.y += this.vy;
}
}
}
//app.js
...
createBall() {
this.ball = new Ball(
this.stageWidth,
this.stageHeight,
30,
20,
'./srcs/js.png'
);
}
animate() {
window.requestAnimationFrame(this.animate.bind(this));
this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);
this.ball.draw(this.ctx, this.stageWidth, this.stageHeight);
}
//ball.js
draw() {
...
this.img.addEventListener('load', () => {
ctx.drawImage(
this.img,
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
});
}
//ball.js
draw() {
...
this.img.addEventListener('load', () => {
ctx.drawImage(
this.img,
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
});
ctx.drawImage(
this.img,
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
}
//app.js
...
createBall() {
this.ball = new Ball(
this.stageWidth,
this.stageHeight,
300,
1,
'image path'
);
이미지만 혼자 움직이는 것을 확인할 수 있다
이를 해결하기 위해 save()와 restore()를 사용한다
save로 클립되기 전의 캔버스를 저장하고 그리기가 끝난 후 restore()를 이용해 저장했던 캔버스로 되돌려준다
//ball.js
...
draw(ctx, stageWidth, stageHeight) {
...
ctx.save();
//원그리기
//클립
//이미지 그리기
//이미지 소스
ctx.restore();
}
//consts.js
export const logos = [
'c',
'csharp',
'c++',
'java',
'js',
'mongoDB',
'MySQL',
'oracle3',
'php',
'PostgreSQL',
'python',
'r',
'SQLite',
'SQLServer',
'angular',
'backbone',
'django',
'ember',
'flask',
'laravel',
'node',
'preact',
'rails',
'react',
'spring',
'svelte',
'vue',
];
//app.js
import {logos} from './consts.js'; //로고 이름들이 담긴 배열 logos
class App {
constructor() {
...
this.balls = [];
}
createBall() {
//이미지 이름이 담긴 logos 배열
for (let i = 0; i < logos.length; i++) {
let radius = Math.ceil(Math.random() * 30) + 10;
let speed = Math.ceil(Math.random() * 30) + 10;
//logos의 이미지 이름으로
//Ball 인스턴스 생성
//this.balls에 담기
this.balls.push(
new Ball(
this.stageWidth,
this.stageHeight,
radius,
speed,
`path/${logos[i]}.png`
)
);
}
}
animate() {
window.requestAnimationFrame(this.animate.bind(this));
this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);
//this.balls 모든 공 그리기
this.balls.forEach(ball => {
ball.draw(this.ctx, this.stageWidth, this.stageHeight);
});
}
}
//app.js
createBall() {
for (let i = 0; i < logos.length; i++) {
let radius = Math.ceil(Math.random() * 30) + 10;
let speedX = Math.ceil(Math.random() * 50) + 5;
let speedY = Math.ceil(Math.random() * 50) + 5;
let signX = speedX % 2 === 0 ? -1 : 1;
let signY = speedY % 2 === 0 ? -1 : 1;
this.balls.push(
new Ball(
this.stageWidth,
this.stageHeight,
radius,
speedX * signX,
speedY * signY,
`./srcs/${logos[i]}.png`
)
);
}
}
//ball.js
constructor(stageWidth, stageHeight, radius, speedX, speedY, src) {
...
this.vx = speedX;
this.vy = speedY;
...
}
//app.js
import Ball from './velogballs.js';
import {logos} from './consts.js';
class App {
constructor() {
this.canvas = document.createElement('canvas');
this.canvas.setAttribute('id', 'canvas');
this.ctx = this.canvas.getContext('2d');
document.body.appendChild(this.canvas);
window.addEventListener('resize', this.resize.bind(this), false);
this.resize();
this.balls = [];
this.createBall();
this.animate();
}
resize() {
this.stageWidth = document.body.clientWidth;
this.stageHeight = document.body.clientHeight;
this.canvas.width = this.stageWidth * 2;
this.canvas.height = this.stageHeight * 2;
this.ctx.scale(2, 2);
}
createBall() {
for (let i = 0; i < logos.length; i++) {
let radius = Math.ceil(Math.random() * 30) + 10;
let speedX = Math.ceil(Math.random() * 50) + 5;
let speedY = Math.ceil(Math.random() * 50) + 5;
let signX = speedX % 2 === 0 ? -1 : 1;
let signY = speedY % 2 === 0 ? -1 : 1;
this.balls.push(
new Ball(
this.stageWidth,
this.stageHeight,
radius,
speedX * signX,
speedY * signY,
`./srcs/${logos[i]}.png`
)
);
}
}
animate() {
window.requestAnimationFrame(this.animate.bind(this));
this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);
this.balls.forEach(ball => {
ball.draw(this.ctx, this.stageWidth, this.stageHeight);
})
}
}
new App();
//ball.js
export default class Ball {
constructor(stageWidth, stageHeight, radius, speedX, speedY, src) {
this.img = new Image();
this.radius = radius;
this.diameter = this.radius * 2;
this.vx = speedX;
this.vy = speedY;
this.src = src;
this.x = this.radius + Math.random() * (stageWidth - this.diameter);
this.y = this.radius + Math.random() * (stageHeight - this.diameter);
}
draw(ctx, stageWidth, stageHeight) {
this.x += this.vx;
this.y += this.vy;
this.bounceWindow(stageWidth, stageHeight);
ctx.save();
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.shadowColor = '#dee2e6';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 3;
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.clip();
ctx.drawImage(
this.img,
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
this.img.src = this.src;
ctx.restore();
}
bounceWindow(stageWidth, stageHeight) {
if (this.x <= this.radius || this.x >= stageWidth - this.radius) {
this.vx *= -1;
this.x += this.vx;
}
if (this.y <= this.radius || this.y >= stageHeight - this.radius) {
this.vy *= -1;
this.y += this.vy;
}
}
}
//consts.js
export const logos = [
'c',
'csharp',
'c++',
'java',
'js',
'mongoDB',
'MySQL',
'oracle3',
'php',
'PostgreSQL',
'python',
'r',
'SQLite',
'SQLServer',
'angular',
'backbone',
'django',
'ember',
'flask',
'laravel',
'node',
'preact',
'rails',
'react',
'spring',
'svelte',
'vue',
];
/*
stylesheet.css
*/
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
}