일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 천등
- 알고리즘테스트
- 발더스모드
- LeetCode
- 취미
- 미앤아이
- 코딩테스트
- 메일우유
- 나쫌
- 토이프로젝트
- 서울제빵소
- 메탈퍼즐
- 코테
- 발더스3
- 맛집
- DIY
- 노노그램
- 밥먹고
- 게임
- 버즈2프로
- 3d퍼즐
- 잠실새내
- 뜨아거
- javascript
- 바질토마토뭐시기
- 눈알빠지겠네
- 송리단
- 발더스게이트
- 누룽지소금빵
- 하스스톤
- Today
- Total
.Zzumbong
[DEV] 프론트엔드 개발자 포트폴리오 Intro 리뷰 본문
keep calm and develop! 이라는 flag를 가진 우주인은 나를 캐릭터화 한 그림이다.
Astronauts 그림을 그려준 전 여자친구 현 와이프에게 압도적 감사!
전전직장에 입사하기 이전인 5년 전에 만들었던 포폴이 있다.
사실 지금도 사용중이지만 간단한 내용 업데이트만 해놓은 상태이다.
react 16버전으로 만들었고 class component를 사용 할 정도로 오래된 프로젝트이다.
오늘 리뷰할 부분은 인트로 부분이다.
5년전에 만들어서 다른 부분은 약간 촌스러워도
영혼을 갈아 넣었던 인트로 부분은 아직도 기가막히다.
이 부분은 총 6장의 캔버스로 이루어져 있다.
뒷 배경에서 은하수를 표현하는 milky가 1장, earth 컨테이너가 가진 4장, loaded가 1장이다.
earth는 총 3개의 캔버스에서 이미지와 마스킹으로 표현하고
shading으로 좀 더 현실 같은 지구를 표현하기 위해, 반사, 그림자, 대기권을 표현한다.
loading 캔버스는 컴포넌트 로드 시 검은 배경에서 서서히 나온 후 약간의 어두운 마스킹을 위해 추가했다.
지구를 구성하는 이미지는 총 6장인데,
밤과 낮 로딩 시 쓰이는 전체부분 마스킹 이미지로 구성되어있다.
지구이미지는 전부 4k이미지고 몇 시간의 구글링 끝에 다운받을 수 있었다.
3D모델에서 사용하는 텍스쳐 이미지에서 겨우 건저냈다.
마스킹 이미지는 포토샵으로 직접 그렸는데,
이 3장 이미지 때문에 포토샵을 구매할 수 없어서 온라인 무료 이미지 편집사이트에서 만듦..
컴포넌트가 로딩되면
componentDidMount() {
this.setupCanvas();
window.addEventListener('scroll', this.onScroll);
}
캔버스 셋업 메소드와 스크롤 이벤트를 호출한다.
milky와 earth는 parallax scroll로 조금씩 상단으로 올라가기 때문에 스크롤 이벤트도 필수.
처음 들어가서
대륙만 있는 이미지를 로딩하면
이렇게 보인다. ㅋㅋ
그후 마스킹된 밤 부분을 로드해서 위에 올려준다.
이렇게 로드하면 대륙에 낮인 부분에서
여튼 이런식으로 낮에서 밤으로 넘어갈때 실제처럼 불빛이 켜지는 것처럼 보이게 할 수 있다.
이제 구름을 넣으면 50%는 완성이다.
이제 셰이딩 부분을 적용 해주면 된다.
이 부분은 아주 평면적인 지구 2D 이미지를 3D처럼 보이게하는 착시 효과를 주기위해 개발했다.
drawShading() {
let CTX_SHADING = this.SPACE.shading;
let CTX_LOADING2 = this.SPACE.loading2;
let CENTER_X = this.getX('center');
let CENTER_Y = this.getY('center');
let EARTH_SIZE = this.state.earth_height / 2;
// 외곽 glow
this.drawArc(CTX_SHADING, CENTER_X, CENTER_Y, EARTH_SIZE + 150);
this.drawRGradient(
CTX_SHADING,
{
x: CENTER_X,
y: CENTER_Y,
size: EARTH_SIZE,
},
{
x: CENTER_X,
y: CENTER_Y,
size: EARTH_SIZE + 55,
},
[
[0, 'transparent'],
[0.3, 'rgba(33, 84, 150, 0.8)'],
[1, 'transparent'],
]
);
// 내부 glow
this.drawArc(CTX_SHADING, CENTER_X, CENTER_Y, EARTH_SIZE);
this.drawRGradient(
CTX_SHADING,
{
x: CENTER_X,
y: CENTER_Y,
size: EARTH_SIZE - 100,
},
{
x: CENTER_X,
y: CENTER_Y,
size: EARTH_SIZE,
},
[
[0, 'transparent'],
[0.0001, 'rgba(60, 105, 175, 0.01)'],
[1, 'rgba(60, 105, 175, 0.4)'],
]
);
// 태양 빛
this.drawArc(CTX_SHADING, CENTER_X, CENTER_Y, EARTH_SIZE);
this.drawRGradient(
CTX_SHADING,
{
x: CENTER_X + CENTER_X / 2,
y: CENTER_Y - CENTER_Y / 2,
size: 0,
},
{
x: CENTER_X,
y: CENTER_Y,
size: EARTH_SIZE,
},
[
[0, 'rgba(255, 255, 255, 0.5)'],
[1, 'transparent'],
]
);
this.drawArc(CTX_LOADING2, CENTER_X, CENTER_Y, EARTH_SIZE + 10);
this.drawRGradient(
CTX_LOADING2,
{
x: CENTER_X + CENTER_X / 2,
y: CENTER_Y - CENTER_Y / 2,
size: 0,
},
{
x: CENTER_X,
y: CENTER_Y,
size: EARTH_SIZE,
},
[
[0, 'rgba(0,0,0,0.85)'],
[0.5, 'rgba(0,0,0,0.95)'],
[1, 'rgba(0,0,0,1)'],
]
);
}
코드로 만들어진 셰이딩을 마지막에 로드한다. 유무 차이
지금보니 외부 glow부분이 너무 밝긴하다. 위 사진처럼 연하게 했다면 더 좋았을 뜻.
로드 후 마지막으로 animate를 실행 시켜 로드한다.
setupCanvas부분을 보면 이해하기 쉽다.
setupCanvas() {
console.log('---setupCanvas---');
this.SPACE.ctx_B = this.refs.earth_B.getContext('2d');
this.SPACE.ctx_L = this.refs.earth_L.getContext('2d');
this.SPACE.ctx_C = this.refs.earth_C.getContext('2d');
this.SPACE.ctx_back = this.refs.milky.getContext('2d');
this.SPACE.shading = this.refs.shading.getContext('2d');
this.SPACE.loading2 = this.refs.loading2.getContext('2d');
this.drawImageCanvas(
'b_space',
this.resource.milkyway,
this.SPACE.ctx_back,
0,
0,
this.state.canvas_width,
this.state.canvas_width * 2
)
.then(() => {
return this.drawImageCanvasWithMask(
'b_light',
this.resource.earth_L,
this.SPACE.ctx_L,
this.getX(),
this.getY()
);
})
.then(() => {
return this.drawImageCanvasWithMask(
'b_black',
this.resource.earth_B,
this.SPACE.ctx_B,
this.getX(),
this.getY()
);
})
.then(() => {
return this.drawImageCanvasWithMask(
'b_cloud',
this.resource.earth_C,
this.SPACE.ctx_C,
this.getX(),
this.getY()
);
})
.then(() => {
return this.drawShading();
})
.then(() => {
console.log('로딩 끝');
this.loadHandler(true);
setTimeout(() => {
console.log('로딩 애니메이션 시작');
this.startAnimate();
}, 500);
});
}
drawImageCanvas, drawImageCanvasWithMask 함수는 직접 비동기로 구현.
drawImageCanvas(imgName, imgSrc, ctx, x = 0, y = 0) {
var loadImg = new Promise((resolve, reject) => {
this.SPACE[imgName] = {};
let newImg = new Image();
newImg.src = imgSrc;
newImg.onload = () => {
let rato = this.state.canvas_width / newImg.naturalWidth;
let height = this.state.canvas_height * rato;
ctx.drawImage(newImg, x, y, this.state.canvas_width, height);
this.SPACE[imgName].img = newImg;
resolve();
};
});
return loadImg;
}
drawImageCanvasWithMask(imgName, resource, ctx, x = 0, y = 0) {
var loadImg = new Promise((resolve, reject) => {
this.SPACE[imgName] = {};
let mask = new Image();
let newImg = new Image();
mask.src = resource[0];
newImg.src = resource[1];
mask.onload = () => {
ctx.drawImage(
mask,
x,
y,
this.state.earth_width,
this.state.earth_height
);
ctx.globalCompositeOperation = 'source-in';
this.SPACE[imgName].mask = mask;
};
newImg.onload = () => {
ctx.drawImage(
newImg,
x,
0,
this.state.earth_ground,
this.state.earth_height
);
this.SPACE[imgName].img = newImg;
resolve();
};
});
return loadImg;
}
animate로 애니메이션을 한땀 한땀 구현.
animate() {
let X = this.state.x;
this.SPACE.ctx_L.drawImage(
this.SPACE.b_light.img,
X.L,
this.getY('LAND'),
this.state.earth_ground,
this.state.earth_height
);
this.SPACE.ctx_B.drawImage(
this.SPACE.b_black.img,
X.B,
this.getY('LAND'),
this.state.earth_ground,
this.state.earth_height
);
this.SPACE.ctx_C.globalCompositeOperation = 'source-over';
this.SPACE.ctx_C.clearRect(
0,
0,
this.state.canvas_width,
this.state.canvas_height
);
this.SPACE.ctx_C.drawImage(
this.SPACE.b_cloud.mask,
this.getX(),
this.getY(),
this.state.earth_width,
this.state.earth_height
);
this.SPACE.ctx_C.globalCompositeOperation = 'source-in';
this.SPACE.ctx_C.drawImage(
this.SPACE.b_cloud.img,
X.C,
this.getY('LAND'),
this.state.earth_ground,
this.state.earth_height
);
for (const key in X) {
if (X[key] < -(this.state.earth_ground / 2)) {
X[key] = 0;
}
}
this.setState({
x: {
L: X.L - 0.05,
B: X.B - 0.05,
C: X.C - 0.1,
},
});
requestAnimationFrame(this.animate);
}
getX(posi) {
if (posi === 'center') return this.state.canvas_width / 2;
return (this.state.canvas_width - this.state.earth_width) / 2;
}
getY(posi) {
if (posi === 'center')
return -(this.state.earth_height / 3) + this.state.earth_height / 2;
else if (posi === 'LAND') return -(this.state.earth_height / 4);
return -(this.state.earth_height / 3);
}
완성된 지구와 우리 은하수
오랜만에 class component를 보니 답답함도 느껴지고 나의 부족함도 느껴지는 코드가 보이기도 하고
나중에 다른 포폴 사이트를 만든다면 없어질 수 있는 사이트지만
5년전에 정말 좋은 곳으로 이직하고 싶은 마음과
항상 좋아하는 우주에 관련된 무언가를 만든다는데 열정이 생겨 정말 열심히 만들었다.
100% 이것 덕분에 취업이 된건 아니겠지만 나중에 물어보니 포폴이 있어서 더 집중해서 이력서를 봤다고 하더라
짧은 시간내에 기획/개발/디자인/로고 모두 고민해보면서 만드는 것에 대한 재미를 깨닫게 된 계기이다.
그래서 어느날 힘들 때, 이 포트폴리오를 보면 과거의 내가 대견스럽기도 해서 다시 힘이 나기도 한다.
전체 코드와 개발과정을 readMe에 짧게 써놨다.
아래에서 보도록!
https://github.com/blowROCK/portfolio
'Dev' 카테고리의 다른 글
[DEV] react/typescript로 노노그램 만들어보기 - 2 (0) | 2024.03.24 |
---|---|
[DEV] react/typescript로 노노그램 만들어보기 (0) | 2024.03.22 |
[TIP] 쉬운 디버깅을 위한 console 메소드 알아보기 (0) | 2024.02.15 |
[15~22] 8년차 Front-End 개발자의 개발 인생 회고록 (0) | 2022.11.23 |