自己紹介ページの作成
87008 ワード
1.クラス名で画像を変更する
// 클래스명 배열
const directionClassName = ['left','center','right'];
// 이전 버튼
const fadeBack = () => {
const shiftDirection = directionClassName.shift();
directionClassName.push(shiftDirection);
sliderEls.forEach((el,idx)=>{
el.classList.remove(el.classList[1]);
el.classList.add(directionClassName[idx]);
})
}
// 다음 버튼
const fadeFront = () => {
const popDirection = directionClassName.pop();
directionClassName.unshift(popDirection);
sliderEls.forEach((el,idx)=>{
el.classList.remove(el.classList[1]);
el.classList.add(directionClassName[idx]);
})
}
2. mousemove & mouseleave
// rotate 초기화
const resetRotate = () => {
if(document.body.offsetWidth<=650)
return false;
contactInnerEl.style.transform = `rotateY(0deg) rotateX(0deg)`;
}
// contact 영역에 마우스가 움직일때
contactEl.addEventListener('mousemove',(e)=>{
if(document.body.offsetWidth<=650)
return false;
if(cursorOnInfo) // 커서의 위치가 Info영역에 있는지 확인하기 위한 Boolean
return false;
let xAxis = (window.innerWidth/2 - e.pageX)/15;
let yAxis = (window.innerHeight/2 - e.pageY)/15;
contactInnerEl.style.transform = `rotateY(${xAxis}deg) rotateX(${yAxis}deg)`;
});
// contact 영역에서 마우스가 벗어날때
contactEl.addEventListener('mouseleave',(e)=>{
resetRotate();
});
// contactInfo 영역에서 마우스가 움직일때
contactInfoEl.addEventListener('mousemove',(e)=>{
resetRotate();
cursorOnInfo = true;
});
// contactInfo 영역에서 마우스가 벗어날때
contactInfoEl.addEventListener('mouseleave',(e)=>{
if(document.body.offsetWidth<=650)
return false;
cursorOnInfo = false;
});
// 클래스명 배열
const directionClassName = ['left','center','right'];
// 이전 버튼
const fadeBack = () => {
const shiftDirection = directionClassName.shift();
directionClassName.push(shiftDirection);
sliderEls.forEach((el,idx)=>{
el.classList.remove(el.classList[1]);
el.classList.add(directionClassName[idx]);
})
}
// 다음 버튼
const fadeFront = () => {
const popDirection = directionClassName.pop();
directionClassName.unshift(popDirection);
sliderEls.forEach((el,idx)=>{
el.classList.remove(el.classList[1]);
el.classList.add(directionClassName[idx]);
})
}
// rotate 초기화
const resetRotate = () => {
if(document.body.offsetWidth<=650)
return false;
contactInnerEl.style.transform = `rotateY(0deg) rotateX(0deg)`;
}
// contact 영역에 마우스가 움직일때
contactEl.addEventListener('mousemove',(e)=>{
if(document.body.offsetWidth<=650)
return false;
if(cursorOnInfo) // 커서의 위치가 Info영역에 있는지 확인하기 위한 Boolean
return false;
let xAxis = (window.innerWidth/2 - e.pageX)/15;
let yAxis = (window.innerHeight/2 - e.pageY)/15;
contactInnerEl.style.transform = `rotateY(${xAxis}deg) rotateX(${yAxis}deg)`;
});
// contact 영역에서 마우스가 벗어날때
contactEl.addEventListener('mouseleave',(e)=>{
resetRotate();
});
// contactInfo 영역에서 마우스가 움직일때
contactInfoEl.addEventListener('mousemove',(e)=>{
resetRotate();
cursorOnInfo = true;
});
// contactInfo 영역에서 마우스가 벗어날때
contactInfoEl.addEventListener('mouseleave',(e)=>{
if(document.body.offsetWidth<=650)
return false;
cursorOnInfo = false;
});
3. blur & keyup & resize
ファジイイベント
infoTextEl.forEach(input=>{input.addEventListener('blur', toggleInfoValue);});
keyupイベント
document.addEventListener('keyup',(e)=>{
if(e.key === 'ArrowDown'){
++nowIndex;
if(nowIndex > lastNav)
nowIndex = 0;
toggleSlide();
toggleSection();
toggleOutNav();
toggleMoreBtn();
}
else if(e.key === 'ArrowUp'){
--nowIndex;
if(nowIndex<0)
nowIndex = lastNav;
toggleSlide();
toggleSection();
toggleOutNav();
toggleMoreBtn();
}
});
resizeイベント
window.addEventListener('resize', function(){
if(document.body.offsetWidth <= 1180){
if(miniGameTitle.innerText = '🎮 미니게임 🎮'){
miniGameTitle.innerText = '🔒 모바일 미지원';
}else{
return false;
}
}else if(document.body.offsetWidth > 1180){
if(miniGameTitle.innerText = '🔒 모바일 미지원'){
miniGameTitle.innerText = '🎮 미니게임 🎮';
}else{
return false;
}
}
if(document.body.offsetWidth <= 365){
logoEl.children[1].innerText = 'Jeong';
}else{
logoEl.children[1].innerText = 'Jeong SeungOk';
}
})
infoTextEl.forEach(input=>{input.addEventListener('blur', toggleInfoValue);});
document.addEventListener('keyup',(e)=>{
if(e.key === 'ArrowDown'){
++nowIndex;
if(nowIndex > lastNav)
nowIndex = 0;
toggleSlide();
toggleSection();
toggleOutNav();
toggleMoreBtn();
}
else if(e.key === 'ArrowUp'){
--nowIndex;
if(nowIndex<0)
nowIndex = lastNav;
toggleSlide();
toggleSection();
toggleOutNav();
toggleMoreBtn();
}
});
window.addEventListener('resize', function(){
if(document.body.offsetWidth <= 1180){
if(miniGameTitle.innerText = '🎮 미니게임 🎮'){
miniGameTitle.innerText = '🔒 모바일 미지원';
}else{
return false;
}
}else if(document.body.offsetWidth > 1180){
if(miniGameTitle.innerText = '🔒 모바일 미지원'){
miniGameTitle.innerText = '🎮 미니게임 🎮';
}else{
return false;
}
}
if(document.body.offsetWidth <= 365){
logoEl.children[1].innerText = 'Jeong';
}else{
logoEl.children[1].innerText = 'Jeong SeungOk';
}
})
4.ミニゲーム
クラス定義のプロパティとメソッドの使用
// 플레이어
class Player{
constructor(x,y,radius,color,score){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.score = score;
}
draw(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fillStyle = this.color
ctx.fill();
}
}
// 발사체
class Projectile{
constructor(x,y,radius,color,velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity;
}
draw(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fillStyle = this.color;
ctx.fill();
}
update(){
this.draw();
this.x = this.x + this.velocity.x;
this.y = this.y + this.velocity.y;
}
}
// 적
class Enemy{
constructor(x,y,radius,color,velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity;
}
draw(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fillStyle = this.color;
ctx.fill();
}
update(){
this.draw();
this.x = this.x + this.velocity.x;
this.y = this.y + this.velocity.y;
}
}
// 플레이어
class Player{
constructor(x,y,radius,color,score){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.score = score;
}
draw(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fillStyle = this.color
ctx.fill();
}
}
// 발사체
class Projectile{
constructor(x,y,radius,color,velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity;
}
draw(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fillStyle = this.color;
ctx.fill();
}
update(){
this.draw();
this.x = this.x + this.velocity.x;
this.y = this.y + this.velocity.y;
}
}
// 적
class Enemy{
constructor(x,y,radius,color,velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity;
}
draw(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fillStyle = this.color;
ctx.fill();
}
update(){
this.draw();
this.x = this.x + this.velocity.x;
this.y = this.y + this.velocity.y;
}
}
適切なリース時間の実施
const spawnEnemies = () => {
const spawnTime = 3000 - (100 * stage) >= 1000 ? 3000 - (100 * stage) : 1000;
setInterval(()=>{
const radius = Math.random() * (30 - 20) + 20;
let x;
let y;
if(Math.random() < 0.5){
x = Math.random() < 0.5 ? 0 - radius : miniGameCanvasEl.width + radius;
y = Math.random() * miniGameCanvasEl.height;
}else{
x = Math.random() * miniGameCanvasEl.width;
y = Math.random() < 0.5 ? 0 - radius : miniGameCanvasEl.width + radius;
}
const color = `hsl(${Math.random() * 360},50%,50%)`;
const angle = Math.atan2(yCenter - y, xCenter - x);
const velocity = {
x: stage > 1 ? Math.cos(angle) * (stage * 0.7) : Math.cos(angle) * 0.5,
y: stage > 1 ? Math.sin(angle) * (stage * 0.7) : Math.sin(angle) * 0.5
};
enemies.push(new Enemy(x, y, radius, color, velocity));
},spawnTime)
}
アニメーション実装
const animate = () => {
requestId = window.requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(0,0,0,0.1)';
ctx.fillRect(0, 0, miniGameCanvasEl.width, miniGameCanvasEl.height);
player.draw();
projectiles.forEach((projectile,projectileIdx) => {
projectile.update();
if( projectile.x + projectile.radius < 0 ||
projectile.x - projectile.radius > miniGameCanvasEl.width ||
projectile.y + projectile.radius < 0 ||
projectile.y - projectile.radius > miniGameCanvasEl.height){
setTimeout(()=>{
projectiles.splice(projectileIdx,1);
},0);
}
});
enemies.forEach((enemy, enemyIdx) => {
enemy.update();
const distance = Math.hypot(player.x - enemy.x, player.y - enemy.y);
// End Game
// 적과 플레이어가 접촉하거나 200점을 획득했을 경우 애니메이션 중지
if(distance - player.radius - enemy.radius < 1){
window.cancelAnimationFrame(requestId);
setTimeout(()=>{
miniGameFinishEl.classList.add('is-active')
finishInfoEl.innerHTML = `
<span>Name: <input type="text" placeholder="닉네임"></span>
`
finishBtn.innerText = 'RESTART';
}, 0)
}else if(score >= 200 * stage){
window.cancelAnimationFrame(requestId);
setTimeout(() => {
startBtnEl.classList.remove('is-click');
startBtnEl.innerText = 'NEXT STAGE';
}, 0);
}
projectiles.forEach((projectile, projectileIdx) => {
const distance = Math.hypot(projectile.x - enemy.x, projectile.y - enemy.y);
if(distance - projectile.radius - enemy.radius < 1){
if(enemy.radius - 10 > 10){
enemy.radius -= 10;
setTimeout(()=>{
projectiles.splice(projectileIdx,1);
},0);
}else{
setTimeout(()=>{
score += 10;
pointEl.innerText = score;
enemies.splice(enemyIdx,1);
projectiles.splice(projectileIdx,1);
},0);
}
}
})
})
}
👉 パラメータとしてcallbackを受け入れ、callbackの数は表示リフレッシュ率と一致します.
👉 コールバックリスト項目を定義する一意のID
👉 エミッタの(x,y)座標を変更し、キャンバスの範囲を超えた場合、エミッタは、パッチ法によってアレイから除去される.
👉 敵と衝突した場合、敵の半径-10が10より大きい場合、10を減算して発射体
👉 敵とプレイヤーの座標間の距離
👉 敵とエミッタ座標の距離
エミッタ移動実装
miniGameCanvasEl.addEventListener('click',(e)=>{
// 팝업이 떴을 경우
if(startBtnEl.classList.contains('is-click'))
e.preventDefault();
const angle = Math.atan2(e.offsetY - yCenter, e.offsetX - xCenter);
const velocity = {
x: Math.cos(angle)*5,
y: Math.sin(angle)*5
};
projectiles.push(new Projectile(xCenter, yCenter, 30, 'red', velocity));
})
START、RESTART、NEXT STAGEを実施
// 시작, 다음 스테이지 버튼
startBtnEl.addEventListener('click',()=>{
const reset = () =>{
ctx.clearRect(0,0,miniGameCanvasEl.width, miniGameCanvasEl.height);
enemies.splice(0,enemies.length);
projectiles.splice(0,projectiles.length);
stageEl.innerText = stage;
player.score = score;
}
if(startBtnEl.innerText === 'NEXT STAGE'){
++stage;
reset();
}
startBtnEl.classList.add('is-click');
setTimeout(()=>{
animate();
spawnEnemies();
},1000);
})
// 다시 시작, 유저이름 입력
finishBtn.addEventListener('click',()=>{
const reset = () =>{
stage = 1;
score = 0;
ctx.clearRect(0,0,miniGameCanvasEl.width, miniGameCanvasEl.height);
enemies.splice(0,enemies.length);
projectiles.splice(0,projectiles.length);
stageEl.innerText = stage;
pointEl.innerText = score;
}
player.score = score;
const inputEl = finishInfoEl.querySelector('input');
const userDataObj = {
name: inputEl.value,
score: player.score,
stage: stage
}
rankList.push(userDataObj);
setData(rankList);
miniGameFinishEl.classList.remove('is-active');
setTimeout(()=>{
reset();
animate();
spawnEnemies();
player.score = 0;
},1000);
})
👉 キャンバス内部を共に初期化し、敵/エミッタアレイを初期化
👉 START、NEXT STAGEは現在のステージとscoreを保持する
👉 RESTARTの場合、ステージとscoreはそれぞれ0と1
実装モードウィンドウ
miniGameBtnEl.addEventListener('click',(e)=>{
const target = e.target;
if(target === modalInfoBtn){
modalTitle.innerText = '💣게임설명';
miniGameModalEl.classList.add('is-active');
modalDescription.innerHTML = `
<ul class="info--rule">
<li>1. 점수가 200점이 되면 다음 스테이지로 넘어갑니다.</li>
<li>2. 플레이어가 장애물과 닿는 순간 게임이 끝나게 됩니다.</li>
<li>3. 스테이지가 올라갈수록 난이도가 올라가니 집중하세요.</li>
</ul>
`;
}else if(target == modalRankBtn){
let medal;
modalTitle.innerText = '📊 랭킹';
miniGameModalEl.classList.add('is-active');
modalDescription.innerHTML = '';
const rankListEl = document.createElement('ul');
rankListEl.classList.add('rank--list');
rankList.sort((a,b)=>b.score-a.score).map((user,idx)=>{
if(idx>2)
return false;
if(idx === 0)
medal = '🥇';
else if(idx === 1)
medal = '🥈';
else
medal = '🥉';
const liEl = document.createElement('li');
liEl.innerHTML = `${medal} ${user.name} | stage: ${user.stage} | score: ${user.score}`;
rankListEl.append(liEl);
})
modalDescription.append(rankListEl);
}else
return false;
});
modalCloseBtn.addEventListener('click',()=>{
miniGameModalEl.classList.remove('is-active');
})
プレイヤー情報をLocalStorageに保存
// 랭크 업데이트
const setData = (value) => {
localStorage.setItem('user',JSON.stringify(value));
}
// 초기 랭크 업데이트
const initData = () => {
const userData = JSON.parse(localStorage.getItem('user'));
const defaultData = [
{
name: '정승옥',
score: 550,
stage: 3
},
{
name: 'BOB',
score: 450,
stage: 3
},
{
name: 'STEVE',
score: 250,
stage: 2
}];
rankList = defaultData;
if(userData === null){
localStorage.setItem('user',JSON.stringify(defaultData));
}
}
initData(); // 초기 랭크 업데이트 실행
👉 更新されたRankList配列をパラメータとしてパラメータ値に渡す
👉 setItemメソッド、キーは「user」、値はJSONです.stringify(value)ローカルリポジトリ
管理:https://jeong-seungok.github.io/WECODE_self-introduction/
Reference
この問題について(自己紹介ページの作成), 我々は、より多くの情報をここで見つけました https://velog.io/@vsnm25/바닐라-자바스크립트-개발공부-기록テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol