<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DOG FOOT</title>
    <link>https://dog-foooot.tistory.com/</link>
    <description>개발 관련된 잡다한 글 쓰는 블로그</description>
    <language>ko</language>
    <pubDate>Sat, 11 Apr 2026 10:10:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>퀸디</managingEditor>
    <item>
      <title>[git] branch 명령어로 로컬 브랜치 한 번에 정리하기</title>
      <link>https://dog-foooot.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;가끔 개발을 하다보면 로컬 브랜치가 손쓸 수 없을만큼 많아지는 경험, 해본 적 있으신가요?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;로컬 브랜치 상태를 원격과 동일하게 늘 맞춰주지 않으면 로컬에 이미 병합되어버린 브랜치들이 남아있는데요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;저는 귀찮을 때마다 아래 명령어로 브랜치를 정리합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;TL;DR&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;로컬에 있는 모든 브랜치를 제거하고 싶다면 아래 커맨드를 입력해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;*주의) force는 병합되지 않은 브랜치도 지워버리므로 조심히 사용하세요.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;git branch | grep -v '^*' | xargs git branch -d --force&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;커맨드 자세히 알아보기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 git branch 커맨드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736512840679&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch

// 예시 출력
// * main
// feature-1
// feature-2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 Git 저장소에 존재하는 모든 로컬 브랜치의 목록을 출력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;체크아웃된 브랜치 앞에는 *로 표시됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 현재 체크아웃된 브랜치를 지워버리면 문제가 되겠죠?&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736512950972&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;grep -v '^*'

// feature-1
// feature-2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 grep 을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;grep은 주어진 패턴과 일치하는 텍스트를 필터링하는 명령입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-v&amp;nbsp;옵션은&amp;nbsp;패턴과&amp;nbsp;일치하지&amp;nbsp;않는&amp;nbsp;행만&amp;nbsp;출력합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;'^*'는&amp;nbsp;정규식으로,&amp;nbsp;줄의&amp;nbsp;시작(^)에&amp;nbsp;*가&amp;nbsp;있는&amp;nbsp;경우를&amp;nbsp;의미합니다.&amp;nbsp;즉,&amp;nbsp;현재&amp;nbsp;체크아웃된&amp;nbsp;브랜치입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 체크아웃된 브랜치를 제외한 브랜치 이름만 출력하게 됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736513084652&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;xargs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;xargs는 이전 명령의 출력 결과를 인수로 전달해서 이후 커맨드를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736513137638&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;git branch 커맨드에 -d 옵션을 주게 되면 브랜치를 삭제합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 때, 병합되지 않은 브랜치는 삭제할 수 없기 때문에 로컬의 모든 브랜치를 정리하고 싶다면 --force 옵션을 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모든 명령어를 조합하면 로컬 브랜치를 정리할 수 있게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736513258411&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch | grep -v '^*' | xargs git branch -d

// 로컬에 main(체크아웃), feature-1, feature-2 브랜치가 있는 경우
// 아래 커맨드를 직접 입력하는 것과 같은 효과
// git branch -d --force feature-1
// git branch -d --force feature-2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;실수로 중요한 브랜치를 삭제하지 않도록 삭제 전에 브랜치 상태를 확인하며 force 옵션을 적절히 사용하면 좋아요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;끝!&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발새발/Git</category>
      <category>git</category>
      <category>git branch</category>
      <category>git command</category>
      <category>IT</category>
      <category>개발</category>
      <category>깃허브</category>
      <category>로컬 브랜치</category>
      <category>브랜치 삭제</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/29</guid>
      <comments>https://dog-foooot.tistory.com/29#entry29comment</comments>
      <pubDate>Fri, 10 Jan 2025 21:54:17 +0900</pubDate>
    </item>
    <item>
      <title>[git] 날려버린 스태시를 찾아서</title>
      <link>https://dog-foooot.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[problem]&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VSCode로 작업하다가 잘못된 스태시를 드랍해버린 걸 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깨달았을 때는 이미 드랍하고도 한참 지나서 커밋해시는 기억도 안나고... 근데 생각보다 작업은 많이 했었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[solution]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 명령창에 아래 명령어 입력&lt;/p&gt;
&lt;pre id=&quot;code_1670258152127&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git fsck --no-reflog | awk '/dangling commit/ {print $3}' | xargs -L 1 git --no-pager show -s --format=&quot;%ci %H&quot; | sort&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 목록에 있는 커밋 해시를 검색해서 찾기 (gitLens 확장프로그램 쓰면 편함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-06 오전 1.36.29.png&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euqf3q/btrSUIGbNGI/OhHkqKk5ATJndqGfntzua1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euqf3q/btrSUIGbNGI/OhHkqKk5ATJndqGfntzua1/img.png&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euqf3q/btrSUIGbNGI/OhHkqKk5ATJndqGfntzua1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feuqf3q%2FbtrSUIGbNGI%2FOhHkqKk5ATJndqGfntzua1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;285&quot; height=&quot;242&quot; data-filename=&quot;스크린샷 2022-12-06 오전 1.36.29.png&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이제 살릴 수 있다!&lt;/p&gt;
&lt;pre id=&quot;code_1670258247004&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git stash apply &amp;lt;hash&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[회고]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 스태시는 영원하지 않고, 휴먼에러로 날려버리기 딱 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 중요한 작업물은 꼭 꼭 commit 해놓기로...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이래놓고 또 스태시 쓸듯 ㅠㅠ 스태시 찬양론자라&lt;/p&gt;</description>
      <category>개발새발/Git</category>
      <category>git</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/28</guid>
      <comments>https://dog-foooot.tistory.com/28#entry28comment</comments>
      <pubDate>Tue, 6 Dec 2022 01:39:04 +0900</pubDate>
    </item>
    <item>
      <title>[스코페 2021] 스코페 2021 1차 코테 참가 후기</title>
      <link>https://dog-foooot.tistory.com/27</link>
      <description>&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://vrn68.app.goo.gl/scofe2021&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;vrn68.app.goo.gl/scofe2021&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이번에 2021 스코페가 열렸는데,&lt;/p&gt;
&lt;p&gt;상품이 너무 빵빵해서 도전하지 않을 수 없었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1차는 4시간 동안 총 6문제가 나왔습니다.&lt;/p&gt;
&lt;p&gt;문제 유형은 dp, dfs, 소팅, 완탐 등 그냥 구현문제였습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;난이도가 너무 어려워서 도저히 못풀겠다! 시도도 못하겠다! 이런 문제는 없었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중간에 구름이 터진건지 뭔지 제출이 안 되는 구간이 있었는데 새로고침 하니 잘 제출됐습니다.&lt;/p&gt;
&lt;p&gt;좀 화났던 점은 저장하지 않으면 코드가 날아가버린다는 점? 문제 옮길 때는 알람 잘 띄워주던데&lt;/p&gt;
&lt;p&gt;뒤로가기 할 때는 그냥 휙 넘어가서 코드를 몇 번 날려먹었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4.5문제 풀어서 그닥 기대 안 하고 있었는데 2차 합격 메일이 왔네요?&lt;/p&gt;
&lt;p&gt;커트라인이 3문제 +조금 더 였나 봅니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;생각보다 다들 설렁설렁 풀었나봐요.&lt;/p&gt;
&lt;p&gt;어쨌든 2차도 풀게 됐으니 추첨 100명에 뽑히기를 기대해 봅니다. 하핫&lt;/p&gt;</description>
      <category>알고리즘</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/27</guid>
      <comments>https://dog-foooot.tistory.com/27#entry27comment</comments>
      <pubDate>Tue, 23 Mar 2021 14:25:21 +0900</pubDate>
    </item>
    <item>
      <title>[Git] Git History 정리를 위한 rebase -i 명령어 알아보기</title>
      <link>https://dog-foooot.tistory.com/26</link>
      <description>&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;이번에 개발서버에 배포를 줄기차게 하는 프로젝트(모바일 웹뷰 테스트...) 개발을 진행하면서,&lt;/p&gt;
&lt;p&gt;커밋로그를 신경쓰지 않고 작업했더니 로컬 브랜치의 깃 히스토리가 살짝 보기싫어졌습니다.&lt;/p&gt;
&lt;p&gt;비슷한 커밋로그의 반복... 그래서 마스터 브랜치에 푸시하기 전에 커밋을 하나로 합쳐버릴 생각입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 어떻게 하면 커밋을 하나로 만들 수 있는지 알아볼까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 서론&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;However, once you push your work, it is a different story entirely, and you should consider pushed work as final unless you have good reason to change it. In short, you should avoid pushing your work until you&amp;rsquo;re happy with it and ready to share it with the rest of the world.&lt;/blockquote&gt;
&lt;p&gt;먼저, 깃이 자유로워도 이미 커밋한 내용을 물리기는 불가능하기 때문에&lt;/p&gt;
&lt;p&gt;PUSH하지 않은 로컬 저장소에서만 가능하다는 사실을 잊지 말아야 한다.&lt;/p&gt;
&lt;p&gt;* rebase 명령어를 쓰기 때문에 이미 공용 브랜치에 커밋한 경우라면 다른 작업자들을 위해서라도&lt;/p&gt;
&lt;p&gt;커밋 합치기 같은 짓을 하면 안 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 마지막 커밋 수정&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;마지막 커밋 메시지를 단순히 수정할 경우라면 다음 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1607265500589&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git commit --amend
git commit --amend --no-edit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저장소에서 위 명령을 터미널에 입력하면 편집기로 들어갈 수 있는데, 여기서 커밋 로그를 수정하면 된다.&lt;/p&gt;
&lt;p&gt;만약 커밋을 수정할 필요가 없다면 --no-edit을 같이 추가해주면 됨.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 이건 마지막 커밋에 대해서만 쓸 수 있는 방법이다.&lt;/p&gt;
&lt;p&gt;나처럼 이미 28301580개의 커밋을 했다면 다른 명령어를 찾아야 한다.&lt;/p&gt;
&lt;p&gt;(아무 숫자나 쓴 거지만 실제로 수십개의 테스트 및 오타수정 등의 커밋을 했다.)&lt;/p&gt;
&lt;p&gt;그럼 여러 커밋을 변경하는 명령어는 뭘까? 바로 rebase 명령어에 -i옵션을 추가해서 쓴다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 커밋 합치기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1607265726718&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git rebase -i HEAD~3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HEAD~3은 HEAD(현재 위치)부터 그 위로 3개의 커밋을 수정한다는 뜻이고,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HEAD까지 4개의 커밋이 수정된다.&lt;/p&gt;
&lt;pre id=&quot;code_1607265782636&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick &amp;lt;commit&amp;gt; = use commit
# r, reword &amp;lt;commit&amp;gt; = use commit, but edit the commit message
# e, edit &amp;lt;commit&amp;gt; = use commit, but stop for amending
# s, squash &amp;lt;commit&amp;gt; = use commit, but meld into previous commit
# f, fixup &amp;lt;commit&amp;gt; = like &quot;squash&quot;, but discard this commit's log message
# x, exec &amp;lt;command&amp;gt; = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop &amp;lt;commit&amp;gt; = remove commit
# l, label &amp;lt;label&amp;gt; = label current HEAD with a name
# t, reset &amp;lt;label&amp;gt; = reset HEAD to a label
# m, merge [-C &amp;lt;commit&amp;gt; | -c &amp;lt;commit&amp;gt;] &amp;lt;label&amp;gt; [# &amp;lt;oneline&amp;gt;]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c &amp;lt;commit&amp;gt; to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;터미널에서 명령을 실행하면 위와 같은 화면을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;사용할 수 있는 명령어가 주루룩 나열되는데,&lt;/p&gt;
&lt;p&gt;내가 원하는 건 커밋 합치기이기 때문에 squash 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1607265962775&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pick f7f3f6d Change my name a bit
squash 310154e Update README formatting and add blame
squash a5f4a0d Add cat-file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령을 수행하고 나면 이전 커밋이 모두 변경된다.&lt;/p&gt;
&lt;p&gt;로컬 브랜치를 사용하면서 git 히스토리가 더럽혀지는 게 싫었는데,&lt;/p&gt;
&lt;p&gt;깃에서 편리한 기능을 제공해줘서 안심할 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;집에서는 깃을 안쓰기 때문에... 모든 예시는 깃에서 퍼왔다.&lt;/p&gt;
&lt;p&gt;그럼 이만~&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;Referances&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://book.git-scm.com/book/en/v2/Git-Tools-Rewriting-History&quot;&gt;https://book.git-scm.com/book/en/v2/Git-Tools-Rewriting-History&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발새발/Git</category>
      <category>git</category>
      <category>local branch</category>
      <category>Rebase</category>
      <category>rebase -i</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/26</guid>
      <comments>https://dog-foooot.tistory.com/26#entry26comment</comments>
      <pubDate>Sun, 6 Dec 2020 23:51:46 +0900</pubDate>
    </item>
    <item>
      <title>[CSS3] pure CSS로 parallax scrolling 구현하기</title>
      <link>https://dog-foooot.tistory.com/25</link>
      <description>&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;이번에 회사에서 기능 소개 랜딩 페이지를 만들면서,&lt;/p&gt;
&lt;p&gt;디자이너 분께 parallax scrolling으로 해줬으면 한다는 부탁을 받았는데요!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;솔직히 처음 들어본 이름이라 생소했는데, (14년도 쯤에는 유행이었다고 합니다.)&lt;/p&gt;
&lt;p&gt;애플의 제품소개 페이지에서 사용된 기능을 parallax라고 칭한다는 걸 알았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어쨌든, 이번에는 이 parallax scrolling을 pure css로 구현하는 방법에 대해 써보겠습니다.&lt;/p&gt;
&lt;p&gt;(opacity가 변경되는 trigger 형식의 애니메이션 같은 경우는... 따로 구현했습니다)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 서론&lt;/h2&gt;
&lt;p&gt;먼저, Parallax란? 직역하면 '시차'다.&lt;/p&gt;
&lt;p&gt;천문학에서 가까이 있으면 빠르게 움직이고 멀리있으면 느리게 움직인다고 하는 그 개념이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;시차&lt;br /&gt;(Parallax)는 고정된 먼 배경이 존재하는 상황에서 한 물체를 서로 다른 위치에 있는 두 관측자가 관측했을 때 발생하는 겉보기 위치의 차이 또는 변위이다.&lt;/blockquote&gt;
&lt;p&gt;고전게임을 생각하면 쉽다. 마리오 같은 게임은 배경을 고정하고 다른 사물의 움직이는 속도를 설정해서 시차 스크롤링을 하고 있다. 어쨌든, 우리는 이미 시차 스크롤링이 뭔지 알고 있었다. (정확히 무슨 개념인지 몰랐을 뿐이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;1. background 로 parallax 구현하기&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;위의 정의에서 알 수 있듯, parallax의 기본 원리는 '고정된 먼 배경'이다.&lt;/p&gt;
&lt;p&gt;즉, 우리는 css에서 background 속성을 이용해 고정된 먼 배경을 만들어줄 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 이걸 하면 스크롤 할 때 파워포인트에서 닦아내기 한 효과 비슷한 걸 줄 수 있다.&lt;/p&gt;
&lt;p&gt;이건 그냥 CSS 프로퍼티만으로 설정이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1606043835212&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.parallax-background{
  background-attachment: fixed;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. transform으로 parallax 구현하기&lt;/h2&gt;
&lt;p&gt;근데, 배경만 고정해서는 내가 원한 느낌과는 다르다.&lt;/p&gt;
&lt;p&gt;좀 더 가까이에 있는 녀석은 빨리 움직이는 효과를 줘야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 CSS만으로 어떻게 원근을 줄까?&lt;/p&gt;
&lt;p&gt;답은 간단하다. z축을 이용하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dw7wdV/btqNXeDNXdE/MVM5fGZpon1HRX7evGdy71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dw7wdV/btqNXeDNXdE/MVM5fGZpon1HRX7evGdy71/img.png&quot; data-alt=&quot;z축이 모니터쪽으로 튀어나오는 방향이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dw7wdV/btqNXeDNXdE/MVM5fGZpon1HRX7evGdy71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdw7wdV%2FbtqNXeDNXdE%2FMVM5fGZpon1HRX7evGdy71%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;z축이 모니터쪽으로 튀어나오는 방향이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이 그림을 보면 알 수 있다시피, z축으로 가까워 질수록 점점 크기가 커질 것을 예상해볼 수 있다.&lt;/p&gt;
&lt;p&gt;그럼 transform을 써보자! 아래 두 개 코드를 예시로 쓴다.&lt;/p&gt;
&lt;pre id=&quot;code_1606045132571&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.element {
  height: 100px;
  width: 100px;
  background: pink;
  transform: translateZ(0px);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1606045111178&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
&amp;lt;div class=&quot;element&quot;&amp;gt;커져라 네모네모!&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YG7xk/btqNXFHW7bw/uOFJOfS4EMkTtAQzEsUXmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YG7xk/btqNXFHW7bw/uOFJOfS4EMkTtAQzEsUXmK/img.png&quot; data-alt=&quot;translateZ 값을 바꿔도 동일.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YG7xk/btqNXFHW7bw/uOFJOfS4EMkTtAQzEsUXmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYG7xk%2FbtqNXFHW7bw%2FuOFJOfS4EMkTtAQzEsUXmK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;translateZ 값을 바꿔도 동일.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;CSS에서 translateZ를 바꿔서 앞으로 밀어보자. 결과는?&lt;/p&gt;
&lt;p&gt;무엇이 바뀌었는지 확인해보면 아무것도 일어나지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;perspective속성이 빠졌기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;원근 속성을 주지 않으면 아무리 Z축으로 움직여도 아무 일도 일어나지 않는다는 점을 명심해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;b&gt;perspective&lt;/b&gt;는 사용자의 시점 위치를 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;값이 커질수록 사용자가 멀리 있음을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;주의할 점은 값을 잘 생각해서 결정해야 하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;예를 들어 perspective: 4px로 주고 z축 4px 이상을 이동해버리면 사용자를 지나쳐 가버렸기 때문에 더이상 물체가 보이지 않는 상태가 된다. 당연한 얘기겠지만 음수로 주면 멀어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;이런 것을 염두에 두고 잘 설정하도록 하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;더불어 알면 좋은 것은 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;perspective-origin&lt;/b&gt;이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 속성의 기본값은 50%50%지만, center로 주면 보통 원하는 위치로 이동한다고 보면 된다. 투시 원점을 나타낸다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;마지막으로, &lt;span&gt;&lt;b&gt;backface-visibility&lt;/b&gt; 속성으로 뒷면도 보여줄지 말지 정할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 위 코드를 수정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1606049663076&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
&amp;lt;div class=&quot;parallax container&quot;&amp;gt;
  &amp;lt;div class=&quot;background&quot;&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div class=&quot;foreground&quot;&amp;gt;
  		빨라...
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;foreground fast&quot;&amp;gt;
  		더빨라...
    &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1606049680145&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 500px;
}
.parallax.container {
  position: relative;
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 8px;
  perspective-origin: 0%;
}
.parallax: {
  position: absolute;
  transform-origin: 0 50%;
  width: 420px;
  height: 420px;
}
.background:before {
  position: relative;
  content: '느려';
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  transform: translateZ(0px) scale(1);
  background: red;
  height: 800px;
  width: 100%;
}

.foreground {
  position: absolute;
  height: 100px;
  width: 100px;
  background: pink;
  top: 300px;
  transform: translateZ(4px) scale(0.5);
}
.foreground.fast {
background: yellow;
top: 250px;
transform: translateZ(6px) scale(0.25);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드를 실행 해 보면 배경은 느리고, 빠른 놈은 빠르고,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;더 빠른 놈은 더 빠르게 움직이는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;계산은 귀찮으면 따로 css함수로 빼도 된다.&lt;/p&gt;
&lt;p&gt;그리고 z축을 움직일 때는 크기가 커지는 것을 감안해서 스케일 작업을 잊지 말고 해줘야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;스케일 식은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;스케일 = 1 - (z축 깊이/원근 시야)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;보시다시피 빠른녀석은 0.5, 더 빠른 녀석은 0.25로 크기를 조절해주고 있는데,&lt;/p&gt;
&lt;p&gt;위 식에 따른 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;빠른 녀석 스케일(0.5) = 1 - (z축 깊이(4)/원근 시야(8))&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;더 빠른 녀석 스케일(0.25) = 1 - (z축 깊이(6)/원근 시야(8))&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 이만~.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb5Lf2/btqNYvLR1NI/DatytMpJfMuUMwkUvjQAK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb5Lf2/btqNYvLR1NI/DatytMpJfMuUMwkUvjQAK0/img.png&quot; data-alt=&quot;느려 빨라 더빨라...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb5Lf2/btqNYvLR1NI/DatytMpJfMuUMwkUvjQAK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb5Lf2%2FbtqNYvLR1NI%2FDatytMpJfMuUMwkUvjQAK0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;느려 빨라 더빨라...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;Referances&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8B%9C%EC%B0%A8_(%EC%B2%9C%EB%AC%B8%ED%95%99)&quot;&gt;https://ko.wikipedia.org/wiki/%EC%8B%9C%EC%B0%A8_(%EC%B2%9C%EB%AC%B8%ED%95%99)&lt;/a&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606043115044&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;시차 (천문학) - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;위키백과, 우리 모두의 백과사전. 시차에 대한 간단한 개념도. 관측자는 같은 물체를 보지만 충분히 멀어 고정된 배경에 대해 A에서의 관측자는 물체가 청색 영역 위에 존재하는 것처럼 관측될 &quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/%EC%8B%9C%EC%B0%A8_(%EC%B2%9C%EB%AC%B8%ED%95%99)&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/%EC%8B%9C%EC%B0%A8_(%EC%B2%9C%EB%AC%B8%ED%95%99)&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdToLe/hyIjuLw1WB/4ZcQZsnpTK6BzKPaqCg4m1/img.png?width=1200&amp;amp;height=864&amp;amp;face=0_0_1200_864&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8B%9C%EC%B0%A8_(%EC%B2%9C%EB%AC%B8%ED%95%99)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/%EC%8B%9C%EC%B0%A8_(%EC%B2%9C%EB%AC%B8%ED%95%99)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdToLe/hyIjuLw1WB/4ZcQZsnpTK6BzKPaqCg4m1/img.png?width=1200&amp;amp;height=864&amp;amp;face=0_0_1200_864');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;시차 (천문학) - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;위키백과, 우리 모두의 백과사전. 시차에 대한 간단한 개념도. 관측자는 같은 물체를 보지만 충분히 멀어 고정된 배경에 대해 A에서의 관측자는 물체가 청색 영역 위에 존재하는 것처럼 관측될&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3schools.com/howto/howto_css_parallax.asp&quot;&gt;https://www.w3schools.com/howto/howto_css_parallax.asp&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606042913233&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How To Create a Parallax Scrolling Effect&quot; data-og-description=&quot;How TO - Parallax Scrolling Learn how to create a &amp;quot;parallax&amp;quot; scrolling effect with CSS. Parallax Parallax scrolling is a web site trend where the background content (i.e. an image) is moved at a different speed than the foreground content while scrolling. &quot; data-og-host=&quot;www.w3schools.com&quot; data-og-source-url=&quot;https://www.w3schools.com/howto/howto_css_parallax.asp&quot; data-og-url=&quot;https://www.w3schools.com/howto/howto_css_parallax.asp&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c7xL89/hyIjvcBnkr/An0zUVXybwrAIhjQkkRYZK/img.jpg?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/howto/howto_css_parallax.asp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.w3schools.com/howto/howto_css_parallax.asp&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c7xL89/hyIjvcBnkr/An0zUVXybwrAIhjQkkRYZK/img.jpg?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;How To Create a Parallax Scrolling Effect&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;How TO - Parallax Scrolling Learn how to create a &quot;parallax&quot; scrolling effect with CSS. Parallax Parallax scrolling is a web site trend where the background content (i.e. an image) is moved at a different speed than the foreground content while scrolling.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.w3schools.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/perspective&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/perspective&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606045377360&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;perspective&quot; data-og-description=&quot;The perspective CSS property determines the distance between the z=0 plane and the user in order to give a 3D-positioned element some perspective.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/perspective&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/perspective&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bN4fpJ/hyIjDocdUK/XFK937KikScd9zJVNHgG91/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/ciicYW/hyIkBoLe0x/ZMY5Ga7LGMEy7LycXEIKlk/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/perspective&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/perspective&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bN4fpJ/hyIjDocdUK/XFK937KikScd9zJVNHgG91/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/ciicYW/hyIkBoLe0x/ZMY5Ga7LGMEy7LycXEIKlk/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;perspective&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;The perspective CSS property determines the distance between the z=0 plane and the user in order to give a 3D-positioned element some perspective.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>뚱땅뚱땅</category>
      <category>CSS</category>
      <category>CSS3</category>
      <category>HTML</category>
      <category>parallax</category>
      <category>parallax scrolling</category>
      <category>pure CSS</category>
      <category>시차</category>
      <category>시차 스크롤</category>
      <category>시차애니메이션</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/25</guid>
      <comments>https://dog-foooot.tistory.com/25#entry25comment</comments>
      <pubDate>Sun, 22 Nov 2020 22:00:54 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] Interface</title>
      <link>https://dog-foooot.tistory.com/24</link>
      <description>&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;회사에서 모바일-웹 통신 인터페이스를 정의했고,&lt;/p&gt;
&lt;p&gt;코드를 정리하는 과정에서 typescript interface를 사용했습니다.&lt;/p&gt;
&lt;p&gt;그리고 매번 프로젝트성 글만 썼었는데, 개념글을 쓰면 블로그 유입량이 늘어날까? 하는 실험도 있음.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 서론&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;아시다시피 타입스크립트는 자바스크립트의 슈퍼셋으로,&lt;/p&gt;
&lt;p&gt;자바스크립트의 요상한(?)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;this&lt;/b&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;함수를 1급 객체&lt;/b&gt;로 취급하는 부분이 동일하다.&lt;/p&gt;
&lt;p&gt;이번에 어떤 문제를 해결하는 과정에서 유틸 펑션을 만들어야 하는 부분이 있었는데,&lt;/p&gt;
&lt;p&gt;위 언급한 특성 덕분에 타입스크립트에서는 클래스 대신 함수에도 interface 적용이 가능하다.&lt;/p&gt;
&lt;p&gt;MDN에서도 class의 첫 소개에 이렇게 적혀있다.&lt;/p&gt;
&lt;p&gt;&quot;클래스는 사실 함수입니다.&quot;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Duck typing&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;인터페이스는 타입스크립트의 핵심 근간인 덕 타이핑에서 비롯된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span&gt;&quot;만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다.&quot; &lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1604838364587&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Duck{
	quack() { console.log('꽥'); }
    fly() { console.log('파닥파닥'); }
}

class Person {
	quack() { console.log('꽦!!!'); }
    fly() { console.log('인간은 비행기를 탔다.'); }
}


function duckHavior(duck){
	duck.quack();
    duck.fly();
}

james = new Duck();
jonh = new Person();
duckHavior(james); //꽥 파닥파닥
duckHavior(jonh); // 꽦!!!  인간은 비행기를 탔다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Duck 클래스와 Person 클래스는 다른 클래스지만 모양이 같기 때문에 동일한 타입으로 간주해버린다.&lt;/p&gt;
&lt;p&gt;quack과 fly함수만 구현되어 있다면 그게 뭐든 간에 duckHavior 내부에서는 오리가 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Interface&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;이런 덕 타이핑은 매우 편리하지만, 문제가 있다.&lt;/p&gt;
&lt;p&gt;duckHavior의 매개변수로 quack(), fly()를 가지고 있지 않은 객체를 넘긴다면 런타임 에러가 발생한다.&lt;/p&gt;
&lt;p&gt;그렇다고 함수 내에서 타입 가드를 작성하기엔, 덕 타이핑을 사용하려다 타입가드가 더 복잡해지는 경우가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;타입스크립트에서는 덕타이핑의 문제점을 해소하기 위해 Interface가 등장했다.&lt;/p&gt;
&lt;p&gt;Interface는 간단히 말하면 타입에 이름을 지어주는 것이고,&lt;/p&gt;
&lt;p&gt;컴파일타임에서 잘못된 타입을 사용했을 때 에러를 뱉어주기 때문에&lt;/p&gt;
&lt;p&gt;좀 더 타입 세이프티한 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;인터페이스는 다음과 같이 선언한다.&lt;/p&gt;
&lt;pre id=&quot;code_1604838791496&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface DuckLike{
	quack(): void;
    fly(): void;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #dddddd;&quot;&gt;&lt;s&gt;(코드블럭 인덴트가 왜이러는지?)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언한 인터페이스를 활용하면 위에서 작성했던 덕 타이핑 코드를 타입 세이프티하게 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1604840485261&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Duck implements DuckLike{
	quack() { console.log('꽥'); }
    fly() { console.log('파닥파닥'); }
}

class Person implements DuckLike{
	quack() { console.log('꽦!!!'); }
    fly() { console.log('인간은 비행기를 탔다.'); }
}


function duckHavior(duck: DuckLike){
	duck.quack();
    duck.fly();
}

james = new Duck();
jonh = new Person();
duckHavior(james); //꽥 파닥파닥
duckHavior(jonh); // 꽦!!!  인간은 비행기를 탔다.
duckHavior('test') // error&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2-1. Optional Property&lt;br /&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1604840540321&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Person{
	name: string;
    birth?: string;
    gender: string;
    age: number;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;?가 붙어있는 birth는 옵셔널 프로퍼티(Optional Property)라 하여, undefined가 될 수 있다.&lt;/p&gt;
&lt;p&gt;즉 birth는 &lt;i&gt;&lt;b&gt;string|undefined&lt;/b&gt;&lt;/i&gt; 타입이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2-2. Indexable Types&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1604840570472&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Person{
	name: string;
    birth?: string;
    gender: string;
    age: number;
    [indxe: string]: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;인터페이스에서는 인덱스를 정해줄 수 있다.&lt;/p&gt;
&lt;p&gt;Person에 인덱스를 선언해주자. 이제 string: string인 어느 프로퍼티를 선언해도 Person 타입이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2-3. Function Types&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1604840802761&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface NumberToString {
  (value: number): string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Typescript에서는 Function용 Interface도 선언 가능하다.&lt;/p&gt;
&lt;p&gt;NumberToString을 구현한 함수는 number타입 매개변수를 받아 어떻게든 가공하여 string값을 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1604840922918&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const ageString:NumberToString = (value: number) =&amp;gt; {
	return `i am ${value}years old`;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 식으로 사용한다.&lt;/p&gt;
&lt;p&gt;인터페이스 내부에 있는 펑션 시그니처가 길다면 펑션 타입으로 빼내 사용하는 것도 좋아 보인다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이런 특성들을 이용하여 코드를 정리했더니 보기도 깔끔하고 좋더라.&lt;/p&gt;
&lt;p&gt;갓터페이스.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;타입스크립트 인터페이스에 대해 알아봤다.&lt;/p&gt;
&lt;p&gt;그럼 이만~.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Refernces&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/interfaces.html#extending-interfaces&quot;&gt;https://www.typescriptlang.org/docs/handbook/interfaces.html#extending-interfaces&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1604837103102&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Handbook - Interfaces&quot; data-og-description=&quot;How to write an interface with TypeScript&quot; data-og-host=&quot;www.typescriptlang.org&quot; data-og-source-url=&quot;https://www.typescriptlang.org/docs/handbook/interfaces.html#extending-interfaces&quot; data-og-url=&quot;https://www.typescriptlang.org/docs/handbook/interfaces.html#extending-interfaces&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/interfaces.html#extending-interfaces&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.typescriptlang.org/docs/handbook/interfaces.html#extending-interfaces&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Handbook - Interfaces&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;How to write an interface with TypeScript&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.typescriptlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1604837110041&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Classes&quot; data-og-description=&quot;Class는 객체를 생성하기 위한 템플릿입니다. 클래스는 데이터와 이를 조작하는 코드를 하나로 추상화합니다. 자바스크립트에서 클래스는 프로토타입을 이용해서 만들어졌지만 ES5의 클래스 의&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8PLMu/hyIayTeMOT/cP8vinT6Bzs7YPGYcf14kK/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/CQBa7/hyH9ASGpBi/xeTZt5x3AhM0OObkwreeT1/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8PLMu/hyIayTeMOT/cP8vinT6Bzs7YPGYcf14kK/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/CQBa7/hyH9ASGpBi/xeTZt5x3AhM0OObkwreeT1/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Classes&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Class는 객체를 생성하기 위한 템플릿입니다. 클래스는 데이터와 이를 조작하는 코드를 하나로 추상화합니다. 자바스크립트에서 클래스는 프로토타입을 이용해서 만들어졌지만 ES5의 클래스 의&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91&quot;&gt;https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1604837809415&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;덕 타이핑 - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;위키백과, 우리 모두의 백과사전. 컴퓨터 프로그래밍 분야에서 덕 타이핑(duck typing)은 동적 타이핑의 한 종류로, 객체의 변수 및 메소드의 집합이 객체의 타입을 결정하는 것을 말한다. 클래스 &quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;덕 타이핑 - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;위키백과, 우리 모두의 백과사전. 컴퓨터 프로그래밍 분야에서 덕 타이핑(duck typing)은 동적 타이핑의 한 종류로, 객체의 변수 및 메소드의 집합이 객체의 타입을 결정하는 것을 말한다. 클래스&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발새발/Frontend</category>
      <category>duck typing</category>
      <category>Interface</category>
      <category>Javascript</category>
      <category>TypeScript</category>
      <category>util fuction</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/24</guid>
      <comments>https://dog-foooot.tistory.com/24#entry24comment</comments>
      <pubDate>Sun, 8 Nov 2020 22:12:24 +0900</pubDate>
    </item>
    <item>
      <title>[HTML5/JavaScript] 직소퍼즐 만들기 4(完) : 퍼즐 게임으로 만들기</title>
      <link>https://dog-foooot.tistory.com/23</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://dog-foooot.tistory.com/22&quot;&gt;dog-foooot.tistory.com/22&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1603632872493&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[HTML5/Javascript] 직소퍼즐 만들기 3: 퍼즐 조각 무작위로 섞기&quot; data-og-description=&quot;dog-foooot.tistory.com/21 [HTML5/JavaScript] 직소퍼즐 만들기 - 2: 드래그드롭으로 퍼즐넣기 dog-foooot.tistory.com/20 [HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력 안녕하세요, 김선진입니다. 주말..&quot; data-og-host=&quot;dog-foooot.tistory.com&quot; data-og-source-url=&quot;https://dog-foooot.tistory.com/22&quot; data-og-url=&quot;https://dog-foooot.tistory.com/22&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GoZBc/hyHZpJw39M/N9k7DWAOfHf9aVg7kmBp40/img.png?width=337&amp;amp;height=199&amp;amp;face=0_0_337_199,https://scrap.kakaocdn.net/dn/HIIYW/hyHZrm1sLI/VD9ktU9aExFkeX6BVHZcA0/img.png?width=337&amp;amp;height=199&amp;amp;face=0_0_337_199,https://scrap.kakaocdn.net/dn/bG4hJe/hyHZzyBtUI/gN8sMgQUkNIbbR0tC9XgEk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://dog-foooot.tistory.com/22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dog-foooot.tistory.com/22&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GoZBc/hyHZpJw39M/N9k7DWAOfHf9aVg7kmBp40/img.png?width=337&amp;amp;height=199&amp;amp;face=0_0_337_199,https://scrap.kakaocdn.net/dn/HIIYW/hyHZrm1sLI/VD9ktU9aExFkeX6BVHZcA0/img.png?width=337&amp;amp;height=199&amp;amp;face=0_0_337_199,https://scrap.kakaocdn.net/dn/bG4hJe/hyHZzyBtUI/gN8sMgQUkNIbbR0tC9XgEk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[HTML5/Javascript] 직소퍼즐 만들기 3: 퍼즐 조각 무작위로 섞기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;dog-foooot.tistory.com/21 [HTML5/JavaScript] 직소퍼즐 만들기 - 2: 드래그드롭으로 퍼즐넣기 dog-foooot.tistory.com/20 [HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력 안녕하세요, 김선진입니다. 주말..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;dog-foooot.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;직소 퍼즐 마지막 시리즈입니다. 직소 퍼즐을 게임으로 만들자!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 퍼즐 조각이 맞춰졌을 때 성공 여부 검사&lt;/p&gt;
&lt;p&gt;2. 퍼즐 맞추기 타이머 생성&lt;/p&gt;
&lt;p&gt;3. CSS (알아서 하시길)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존 코드에 이어서 작업합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. 퍼즐 성공 여부 검사&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;1-1. td에 index부여하기&lt;/p&gt;
&lt;p&gt;저번 코드에서 전역으로 선언해놓은 퍼즐 조각 리스트를 이용하여 퍼즐 성공을 검사하는 로직을 짜보자.&lt;/p&gt;
&lt;p&gt;간단하게 4*4 퍼즐을 1중 배열이라 생각하고 인덱싱해서 결과물 배열을 만들고, 초기 리스트와 결과물이 같은 경우 성공 판정을 한다.&lt;/p&gt;
&lt;p&gt;먼저 td를 생성할 때 해당 index를 id로 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1603634942881&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; tableData.id = `piece_${x * numColsToCut + y}`;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ya1SX/btqLE1AsDLl/Nrk3qHm3PSIjUFeIKmTZc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ya1SX/btqLE1AsDLl/Nrk3qHm3PSIjUFeIKmTZc1/img.png&quot; data-alt=&quot;2*2로 생성했을 때의 예시 인덱스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ya1SX/btqLE1AsDLl/Nrk3qHm3PSIjUFeIKmTZc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fya1SX%2FbtqLE1AsDLl%2FNrk3qHm3PSIjUFeIKmTZc1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2*2로 생성했을 때의 예시 인덱스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;1-2. 결과 배열과 기존 배열을 비교하여 성공 판정&lt;/p&gt;
&lt;p&gt;성공 판정은 간단하게 구현했다. 배열 두 개를 받아 두 배열이 같은지만 검사한다.&lt;/p&gt;
&lt;pre id=&quot;code_1603636517070&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const completePuzzle = (puzzle, result) =&amp;gt; puzzle.every((piece, index) =&amp;gt;piece === result[index]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1-3. drop함수 수정&lt;/p&gt;
&lt;pre id=&quot;code_1603636706889&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const index = ev.target.id.replace('piece_', '');
  resultPieces[Number(index)] = data;
  if (completePuzzle(originImagePieces, resultPieces)) window.alert('퍼즐 성공!');&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존 코드 아래에 추가해준다.&lt;/p&gt;
&lt;p&gt;이걸로 간단하게 퍼즐을 검증해서 성공 시에는 알람을 준다.&lt;/p&gt;
&lt;p&gt;인덱스 대신 id를 긁었다 말았다 하는 이유는 elements만 보고 퍼즐을 맞출 수 없게 하기 위함이다.&lt;/p&gt;
&lt;p&gt;그리고, 다시 시작하는 함수에 아래 코드도 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1603637988887&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while(resultPieces.length &amp;gt; 0) {
    resultPieces.pop();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. 퍼즐 맞추기 타이머&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;2-1. setInterval로 타이머 생성하기&lt;/p&gt;
&lt;pre id=&quot;code_1603637492272&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const maxTime = 30;
let nowTime = 0;
let timer;
function startTimer() {
timer = window.setInterval(() =&amp;gt; {
	if(nowTime === maxTime) {
		window.clearInterval(timer);
		suffleRendering();
		window.alert('시간 종료!');
	}
	nowTime += 1;
	
}, 1000);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;startTimer라는 함수를 만들어 우리의 maxTime까지 기다려주게 한다.&lt;/p&gt;
&lt;p&gt;게임을 시작함과 동시에 timer가 세팅되기 때문에 게임이 종료된 후에는 suffle을 한번 더 돌려줘야 한다.&lt;/p&gt;
&lt;p&gt;그리고 처음 퍼즐을 맞추기 시작했을 때 다시 timer를 시작해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1603637943593&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  if (resultPieces.length === 0) {
	startTimer();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(아니면 게임 시작, 다시하기 등의 버튼을 제공해줘도 좋다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3. 전체 js 코드&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;margin: 20px 0px; caret-color: auto; background-color: #fafafa; padding: 20px 20px 22px; border: 1px dashed #c5c5c5; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;line-height: 1.6; margin-top: 0px;&quot;&gt;전체 변경된 코드는 길어서 접었다.&lt;/p&gt;
&lt;pre id=&quot;code_1603632852444&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numColsToCut = 4;
const numRowsToCut = 4;
const imagePieces = [];
const originImagePieces = [];
const resultPieces = new Array(numColsToCut*numRowsToCut);
function suffleList(array) {
  for (let i = array.length - 1; i &amp;gt; 0; i -= 1) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}
function suffleRendering() {
const box = document.getElementById('puzzle-box');
suffleList(imagePieces);
while(resultPieces.length &amp;gt; 0) {
    resultPieces.pop();
}
imagePieces.forEach(img =&amp;gt; {
	box.appendChild(img);
	});
}

const maxTime = 30;
let nowTime = 0;
let timer;
function startTimer() {
timer = window.setInterval(() =&amp;gt; {
	if(nowTime === maxTime) {
		window.clearInterval(timer);
		suffleRendering();
		window.alert('시간 종료!');
	}
	nowTime += 1;
	
}, 1000);
}

function allowDrop(ev) {
  ev.preventDefault();
}

function drag(ev) {
  ev.dataTransfer.setData(&quot;text&quot;, ev.target.id);
}

const completePuzzle = (puzzle, result) =&amp;gt; puzzle.every((piece, index) =&amp;gt;piece === result[index]);

function drop(ev) {
  ev.preventDefault();
  var data = ev.dataTransfer.getData(&quot;text&quot;);
  const child = document.getElementById(data);
  if (!data) return;
  if (resultPieces.length === 0) {
	startTimer();
  }
  if (ev.target.nodeName !== 'TD') {
    console.log(ev.target.parentNode);
    const currentImage = ev.target;
    const td = ev.target.parentNode;
    td.removeChild(currentImage);
    td.appendChild(child)
    const box = document.getElementById('puzzle-box');
    box.appendChild(currentImage);
    return;
  }
  ev.target.appendChild(document.getElementById(data));
  const index = ev.target.id.replace('piece_', '');
  resultPieces[Number(index)] = data;
  if (completePuzzle(originImagePieces, resultPieces)) window.alert('퍼즐 성공!');
}

function updateImageDisplay() {
  const preview = document.querySelector('.preview');
  const input = document.querySelector('input');
  const board = document.getElementById('puzzle-board');

  while(preview.firstChild) {
    preview.removeChild(preview.firstChild);
  }
  const curFiles = input.files;
    for(const file of curFiles) {
         const para = document.createElement('p');
         const imageItem = document.createElement('div');
           const img = new Image();
           img.onload = function() {
             const widthOfOnePiece = this.width/numColsToCut;
             const heightOfOnePiece = this.height/numRowsToCut;
             para.innerHTML = `${numColsToCut}X${numRowsToCut}로 생성된 퍼즐입니다.`;

              while(board.firstChild) {
                board.removeChild(board.firstChild);
              }
                 for(var x = 0; x &amp;lt; numColsToCut; ++x) {
                    let tableRow = document.createElement('tr');
                     for(var y = 0; y &amp;lt; numRowsToCut; ++y) {
                         var canvas = document.createElement('canvas');
                         canvas.width = widthOfOnePiece;
                         canvas.height = heightOfOnePiece;
                         var context = canvas.getContext('2d');
                         context.drawImage(img, y * widthOfOnePiece, x * heightOfOnePiece, widthOfOnePiece, heightOfOnePiece, 0, 0, canvas.width, canvas.height);
                         let pieceImage = new Image();
                         pieceImage.src = canvas.toDataURL();
                         pieceImage.id = canvas.toDataURL();
                         pieceImage.draggable = true;
                         pieceImage.ondragstart = drag;
                         let tableData = document.createElement('td');
                         tableData.ondrop = drop;
                         tableData.ondragover = allowDrop;
                         tableData.width = widthOfOnePiece;
                         tableData.height = heightOfOnePiece;
                         tableData.id = `piece_${x * numColsToCut + y}`;
                         tableRow.appendChild(tableData);
		   originImagePieces.push(pieceImage.src);
                         imagePieces.push(pieceImage);
                     }
                     board.appendChild(tableRow);
                 }
	suffleRendering();
	startTimer();
	}	
           img.src = URL.createObjectURL(file);

           imageItem.appendChild(img);
           imageItem.appendChild(para);
           preview.appendChild(imageItem);
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;퍼즐게임 끝.&lt;/p&gt;
&lt;p&gt;재미로 만들어본 프로젝트라 코드가 난잡하고 더럽지만,&lt;/p&gt;
&lt;p&gt;재미로 하기에는 아주 좋은 프로젝트였다.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;그럼 이만~.&lt;/span&gt;&lt;/p&gt;</description>
      <category>뚱땅뚱땅</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/23</guid>
      <comments>https://dog-foooot.tistory.com/23#entry23comment</comments>
      <pubDate>Mon, 26 Oct 2020 00:00:01 +0900</pubDate>
    </item>
    <item>
      <title>[HTML5/Javascript] 직소퍼즐 만들기 3: 퍼즐 조각 무작위로 섞기</title>
      <link>https://dog-foooot.tistory.com/22</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://dog-foooot.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dog-foooot.tistory.com/21&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1603628048107&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[HTML5/JavaScript] 직소퍼즐 만들기 - 2: 드래그드롭으로 퍼즐넣기&quot; data-og-description=&quot;dog-foooot.tistory.com/20 [HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력 안녕하세요, 김선진입니다. 주말에 블로그 글을 3개나 써야 해서(업보...) 시리즈물로 쓰려고 직소퍼즐 만들기를 기획했는데요,&quot; data-og-host=&quot;dog-foooot.tistory.com&quot; data-og-source-url=&quot;https://dog-foooot.tistory.com/21&quot; data-og-url=&quot;https://dog-foooot.tistory.com/21&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bThMU4/hyHZlG0w8o/iDHQjScN51VWu6DskDk0k1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b6T7WL/hyHZlG0w7Q/vhpwB9lI2C5jYeGUcxN5dk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bx634c/hyHZpWTOV8/IkEUF7ZLOchEg8zFtDR8Q0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://dog-foooot.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dog-foooot.tistory.com/21&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bThMU4/hyHZlG0w8o/iDHQjScN51VWu6DskDk0k1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b6T7WL/hyHZlG0w7Q/vhpwB9lI2C5jYeGUcxN5dk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bx634c/hyHZpWTOV8/IkEUF7ZLOchEg8zFtDR8Q0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[HTML5/JavaScript] 직소퍼즐 만들기 - 2: 드래그드롭으로 퍼즐넣기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;dog-foooot.tistory.com/20 [HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력 안녕하세요, 김선진입니다. 주말에 블로그 글을 3개나 써야 해서(업보...) 시리즈물로 쓰려고 직소퍼즐 만들기를 기획했는데요,&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;dog-foooot.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;저번 시리즈에 이어 이번에는 아래 3가지 기능을 퍼즐에 추가해봅시다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 퍼즐 조각을 랜덤하게 셔플&lt;/p&gt;
&lt;p&gt;2. 퍼즐 다시 시작&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존 코드에 이어서 작업합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. Suffle 알고리즘&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;1-1. O(N log N)&lt;/p&gt;
&lt;p&gt;간단하게 접근해보면 각각의 퍼즐 조각에 난수를 할당하고 난수를 기준으로 정렬하는 방법이 있을 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 이 방법은 정렬이 1번 들어가기 때문에 최소 정렬 알고리즘의 시간복잡도를 갖게 된다.&lt;/p&gt;
&lt;p&gt;심지어 위 방법으로 구현하면 셔플 빈도가 쏠리는 현상이 나타나게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/unIpu/btqLNrxkn0g/kUvasi42aluHvqMHwJpDB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/unIpu/btqLNrxkn0g/kUvasi42aluHvqMHwJpDB0/img.png&quot; data-alt=&quot;빈도 결과표 출처 https://ko.javascript.info/task/shuffle&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/unIpu/btqLNrxkn0g/kUvasi42aluHvqMHwJpDB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FunIpu%2FbtqLNrxkn0g%2FkUvasi42aluHvqMHwJpDB0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빈도 결과표 출처 https://ko.javascript.info/task/shuffle&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;1-2. O(N)&lt;/p&gt;
&lt;p&gt;정렬 없이 구현하려면 어떻게 해야 할까? 피셔 예이츠는 연필과 종이를 이용하여 동등한 확률을 가진 랜덤 알고리즘을 만들었고, 크누스는 그 알고리즘에서 불필요한 고정 부분을 제외한 현대판 알고리즘을 정의했다.&lt;/p&gt;
&lt;p&gt;구현은 아주 쉽다. 퍼즐 조각 리스트를 뒤에서부터 1바퀴 돌면서 현재 위치를 무작위로 생성된 index번째의 위치와 swap하면 된다. 그럼 마지막 퍼즐 조각까지 끝났을 때 무작위로 퍼즐 리스트가 섞여 있을 것이다.&lt;/p&gt;
&lt;p&gt;sudo 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1603630800409&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- To shuffle an array a of n elements (indices 0..n-1):
for i from n&amp;minus;1 downto 1 do
     j &amp;larr; random integer such that 0 &amp;le; j &amp;le; i
     exchange a[j] and a[i]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;198&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tQS78/btqLJVTexCI/I3HGaulsUu2qhQjNzqvc8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tQS78/btqLJVTexCI/I3HGaulsUu2qhQjNzqvc8K/img.png&quot; data-alt=&quot;피셔 예이츠 셔플 순서도 출처 https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Fisher_and_Yates&amp;amp;#39;_original_method&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tQS78/btqLJVTexCI/I3HGaulsUu2qhQjNzqvc8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtQS78%2FbtqLJVTexCI%2FI3HGaulsUu2qhQjNzqvc8K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;198&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;피셔 예이츠 셔플 순서도 출처 https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Fisher_and_Yates'_original_method&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. Suffle 적용&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;2-1. Suffle 코드 작성&lt;/p&gt;
&lt;pre id=&quot;code_1603631003498&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function suffleList(array) {
  for (let i = array.length - 1; i &amp;gt; 0; i -= 1) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}
function suffleRendering() {
const box = document.getElementById('puzzle-box');
suffleList(imagePieces);
imagePieces.forEach(img =&amp;gt; {
	box.appendChild(img);
	});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2-2. 퍼즐 게임 시작 시 suffleList 호출&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGi9Wg/btqLENPRU7J/FcW5aim0CIlmcaqXQO95lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGi9Wg/btqLENPRU7J/FcW5aim0CIlmcaqXQO95lk/img.png&quot; data-alt=&quot;셔플되지 않은 퍼즐조각&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGi9Wg/btqLENPRU7J/FcW5aim0CIlmcaqXQO95lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGi9Wg%2FbtqLENPRU7J%2FFcW5aim0CIlmcaqXQO95lk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;셔플되지 않은 퍼즐조각&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GTrQz/btqLNq6gpXf/l6hV23fiyoKGe7ZJhTGAt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GTrQz/btqLNq6gpXf/l6hV23fiyoKGe7ZJhTGAt1/img.png&quot; data-alt=&quot;셔플된 퍼즐조각&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GTrQz/btqLNq6gpXf/l6hV23fiyoKGe7ZJhTGAt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGTrQz%2FbtqLNq6gpXf%2Fl6hV23fiyoKGe7ZJhTGAt1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;셔플된 퍼즐조각&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3. 퍼즐 다시 시작&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;3-1. 셔플 기능을 등록한 버튼 생성&lt;/p&gt;
&lt;pre id=&quot;code_1603632607909&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button onclick=&quot;suffleRendering()&quot;&amp;gt;다시시작&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;4. 전체 js 코드&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p&gt;전체 변경된 코드는 길어서 접었다.&lt;/p&gt;
&lt;pre id=&quot;code_1603632701018&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numColsToCut = 4;
const numRowsToCut = 4;
const imagePieces = [];

function suffleList(array) {
  for (let i = array.length - 1; i &amp;gt; 0; i -= 1) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}
function suffleRendering() {
const box = document.getElementById('puzzle-box');
suffleList(imagePieces);
imagePieces.forEach(img =&amp;gt; {
   box.appendChild(img);
   });
}
function allowDrop(ev) {
  ev.preventDefault();
}

function drag(ev) {
  ev.dataTransfer.setData(&quot;text&quot;, ev.target.id);
}

function drop(ev) {
  ev.preventDefault();
  var data = ev.dataTransfer.getData(&quot;text&quot;);
  if (!data) return;
  if (ev.target.nodeName !== 'TD') {
    console.log(ev.target.parentNode);
    const currentImage = ev.target;
    const td = ev.target.parentNode;
    td.removeChild(currentImage);
    td.appendChild(document.getElementById(data))
    const box = document.getElementById('puzzle-box');
    box.appendChild(currentImage);
    return;
  }
  ev.target.appendChild(document.getElementById(data));
}

function updateImageDisplay() {
  const preview = document.querySelector('.preview');
  const input = document.querySelector('input');
  const board = document.getElementById('puzzle-board');

  while(preview.firstChild) {
    preview.removeChild(preview.firstChild);
  }
  const curFiles = input.files;
    for(const file of curFiles) {
         const para = document.createElement('p');
         const imageItem = document.createElement('div');
           const img = new Image();
           img.onload = function() {
             const widthOfOnePiece = this.width/numColsToCut;
             const heightOfOnePiece = this.height/numRowsToCut;
             para.innerHTML = `${numColsToCut}X${numRowsToCut}로 생성된 퍼즐입니다.`;

              while(board.firstChild) {
                board.removeChild(board.firstChild);
              }
                 for(var x = 0; x &amp;lt; numColsToCut; ++x) {
                    let tableRow = document.createElement('tr');
                     for(var y = 0; y &amp;lt; numRowsToCut; ++y) {
                         var canvas = document.createElement('canvas');
                         canvas.width = widthOfOnePiece;
                         canvas.height = heightOfOnePiece;
                         var context = canvas.getContext('2d');
                         context.drawImage(img, y * widthOfOnePiece, x * heightOfOnePiece, widthOfOnePiece, heightOfOnePiece, 0, 0, canvas.width, canvas.height);
                         let pieceImage = new Image();
                         pieceImage.src = canvas.toDataURL();
                         pieceImage.id = canvas.toDataURL();
                         pieceImage.draggable = true;
                         pieceImage.ondragstart = drag;
                         let tableData = document.createElement('td');
                         tableData.ondrop = drop;
                         tableData.ondragover = allowDrop;
                         tableData.width = widthOfOnePiece;
                         tableData.height = heightOfOnePiece;
                         tableRow.appendChild(tableData);
                         imagePieces.push(pieceImage);
                     }
                     board.appendChild(tableRow);
                 }
   suffleRendering();
   }   
           img.src = URL.createObjectURL(file);

           imageItem.appendChild(img);
           imageItem.appendChild(para);
           preview.appendChild(imageItem);
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;5. References&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.javascript.info/task/shuffle&quot;&gt;https://ko.javascript.info/task/shuffle&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Fisher_and_Yates'_original_method&quot;&gt;https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Fisher_and_Yates'_original_method&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;다음 포스트에서는 퍼즐을 게임으로 만들고 퍼즐 시리즈를 마치도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;오랜만에 아주 열심히 블로그를 썼습니다^^... (자극 받아서)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;그럼 이만~.&lt;/span&gt;&lt;/p&gt;</description>
      <category>뚱땅뚱땅</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/22</guid>
      <comments>https://dog-foooot.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 24 Oct 2020 16:16:51 +0900</pubDate>
    </item>
    <item>
      <title>[HTML5/JavaScript] 직소퍼즐 만들기 - 2: 드래그드롭으로 퍼즐넣기</title>
      <link>https://dog-foooot.tistory.com/21</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://dog-foooot.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dog-foooot.tistory.com/20&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1601254914491&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력&quot; data-og-description=&quot;안녕하세요, 김선진입니다. 주말에 블로그 글을 3개나 써야 해서(업보...) 시리즈물로 쓰려고 직소퍼즐 만들기를 기획했는데요, 코딩하는 동안 스크린샷찍는 걸 까먹어서... 1탄에서는 input과 canva&quot; data-og-host=&quot;dog-foooot.tistory.com&quot; data-og-source-url=&quot;https://dog-foooot.tistory.com/20&quot; data-og-url=&quot;https://dog-foooot.tistory.com/20&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k3uei/hyHFcDCQv6/LaK7DlltCjbekT7efrojDk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cqvJcv/hyHEXsW9rF/30RJ5KLLwNtpAq3GLlS9fK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cMxUah/hyHE5kc5ZG/kfgHtAGtgKkEobXivZNex0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://dog-foooot.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dog-foooot.tistory.com/20&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k3uei/hyHFcDCQv6/LaK7DlltCjbekT7efrojDk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cqvJcv/hyHEXsW9rF/30RJ5KLLwNtpAq3GLlS9fK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cMxUah/hyHE5kc5ZG/kfgHtAGtgKkEobXivZNex0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;안녕하세요, 김선진입니다. 주말에 블로그 글을 3개나 써야 해서(업보...) 시리즈물로 쓰려고 직소퍼즐 만들기를 기획했는데요, 코딩하는 동안 스크린샷찍는 걸 까먹어서... 1탄에서는 input과 canva&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;dog-foooot.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1601254917123&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numColsToCut = 4;
const numRowsToCut = 4;
function allowDrop(ev) {
  ev.preventDefault();
}

function drag(ev) {
  ev.dataTransfer.setData(&quot;text&quot;, ev.target.id);
}

function drop(ev) {
  ev.preventDefault();
  var data = ev.dataTransfer.getData(&quot;text&quot;);
  if (!data) return;
  if (ev.target.nodeName !== 'TD') {
    console.log(ev.target.parentNode);
    const currentImage = ev.target;
    const td = ev.target.parentNode;
    td.removeChild(currentImage);
    td.appendChild(document.getElementById(data))
    const box = document.getElementById('puzzle-box');
    box.appendChild(currentImage);
    return;
  }
  ev.target.appendChild(document.getElementById(data));
}

function updateImageDisplay() {
  const preview = document.querySelector('.preview');
  const input = document.querySelector('input');
  while(preview.firstChild) {
    preview.removeChild(preview.firstChild);
  }
  const curFiles = input.files;
    for(const file of curFiles) {
         const para = document.createElement('p');
         const imageItem = document.createElement('div');
           const img = new Image();
           img.onload = function() {
             const widthOfOnePiece = this.width/numColsToCut;
             const heightOfOnePiece = this.height/numRowsToCut;
             para.innerHTML = `${numColsToCut}X${numRowsToCut}로 생성된 퍼즐입니다.`;

              const imagePieces = [];
              const board = document.getElementById('puzzle-board');
              const box = document.getElementById('puzzle-box');
              while(board.firstChild) {
                board.removeChild(board.firstChild);
              }
                 for(var x = 0; x &amp;lt; numColsToCut; ++x) {
                    let tableRow = document.createElement('tr');
                     for(var y = 0; y &amp;lt; numRowsToCut; ++y) {
                         var canvas = document.createElement('canvas');
                         canvas.width = widthOfOnePiece;
                         canvas.height = heightOfOnePiece;
                         var context = canvas.getContext('2d');
                         context.drawImage(img, y * widthOfOnePiece, x * heightOfOnePiece, widthOfOnePiece, heightOfOnePiece, 0, 0, canvas.width, canvas.height);
                         let pieceImage = new Image();
                         pieceImage.src = canvas.toDataURL();
                         pieceImage.id = canvas.toDataURL();
                         pieceImage.draggable = true;
                         pieceImage.ondragstart = drag;
                         let tableData = document.createElement('td');
                         tableData.ondrop = drop;
                         tableData.ondragover = allowDrop;
                         tableData.width = widthOfOnePiece;
                         tableData.height = heightOfOnePiece;
                         box.appendChild(pieceImage);
                         tableRow.appendChild(tableData);
                         imagePieces.push(canvas.toDataURL());
                     }
                     board.appendChild(tableRow);
                 }
           }
           img.src = URL.createObjectURL(file);

           imageItem.appendChild(img);
           imageItem.appendChild(para);
           preview.appendChild(imageItem);
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1601254939251&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;script src=&quot;puzzle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;title&amp;gt;PUZZLE GAME&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;puzzle-container&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            퍼즐게임
        &amp;lt;/div&amp;gt;
        &amp;lt;label for=&quot;puzzle-input&quot;&amp;gt;
            이미지 선택
            &amp;lt;input onchange=&quot;updateImageDisplay()&quot; name=&quot;puzzle-input&quot; id=&quot;puzzle-input&quot; type=&quot;file&quot; accept=&quot;image/*&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;div class=&quot;preview&quot;&amp;gt;
            &amp;lt;p&amp;gt;선택한 이미지로 퍼즐을 만듭니다.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;table id=&quot;puzzle-board&quot;&amp;gt;

        &amp;lt;/table&amp;gt;
        &amp;lt;div id=&quot;puzzle-box&quot;&amp;gt;

        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&amp;lt;style&amp;gt;
    input {
    opacity: 0;
    }
    table {
    border: 1px solid black;
    border-spacing: 0;
    box-sizing: border-box;
    }
    td {
    border: 1px solid black;
    padding:0px !important;
    box-sizing: border-box;
    border-spacing: unset;
    border-collapse: collapse;
    }
    td img {
    display: block;
    }
    img:hover {
    box-sizing:border-box;
    outline: solid 2px red;
    }
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>뚱땅뚱땅</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/21</guid>
      <comments>https://dog-foooot.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 28 Sep 2020 10:02:25 +0900</pubDate>
    </item>
    <item>
      <title>[HTML5/JavaScript] 직소퍼즐 만들기 - 1 : 파일 입력</title>
      <link>https://dog-foooot.tistory.com/20</link>
      <description>&lt;p&gt;안녕하세요, 김선진입니다.&lt;/p&gt;
&lt;p&gt;주말에 블로그 글을 3개나 써야 해서(업보...) 시리즈물로 쓰려고 직소퍼즐 만들기를 기획했는데요,&lt;/p&gt;
&lt;p&gt;코딩하는 동안 스크린샷찍는 걸 까먹어서...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1탄에서는 input과 canvas를 이용해서 입력받은 이미지를 4*4로 쪼개서 퍼즐 박스에 넣는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1601254816704&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numColsToCut = 4;
const numRowsToCut = 4;
function allowDrop(ev) {
  ev.preventDefault();
}
function updateImageDisplay() {
  const preview = document.querySelector('.preview');
  const input = document.querySelector('input');
  while(preview.firstChild) {
    preview.removeChild(preview.firstChild);
  }
  const curFiles = input.files;
    for(const file of curFiles) {
         const para = document.createElement('p');
         const imageItem = document.createElement('div');
           const img = new Image();
           img.onload = function() {
             const widthOfOnePiece = this.width/numColsToCut;
             const heightOfOnePiece = this.height/numRowsToCut;
             para.innerHTML = `${numColsToCut}X${numRowsToCut}로 생성된 퍼즐입니다.`;

              const box = document.getElementById('puzzle-box');
              while(board.firstChild) {
                board.removeChild(board.firstChild);
              }
                 for(var x = 0; x &amp;lt; numColsToCut; ++x) {
                     for(var y = 0; y &amp;lt; numRowsToCut; ++y) {
                         var canvas = document.createElement('canvas');
                         canvas.width = widthOfOnePiece;
                         canvas.height = heightOfOnePiece;
                         var context = canvas.getContext('2d');
                         context.drawImage(img, y * widthOfOnePiece, x * heightOfOnePiece, widthOfOnePiece, heightOfOnePiece, 0, 0, canvas.width, canvas.height);
                         let pieceImage = new Image();
                         pieceImage.src = canvas.toDataURL();
                         pieceImage.id = canvas.toDataURL();
                         pieceImage.draggable = true;
                         pieceImage.ondragstart = drag;
                         box.appendChild(pieceImage);
                     }
                 }
           }
           img.src = URL.createObjectURL(file);

           imageItem.appendChild(img);
           imageItem.appendChild(para);
           preview.appendChild(imageItem);
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1601254732538&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;script src=&quot;puzzle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;title&amp;gt;PUZZLE GAME&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;puzzle-container&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            퍼즐게임
        &amp;lt;/div&amp;gt;
        &amp;lt;label for=&quot;puzzle-input&quot;&amp;gt;
            이미지 선택
            &amp;lt;input onchange=&quot;updateImageDisplay()&quot; name=&quot;puzzle-input&quot; id=&quot;puzzle-input&quot; type=&quot;file&quot; accept=&quot;image/*&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;div class=&quot;preview&quot;&amp;gt;
            &amp;lt;p&amp;gt;선택한 이미지로 퍼즐을 만듭니다.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id=&quot;puzzle-box&quot;&amp;gt;

        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&amp;lt;style&amp;gt;
    input {
    opacity: 0;
    }
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>뚱땅뚱땅</category>
      <author>퀸디</author>
      <guid isPermaLink="true">https://dog-foooot.tistory.com/20</guid>
      <comments>https://dog-foooot.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 28 Sep 2020 10:01:04 +0900</pubDate>
    </item>
  </channel>
</rss>