출처: https://bumcrush.tistory.com/182 [맑음때때로 여름]

상세 컨텐츠

본문 제목

Week6(JavaScript)

MOFE 스터디

by 장동균 2020. 10. 28. 00:42

본문


가면 갈수록 할애하는 시간이 적어진다 ㅎ.... 아무래도 학기가 꽤 진행되다보니 과제도 쏟아지고 할 일도 많아지면서 계속 우선순위에서 밀리게 되는 것 같다. 그래도 이번 학기에 스터디한 덕분에 이렇게 반강제(?)적으로 할 수 있어서 좋은 것 같다. 나같이 게으른 사람에게는 확실히 시간과 분량의 압박이 필요하다.


 

16강.

const hero = document.querySelector('.hero');
const text = hero.querySelector('h1');
const walk=100; //100px
function shadow(e){
  //const width = hero.offsetWidth;
  //const height = hero.offsetHeight;
  const {offsetWidth:width, offsetHeight:height}=hero;
  let {offsetX:x, offsetY:y}=e;  
  //단순히 이렇게 해서 offset을 구하면 안되는 이유가 children의 요소에 들어가게 되면 이 offset값이 초기화 되어 버린다.
  //화면 전체의 좌측 상단도 (0,0)이고 children의 좌측 상단도 (0,0)으로 나와버린다.
  if(this !== e.target){
    x=x+e.target.offsetLeft;
    y=y+e.target.offsetTop;
  }
  //이런 식의 if문 처리를 해주어야 children의 요소에 들어오는 시점에 좌표의 값이 (0,0)으로 갱신되지 않는다.
  const xWalk = Math.round((x/width*walk)-(walk/2));
  const yWalk = Math.round((y/height*walk)-(walk/2));
  //위와 같은 처리를 하는 이유는 커서가 맨우측에 갔을 때 shasow가 그 지점까지 딸려오는걸 원하는 것이 아니기 때문. 
  //이 부분은 말로 설명하기 보다는 영상을 다시 보게 되면 왜 이런 식으로 줄였는지 쉽게 알 수 있다.
  //shadow는 text의 주변에서만 생겨나야해.
  text.style.testShadow=`${xWalk}px ${yWalk}px 0 red`;
   
}

hero.addEventListener('mousemove', shadow);

offsetX와 offsetY의 값이 children 요소에 들어오게 되면 초기화 된다는 점을 명시해야한다. 초기화 되지 않게 하기 위해서는 그 아래의 if 문과 같은 요소를 사용할 수 있다.

 


17강

 

developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/join

 

Array.prototype.join()

join() 메서드는 배열의 모든 요소를 연결해 하나의 문자열로 만듭니다.

developer.mozilla.org

join method에 대한 설명

 

<ul id="bands"></ul>

const bands = ['The Plot in You', 'The Devil Wears Prada','Pierce the Veil','Norma Jean','The Bled','Say Anything','The Midway State', 'Anywhere But Here'];

function strip(bandName){
  return bandName.replace(/^(a |the |an )/i,'').trim();
  //a, the, an을 대소문자 구분없이 없애겠다. 하지만 Anyway와 같은 단어에서의 an이 사라지면 안되기 때문에 각각의 단어들마다 뒤에 한 칸의 공간을 둔 것이다.  
}

const sortedBands = bands.sort((a,b)=>strip(a)>strip(b)?1:-1);

document.querySelector('#bands').innerHTML=
  sortedBands
  .map(band=>`<li>${band}</li>`)
  .join('');

a, the, an이 없다고 생각하고 정렬하겠다!!

 


나도 이런 실수를 해놓고 왜 안되는지 되게 헤맸던 경험이 있다.... 함수 자체를 할당하는 것과 함수의 결과를 할당하는 구문이 괄호로 구분된다는 것을 기억하고 있어야 한다.


removeEventListener로 이벤트 핸들러를 삭제한다. 이 메서드는 내 기억에 사용해본적이 없던 것 같다. 보통 이벤트를 할당할 때 이벤트를 해제하는 일은 거의 없었기 때문이다. 이 이벤트 해제의 과정에서도 주의해야할 점이 있다.

 elem.addEventListener("click", () => alert('감사합니다!'));
 elem.removeEventListener("click", () => alert('감사합니다!'));

이와 같이 해제해서는 안되는데 addEventListener 속의 함수의 내용과 removeEventListener 속의 함수의 내용이 같지만 다른 함수 이기 때문이다.

 function handler(){
	alert('감사합니다!');
 }

 input.addEventListener("click", handler);
 input.removeEventListener("click", handler);

이와 같이 해제해야 한다.


 

이벤트 버블링 - 한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작합니다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작합니다.

<form onclick="alert('form')">FORM 
  <div onclick="alert('div')">DIV 
    <p onclick="alert('p')">P</p> 
  </div> 
</form> 


이런 식의 코드에서 <p>를 클릭하게 되면 <p>의 alert뿐만 아니라 <div>와 <form>의 alert도 버블링에 의해 같이 실행된다.

 

이 버블링 때문에 부모 요소의 핸들러는 이벤트가 정확히 어디서 발생했는지 알 수 있다. 이 발생한 지점에 접근하기 위해서는 event.target을 사용한다.

<form id="form">FORM 
    <div>DIV 
      <p>P</p> 
    </div> 
  </form> 

form.onclick = function(event) {

event.target.style.backgroundColor ='yellow';

}

이런 식으로 작성하면 form부분을 누르면 form 부분만 배경이 노란색으로,  div부분을 누르면 div 부분만 배경이 노란색으로, p부분을 누르면 p 부분만 배경이 노란색으로 변화한다.

 

js30 25강에서 이 버블링에 대해서 되게 자세히 설명해준다. 모던자바스크립트에서 나온 설명은 코드만 참고해서 사용하고 자세한 이해는 이 영상을 보는게 더 많이 도움이 될 것 같다.



클라이언트 좌표: clientX와 clientY
페이지 좌표: pageX와 pageY

클라이언트 좌표는 문서를 기준으로 하기 때문에 스크롤이 변하여도 좌표의 값은 변하지 않는다.
페이지 좌표는 창의 왼쪽 위를 기준으로 하기 때문에 스크롤이 변하면 좌표의 값도 변한다.

글자 위에서 마우스를 더블클릭하면 글자가 선택되는 것이 보통이다. 하지만 이러한 기능이 필요하지 않은 경우들이 있다.

<b ondblclick="alert('클릭!')" onmousedown="return false"> 
  여기를 더블클릭해주세요. 
</b> 


이와 같이 코드를 작성하면 더블클릭했을 때 alert 창은 뜨지만, 글자는 드래그 되지 않는다.,

 <div oncopy="alert('불법 복제를 예방하기 복사 기능을 막아놓았습니다!');return false"> 
  Lorem ipsum dolor sit amet, conse 
</div> 


이런 식으로 복사를 막을 수도 있음.


<li data-time="1:23"> 
foo1 
</li> 

<li data-time="2:34"> 
foo2 
</li> 

const timeNodes = document.querySelectorAll('[data-time]'); 


이런 식으로 대괄호를 사용하면 data-time 속성을 가지는 것들을 가져온다. 보통 이런 속성을 특정하는 경우는 그 속성을 가지는
모든 것들을 원할 가능성이 높다. 그때는 querySelectorAll을 통해 가져오면 된다.
이 예제의 경우에서는 li 요소 2개가 모두 timeNodes에 들어오게 된다.
이런 대괄호를 사용해 속성값을 검색하는 방법을 특성 선택자라고 하는데 자세한 사용은 여기에 있기 때문에 필요할 때 보고 사용하면 좋을 것 같다.

 

developer.mozilla.org/ko/docs/Web/CSS/Attribute_selectors

 

특성 선택자

CSS 특성 선택자는 주어진 특성의 존재 여부나 그 값에 따라 요소를 선택합니다.

developer.mozilla.org

제일 중요한 것은 이 특성 선택자를 통해 반환되는 데이터의 type은 배열이 아니라 NodeList.
보통 이 결과를 받아서 map 메서드를 이용해 순환하면서 데이터를 사용할 경우가 많은데 데이터 type이 배열이 아니기 때문에 사용하기 불편. 따라서 배열의 형태로 바꿔준다.
배열로 바꾸어서 데이터를 받는 방법은 이 영상에서 알려준건 2가지.

const timeNodes = Array.from(document.querySelectorAll('[data-time]'));

const timeNodes = [...document.querySelectorAll('[data-time]')];

 

나는 개인적으로 두번째 방법이 더 보기 편한듯

저 data-time 속성을 사용해서 영상의 시간을 표시하기 위해

const seconds = timeNodes
	.map(node => node.dataset.time)
	.map(timeCode => {
	 const [mins, secs] = timeCode.split(':').map(parseFloat);
	return (mins *60)+secs;
})

와 같이 코드를 만듦. 이러한 요소가 필요할 때 보고 따라하면 좋을 듯.



links가 걸린 word를 hover하면 하얀색 동그라미 창이 그 word를 둘러싸게 만들기.
엄청 긴 본문이 있고 그 본문 중간중간에 있는 단어들에 a tag가 걸려있다.

const triggers = document.querySelectorAll('a');
const highlight = document.createElement('span');
highlight.classList.add('highlight');
document.body.append(highlight);

function highlightLink(){
   const linkCoords = this.getBoundingClientRect();
   const coords = {
	width: linkCoords.width,
	height: linkCoords.height,
	top: linkCoords.top + window.scrollY,
	left: linkCoords.left + window.scrollX
};
   highlight.style.width = `${coords.width}px`;
   highlight.style.height = `${coords.height}px`;
   highlight.style.transform = `translate(${coords.left}px, ${coords.top}px)`;
}

triggers.forEach(a=>a.addEventListener('mouseenter', highlightLink));

일단 getBoundingClientRect() 메서드를 처음 봤다. 저 메서드를 사용하면 요소의 위치에 대한 모든 정보들을 받을 수 있는 것 같다.
단어를 클릭했을 때 단어 위로 흰색 동그라미가 그려지는 원리는 그 단어의 위치를 파악해서 그 위치에 흰색 동그라미를 그리는 것일 뿐이다.
이 위치를 파악하기 위해서 자바스크립트의 절대위치와 상대위치 개념이 사용되었는데

mommoo.tistory.com/85

 

[JavaScript] 요소의 절대좌표 상대좌표 구하기.

안녕하세요. 오늘은 자바스크립트로 요소의 위치를 구하는 방법에 대해 포스팅 합니다. 요소의 위치를 구할 때, 생각해야할 개념이 있습니다. 이 개념은 자바스크립트 뿐만 아니라, 모든 화면

mommoo.tistory.com

이곳에 설명이 잘 되어있다. 솔직히 이해하려고 읽었는데 이해하지 못했다...

이 절대좌표와 상대좌표에 대한 내용들이 사실상 JS30 강의에서 제일 중요하고 제일 많은 부분들을 차지한다. 정말 잘 알아야 하는 개념이라고 생각은 드는데 어려워서 공부할 엄두가 안난다... 방학이 시작되면 이것부터 공부해야지 싶다.


삽입 메서드

 

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert";
  div.innerHTML = "<strong>안녕하세요!</strong> 중요 메시지를 확인하셨습니다.";

  document.body.append(div);
  setTimeout(()=>div.remove(), 1000); // 이런 식으로 만들어낸 노드를 1초 후에 삭제할 수도 있다.
</script>

script 내에서 노드를 만들고 그 노드를 삽입하는 것.

node.append(노드나 문자열) - 노드나 문자열을 node 끝에 삽입합니다.

node.prepend(노드나 문자열) - 노드나 문자열을 node 맨 앞에 삽입합니다.

node.before(노드나 문자열) - 노드나 문자열을 node 이전에 삽입합니다.

node.after(노드나 문자열) - 노드나 문자열을 node 다음에 삽입합니다.

node.replaceWith(노드나 문자열) - node를 새로운 노드나 문자열로 대체합니다.

 

새로 만든 노드를 삽입할 위치도 프로그래머가 직접 정할 수 있다.

<ol id="ol">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  ol.before('before'); // <ol> 앞에 문자열 'before'를 삽입함
  ol.after('after'); // <ol> 뒤에 문자열 'after를 삽입함

  let liFirst = document.createElement('li');
  liFirst.innerHTML = 'prepend';
  ol.prepend(liFirst); // <ol>의 첫 항목으로 liFirst를 삽입함

  let liLast = document.createElement('li');
  liLast.innerHTML = 'append';
  ol.append(liLast); // <ol>의 마지막 항목으로 liLast를 삽입함
</script>

document.body.style.margin = '20px';

자바스크립트에서는 이런 식으로 css 요소를 수정 할 수도 있다.

 

ko.javascript.info/size-and-scroll

 

요소 사이즈와 스크롤

 

ko.javascript.info

이거랑 파트 2의 1.10, 1.11 에 나와있는 내용들은 다 중요하다고 생각이 든다. 이런 css적인 요소들이 항상 너무 어렵고 문제가 된다. 자바스크립트도 문제지만 CSS도 참 문제라는 생각이 많이 든다.


상당수 이벤트들이 발생 즉시 브라우저에 의해 특정 동작을 자동으로 수행한다.

링크를 클릭하면 해당 URL로 이동합니다.
폼 전송 버튼을 클릭하면 서버에 폼이 전송됩니다.
마우스 버튼을 누른 채로 글자 위에서 커서를 움직이면 글자가 선택됩니다.

이런 기본 동작을 막아야 하는 경우들이 존재한다. 예를 들면 submit이 수행되면 자동으로  page가reload된다. react에서
이 reload를 막아야 하기 때문에 submit 함수 안에는 항상 e.preventDefault() 가 있었다.


<style>
  .invalid { border-color: red; }
  #error { color: red }
</style>

Your email please: <input type="email" id="input">

<div id="error"></div>

<script>
input.onblur = function() {
  if (!input.value.includes('@')) { // not email
    input.classList.add('invalid');
    error.innerHTML = 'Please enter a correct email.'
  }
};

input.onfocus = function() {
  if (this.classList.contains('invalid')) {
    // remove the "error" indication, because the user wants to re-enter something
    this.classList.remove('invalid');
    error.innerHTML = "";
  }
};
</script>

이런 식으로 입력이 valid할 때는 정상적으로, 그렇지 않을 때는 빨간 테두리와 에러 메시지를 보여줄 수 있다.

elem.focus()와 elem.blur() 메서드를 통해 포커스(입력창을 클릭하면 I 가 깜빡이는 것) 설정할 수도 해제할 수도 있다.


<input type="text" onchange="alert(this.value)">
<input type="button" value="버튼">

이 예제(change 이벤트)에서 onchange의 alert가 실행되는 시점은 첫번째 input이 포커스를 잃을 때이다. 이 때문에 입력창에 입력을 마치고 버튼을 눌려도 alert 창이 뜨고 바깥쪽을 눌러도 alert 창이 뜬다.
반면 input 이벤트만 존재할 경우 이때는 사용자가 값을 수정할 때마다 이벤트가 발생한다. 
select와 input type=checkbox,input type=radio는 선택 값이 변경된 직후에 이벤트가 발생합니다.


DOMContentLoaded – DOM 구성이 완료되었을 때 document 객체에서 실행됩니다. 자바스크립트를 사용해 요소를 조작하는 것은 이 이벤트가 실행된 후입니다.
<script>...</script>나 <script src="..."></script>를 사용해 삽입한 스크립트는 DOMContentLoaded가 실행되는 것을 막습니다. 브라우저는 이 스크립트가 실행되길 기다립니다.
DOMContentLoaded는 실행되어도 이미지를 비롯한 기타 리소스들은 여전히 로드 중일 수 있습니다.
load – 페이지를 비롯한 이미지 등의 자원 전부가 모두 불러와졌을 때 window 객체에서 실행됩니다. 모든 자원이 로드되는 걸 기다리기에는 시간이 오래 걸릴 수 있으므로 이 이벤트는 잘 사용되지 않습니다.
beforeunload – 사용자가 페이지를 떠나려 할 때 window 객체에서 발생합니다. 이 이벤트를 취소하려 하면 브라우저는 사용자에게 "we have unsaved changes…"등의 메시지를 띄워 정말 페이지를 떠날 건지 물어봅니다.
unload – 사용자가 최종적으로 사이트를 떠날 때 window 객체에서 발생합니다. unload 이벤트 핸들러에선 지연을 유발하는 복잡한 작업이나 사용자와의 상호작용은 할 수 없습니다. 이 제약사항 때문에 unload 이벤트는 아주 드물게 사용됩니다. 하지만 예외적으로 navigator.sendBeacon을 사용해 네트워크 요청을 보낼 수 있습니다.
document.readyState – 문서의 현재 상태를 나타내줍니다. readystatechange 이벤트를 사용하면 변화를 추적할 수 있습니다.
loading – 문서를 불러오는 중일 때
interactive – 문서가 완전히 불러와졌을 때. DOMContentLoaded가 실행되기 바로 직전에 해당 값으로 변경됩니다.
complete – 문서를 비롯한 이미지 등의 리소스들도 모두 불러와졌을 때. window.onload가 실행되기 바로 직전에 해당 값으로 변경됩니다.

 

5.1의 요약부분을 그대로 복사해서 가져왔다. 천천히 읽어봤지만 어느 경우에 이런 메서드들을 사용하는지 잘 모르겠어서 
그냥 요약 부분을 그대로 가져왔다. 이 메서드들이 필요해질때 그때 이 글을 다시 봐야 이해할 수 있을 것 같다.


모던 웹브라우저에서 돌아가는 스크립트 파일들은 대부분이 무겁다. 
브라우저는 HTML을 읽다가 <script>...</script> 태그 혹은 외부 스크립트를 참조하는 코드인 <script src="..."></script> 태그를 만나게 되면 스크립트를 먼저 실행해야 하기 때문에 DOM 생성을 멈춘다.

이런 브라우저의 동작 방식은 두 가지의 중요한 이슈를 만들어낸다.
1. 스크립트는에서 스크립트 아래에 있는 DOM 요소에 접근할 수 없습니다. 따라서 DOM 요소에 핸들러를 추가하는 것과 같은 여러 행위가 불가능해집니다.
2. 페이지 위쪽에 용량이 큰 스크립트가 있는 경우 스크립트가 페이지를 ‘막아버립니다’. 페이지에 접속하는 사용자들은 스크립트를 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없게 됩니다.

이런 이유 때문에 항상 script tag를 body의 맨 마지막에 넣으라고 했던 거였다. 하지만 이 방식이 완벽한 방식은 아니다.
HTML 문서 자체가 굉장히 큰 경우 HTML 문서 전체를 다운로드 한 이후에 스크립트를 다운받게 하기 때문에 페이지가 정말 느려진다고 한다.

이러한 문제에 대한 근본적인 해결방식은 defer와 async

defer 속성이 있는 스크립트는 스크립트를 백드라운드에서 다운로드 한다. 따라서 스크립트를 다운로드 하는 와중에도 HTML 파싱이 멈추지 않고 DOM을 만들어 낸다. 그리고 defer 스크립트의 실행은 페이지 구성이 끝날 때까지 지연된다.

 

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

async 속성이 붙은 스크립트(이하 async 스크립트 또는 비동기 스크립트)는 페이지와 완전히 독립적으로 동작합니다.

async 스크립트는 defer 스크립트와 마찬가지로 백그라운드에서 다운로드됩니다. 따라서 HTML 페이지는 async 스크립트 다운이 완료되길 기다리지 않고 페이지 내 콘텐츠를 처리, 출력합니다(하지만 async 스크립트 실행중에는 HTML 파싱이 멈춥니다 – 옮긴이).
DOMContentLoaded 이벤트와 async 스크립트는 서로를 기다리지 않습니다.
페이지 구성이 끝난 후에 async 스크립트 다운로딩이 끝난 경우, DOMContentLoaded는 async 스크립트 실행 전에 발생할 수 있습니다,
async 스크립트가 짧아서 페이지 구성이 끝나기 전에 다운로드 되거나 스크립트가 캐싱처리 된 경우, DOMContentLoaded는 async 스크립트 실행 후에 발생할 수도 있습니다.
다른 스크립트들은 async 스크립트를 기다리지 않습니다. async 스크립트 역시 다른 스크립트들을 기다리지 않습니다.

이런 특징 때문에 페이지에 async 스크립트가 여러 개 있는 경우, 그 실행 순서가 제각각이 됩니다. 실행은 다운로드가 끝난 스크립트 순으로 진행됩니다.

그렇다고 한다. 독립적인 역할을 하는 스크립트에서는 사용할 수 있겠지만 아마도 순서가 보장되어야 하는 경우에는 사용하기 어려울 것 같다는 생각이 든다.


JS30-27강

 

const slider=document.querySelector('.items');

let isDown=false;
let startX;
let scrollLeft;

slider.addEventListener('mousedown',(e)=>{
    isDown=true;
    slider.classList.add('active');
    startX=e.pageX-slider.offsetLeft;
    scrollLeft=slider.scrollLeft;
});

slider.addEventListener('mouseleave',()=>{
    isDown=false;
    slider.classList.remove('active');
});

slider.addEventListener('mouseup',()=>{
    isDown=false;
    slider.classList.remove('active');
});

slider.addEventListener('mousemove',(e)=>{
    if(!isDown)return;
    e.preventDefault();
    const x=e.pageX-slider.offsetLeft;
    const walk=(x-startX)*3;
    slider.scrollLeft=scrollLeft-walk;
})

개인적으로 가장 재밌던 내용. 좌우 드래그를 통해 화면의 구성을 움직인다.

 


JS30-28강 일부

speed.addEventListener('mousemove', (e)=>{ 
   const y = e.pageY - this.offsetTop; //스피드 바의 절대 위치를 알아냄. 
   const percent = y / this.offsetHeight; //그 절대위치를 높이로 나누면 0-1 사이의 실수로 위치를 표현할 수 있다. 
})

JS30-30강 일부

 

min과 max 사이의 랜덤한 정수값을 하나 가져오는 함수

function randTime(min, max){
   return Math.round(Math.random() * (max - min) + min);
}

'MOFE 스터디' 카테고리의 다른 글

Week8(React)  (0) 2020.11.14
Week7(React)  (0) 2020.11.06
Week5(Javascript)  (0) 2020.10.17
Week4(Javascript)  (0) 2020.10.11
Week3(Javascript)  (0) 2020.10.02

관련글 더보기

댓글 영역