<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>느낌으로 남지않기</title>
    <link>https://phant0m.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 15:41:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>freddy</managingEditor>
    <image>
      <title>느낌으로 남지않기</title>
      <url>https://tistory1.daumcdn.net/tistory/1857138/attach/a3ac392a529a4e5aa32e01a48c33daab</url>
      <link>https://phant0m.tistory.com</link>
    </image>
    <item>
      <title>모바일 웹에서 키패드 높이 계산</title>
      <link>https://phant0m.tistory.com/49</link>
      <description>&lt;div style=&quot;background-color: #1f1f1f; color: #cccccc;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// 모바일 키패드 높이 계산&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #c586c0;&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;visualViewport&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;visualViewport&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;'resize'&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; {&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;visualViewportHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;visualViewport&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color: #4fc1ff;&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;windowHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;innerHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;keyboardHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;windowHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;visualViewportHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;;&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;setKeyboardHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #9cdcfe;&quot;&gt;keyboardHeight&lt;/span&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt;);&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; });&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #cccccc;&quot;&gt; }&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>ERROR.LOG</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/49</guid>
      <comments>https://phant0m.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 10 Aug 2023 19:20:04 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD] 아무것도 모르는 상태에서 Github Actions 를 이용한 TestFlight 업로드 자동화(삽질 회고)</title>
      <link>https://phant0m.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions로 CI/CD를 구현하여 앱 빌드 &amp;amp; TestFlight 업로드 자동화를 만들어야 하는 일이 생겨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무것도 모르는 상태에서 1주일 동안 삽질해가며 마침내 구현하였고 모르고 시작하는 분들의 어려움을 조금이나마 덜고자 진행 내용에 대한 회고를 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 정말 진행의 90%를 보고 따라 했던 좋은 포스팅이 있어 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sujinnaljin.medium.com/ci-cd-github-actions-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-testflight-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-8ecdbeb227a3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sujinnaljin.medium.com/ci-cd-github-actions-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-testflight-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-8ecdbeb227a3&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660047803334&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[CI/CD] Github Actions 를 이용한 TestFlight 업로드 자동화&quot; data-og-description=&quot;release branch로 push 하면 TestFlight 업로드하기&quot; data-og-host=&quot;sujinnaljin.medium.com&quot; data-og-source-url=&quot;https://sujinnaljin.medium.com/ci-cd-github-actions-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-testflight-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-8ecdbeb227a3&quot; data-og-url=&quot;https://sujinnaljin.medium.com/ci-cd-github-actions-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-testflight-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-8ecdbeb227a3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ww3N2/hyPoseiJKK/9QyeggSKUXsxWKSDzmcnG1/img.png?width=1200&amp;amp;height=481&amp;amp;face=0_0_1200_481,https://scrap.kakaocdn.net/dn/bZVGKR/hyPol7lgNo/YLEbhx5xRjxWWyQqQKkdNk/img.png?width=1400&amp;amp;height=630&amp;amp;face=0_0_1400_630,https://scrap.kakaocdn.net/dn/bxGB8Z/hyPojazYlh/aqKbLo5YjLhXkCymG0W1q1/img.png?width=1400&amp;amp;height=588&amp;amp;face=0_0_1400_588&quot;&gt;&lt;a href=&quot;https://sujinnaljin.medium.com/ci-cd-github-actions-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-testflight-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-8ecdbeb227a3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sujinnaljin.medium.com/ci-cd-github-actions-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-testflight-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9E%90%EB%8F%99%ED%99%94-8ecdbeb227a3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ww3N2/hyPoseiJKK/9QyeggSKUXsxWKSDzmcnG1/img.png?width=1200&amp;amp;height=481&amp;amp;face=0_0_1200_481,https://scrap.kakaocdn.net/dn/bZVGKR/hyPol7lgNo/YLEbhx5xRjxWWyQqQKkdNk/img.png?width=1400&amp;amp;height=630&amp;amp;face=0_0_1400_630,https://scrap.kakaocdn.net/dn/bxGB8Z/hyPojazYlh/aqKbLo5YjLhXkCymG0W1q1/img.png?width=1400&amp;amp;height=588&amp;amp;face=0_0_1400_588');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[CI/CD] Github Actions 를 이용한 TestFlight 업로드 자동화&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;release branch로 push 하면 TestFlight 업로드하기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sujinnaljin.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 포스터를 보면서 1편(빌드)과 2편(자동화)을 보면서 정말 많이 이해하고 도움을 받아서 너무 감사합니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소개해준 글을 보면서 진행하면 되고 진행하면서 새로 알게 된 내용과 막힌 부분들 위주로 공유하겠습니다. 그럼 시작하겠습니다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;최종 결과물: main branch에 push 하면 build 테스트와 TestFlight에 등록되는 것&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삽질 최종 결과물 yml 파일 (위치는 .github/workflows/build_on_main.yml)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 job 이 두 개입니다 (1.build,&amp;nbsp; 2. deploy(testflight)) 필요하신 부분만 구성하여 진행하여도 상관없음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660048916103&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# workflow 의 이름
name: Run Build

on:
  # main 브랜치에 push 이벤트가 일어났을때 해당 workflow 를 trigger
  push:
    branches: [ main ]

# workflow의 실행은 하나 이상의 job으로 구성 됨
jobs:
  # 이 workflow 는 &quot;build&quot;, &quot;deploy&quot; 라는 job 으로 구성
  build:
    # job이 실행될 환경 - 최신 mac os
    runs-on: macos-latest

    # Step은 job의 일부로 실행될 일련의 task들을 나타냄
    steps:
    # uses 키워드를 통해 Github Actions에서 기본으로 제공하는 액션을 사용 가능. 아래 액션은 repository 에 체크아웃하는 것
    - uses: actions/checkout@v2
    # npm install 
    - name: Run yarn install  
      run: yarn install
    # shell 이용해서 하나의 command 수행
    - name: Start xcode build  
      run: |
        cd ios &amp;amp;&amp;amp; pod install --repo-update --clean-install &amp;amp;&amp;amp; cd ..
        xcodebuild -workspace ios/SampleApp.xcworkspace -scheme SampleApp -destination 'platform=iOS Simulator,name=iPhone 11 Pro,OS=latest';
  deploy:
    # job이 실행될 환경 - 최신 mac os 
    runs-on: macos-latest
    env:
      # app archive 및 export 에 쓰일 환경 변수 설정
      XC_WORKSPACE: ${{ 'ios/SampleApp.xcworkspace' }}
      XC_SCHEME: ${{ 'SampleApp' }}
      XC_ARCHIVE: ${{ 'SampleApp.xcarchive' }}
      
      # certificate 
      ENCRYPTED_CERT_FILE_PATH: ${{ '.github/secrets/certification.p12.gpg' }}
      DECRYPTED_CERT_FILE_PATH: ${{ '.github/secrets/certification.p12' }}
      CERT_ENCRYPTION_KEY: ${{ secrets.CERTS_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호
      
      # provisioning
      ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/SampleApp_GithubActions.mobileprovision.gpg' }}
      DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/SampleApp_GithubActions.mobileprovision' }}
      PROVISIONING_ENCRYPTION_KEY: ${{ secrets.PROVISION_ENCRYPTION_PWD }} # gpg로 파일 암호화할 때 사용한 암호
      
      # certification export key
      CERT_EXPORT_KEY: ${{ secrets.CERT_EXPORT_PWD }}
      
      # 키체인을 새로 생성하고 초기화 하는데 사용 됩니다. 즉 임시 키 체인을 만들때 필요
      KEYCHAIN: ${{ 'test.keychain' }}
      
    # Step은 job의 일부로 실행될 일련의 task들을 나타냄
    steps:
      # 단계별 task 를 나타낼 이름
      - name: Select latest Xcode
        # shell 이용해서 하나의 command 수행
        run: &quot;sudo xcode-select -s /Applications/Xcode.app&quot;
      - name: Checkout project
        # uses 키워드를 통해 Github Actions에서 기본으로 제공하는 액션을 사용 가능. 아래 액션은 repository 에 체크아웃하는 것
        uses: actions/checkout@v2
      - name: Configure Keychain 
        # 키체인 초기화 - 임시 키체인 생성
        run: |
          security create-keychain -p &quot;&quot; &quot;$KEYCHAIN&quot;
          security list-keychains -s &quot;$KEYCHAIN&quot;
          security default-keychain -s &quot;$KEYCHAIN&quot;
          security unlock-keychain -p &quot;&quot; &quot;$KEYCHAIN&quot;
          security set-keychain-settings
      - name : Configure Code Signing
        run: |
          # certificate 복호화
          gpg -d -o &quot;$DECRYPTED_CERT_FILE_PATH&quot; --pinentry-mode=loopback --passphrase &quot;$CERT_ENCRYPTION_KEY&quot; &quot;$ENCRYPTED_CERT_FILE_PATH&quot;
          # provisioning 복호화
          gpg -d -o &quot;$DECRYPTED_PROVISION_FILE_PATH&quot; --pinentry-mode=loopback --passphrase &quot;$PROVISIONING_ENCRYPTION_KEY&quot; &quot;$ENCRYPTED_PROVISION_FILE_PATH&quot;
          # security를 사용하여 인증서와 개인 키를 새로 만든 키 체인으로 가져옴
          security import &quot;$DECRYPTED_CERT_FILE_PATH&quot; -k &quot;$KEYCHAIN&quot; -P &quot;$CERT_EXPORT_KEY&quot; -A
          security set-key-partition-list -S apple-tool:,apple: -s -k &quot;&quot; &quot;$KEYCHAIN&quot;
          # Xcode에서 찾을 수 있는 프로비저닝 프로필 설치하기 위해 우선 프로비저닝 디렉토리를 생성
          mkdir -p &quot;$HOME/Library/MobileDevice/Provisioning Profiles&quot;
          # 디버깅 용 echo 명령어
          echo `ls .github/secrets/*.mobileprovision`
          # 모든 프로비저닝 프로파일을 rename 하고 위에서 만든 디렉토리로 복사하는 과정
          for PROVISION in `ls .github/secrets/*.mobileprovision`
            do
              UUID=`/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin &amp;lt;&amp;lt;&amp;lt; $(security cms -D -i ./$PROVISION)`
            cp &quot;./$PROVISION&quot; &quot;$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision&quot;
          done
      - name: Run yarn install  
        run: yarn install
      - name: Archive app  
        # 빌드 및 아카이브
        run: |
          cd ios &amp;amp;&amp;amp; pod install --repo-update --clean-install &amp;amp;&amp;amp; cd ..
          xcodebuild clean archive -workspace $XC_WORKSPACE -scheme $XC_SCHEME -configuration release -archivePath $XC_ARCHIVE
        # export 를 통해 ipa 파일 만듦
      - name: Export app
        run: |
          xcodebuild -exportArchive -archivePath $XC_ARCHIVE -exportOptionsPlist ExportOptions.plist -exportPath . -allowProvisioningUpdates
      - name: Install private API key P8   &amp;amp;&amp;amp; Upload app to TestFlight  
        # key 파일 생성 &amp;amp; TestFlight 업로드 
        env:
          PRIVATE_API_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
          API_KEY: ${{ secrets.APPSTORE_API_KEY_ID }}
          ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
        run: | 
          mkdir -p ./private_keys
          echo &quot;$PRIVATE_API_KEY&quot; &amp;gt; ./private_keys/AuthKey_$API_KEY.p8
          ls -al
          xcrun altool --output-format xml --upload-app -f SampleApp.ipa --type ios --apiKey $API_KEY --apiIssuer $ISSUER_ID&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;size16&quot;&gt;우선 소개해준 포스팅에 나와있는 yml 파일을 복붙 하여 놓고 나에게 맞는 부분만 수정하여 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;진행하면서 막힌 부분&amp;nbsp;&lt;br /&gt;첫 번째&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.&amp;nbsp; Job - Build (Failed)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1 -1 경로 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 해당 repo를 가상 환경으로 checkout 한 뒤 build 명령을 실행할 때 workspace 경로(ios/SampleApp.xcworkspace)를 알기까지 삽질하였다.. 예제는 ProjectName/ProjectName.xcworkspace로 되어 있어 SampleApp/SampleApp.xcworkspace 이런 식으로 적었더니 계속 경로를 못 찾아서 삽질하였다. 현재 경로 기준에서(Project)에서 SampleApp.xcworkspace 경로를 적어주며 해결하였다. 나의 경우 ios/SampleApp.xcworkspace&lt;/p&gt;
&lt;pre id=&quot;code_1660050240201&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; xcodebuild -workspace ios/SampleApp.xcworkspace -scheme SampleApp -destination 'platform=iOS Simulator,name=iPhone 11 Pro,OS=latest';&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;size16&quot;&gt;&lt;b&gt;1-2 Pod install failed&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 ReactNative로 진행하였는데 pod install 시 계속 실패 에러가 났다.&lt;/p&gt;
&lt;pre id=&quot;code_1660051242159&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; cd ios &amp;amp;&amp;amp; pod install --repo-update --clean-install &amp;amp;&amp;amp; cd ..
 xcodebuild -workspace ios/SampleApp.xcworkspace -scheme SampleApp -destination 'platform=iOS Simulator,name=iPhone 11 Pro,OS=latest';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패 에러&lt;/p&gt;
&lt;pre id=&quot;code_1660051283890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[!] Invalid `Podfile` file: cannot load such file -- /Users/runner/work/SampleApp/SampleApp/node_modules/react-native/scripts/react_native_pods.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 node_modules에 있는 의존성이 필요한 상황이었는데 의존성을 설치하지 않고 pod install을 해서 에러가 났다. 그래서 나의 경우는 yarn 명령을 통해 의존성을 install 하고 pod install &amp;amp;&amp;amp; build 명령을 실행하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1660051527751&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# uses 키워드를 통해 Github Actions에서 기본으로 제공하는 액션을 사용 가능. 아래 액션은 repository 에 체크아웃하는 것
    - uses: actions/checkout@v2
    # npm install 
    - name: Run yarn install  
      run: yarn install
    # shell 이용해서 하나의 command 수행
    - name: Start xcode build  
      run: |
        cd ios &amp;amp;&amp;amp; pod install --repo-update --clean-install &amp;amp;&amp;amp; cd ..
        xcodebuild -workspace ios/SampleApp.xcworkspace -scheme SampleApp -destination 'platform=iOS Simulator,name=iPhone 11 Pro,OS=latest';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod install 하고 다시 cd .. 해서 나간 이유는 두 번째 명령인 build에 workspace 경로를 ios/SampleApp.xcworkspace로 잡아 놓았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 나의 경우 첫 번째 build Job을 성공적으로 마칠 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size20&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;진행하면서 막힌 부분&lt;/b&gt;&lt;br /&gt;&lt;b&gt;두 번째&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&amp;nbsp; Job - Deploy (Failed)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 Job은 가상 환경에서 TestFlight에 등록하려면 &lt;u&gt;인증에 필요한 작업과 환경변수 설정만 잘하면&lt;/u&gt; 순조롭게 진행이 되는데 한 가지 해당 포스팅에 나와있지 않은 부분과 환경변수 설정을 잘못해서 많은 시간을 삽질하였다...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;1186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euAjh5/btrJmwnpyAD/kKs4KS1lIrkhsRcXTWGSt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euAjh5/btrJmwnpyAD/kKs4KS1lIrkhsRcXTWGSt1/img.png&quot; data-alt=&quot;여기 실패만 20번&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euAjh5/btrJmwnpyAD/kKs4KS1lIrkhsRcXTWGSt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeuAjh5%2FbtrJmwnpyAD%2FkKs4KS1lIrkhsRcXTWGSt1%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;1758&quot; height=&quot;1186&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;1186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;여기 실패만 20번&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2-1 권한 에러&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 해당 에러 원인을 찾기까지 3일 가까이 쓴 것 같다..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBde6u/btrJnbwrMkV/MSYZpEsNs5tCFpA18VNL60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBde6u/btrJnbwrMkV/MSYZpEsNs5tCFpA18VNL60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBde6u/btrJnbwrMkV/MSYZpEsNs5tCFpA18VNL60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBde6u%2FbtrJnbwrMkV%2FMSYZpEsNs5tCFpA18VNL60%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;1742&quot; height=&quot;700&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 App store private api key를 secrets에서 설정할 때 value를 잘못 넣은 문제였다. value를 넣을 때 다운로드한 그대로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점선으로 구분되는 BEGIN, END 라인까지 전부 복사해서 넣어야 정상적으로 인식하는데 나의 경우 가운데에 있는 api key 만 복사해서 넣었더니 계속 에러가 났다... 멘붕..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-------BEGIN PRIVATE KEY ----------&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api key...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;------END PRIVATE KEY-----------&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 소개해준 포스팅에는 나와있지 않은 내용의 에러를 마주쳤는데 내용을 보면 해당 경로에서 인증키 파일을 찾을 수 없다는 에러이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #000000; color: #c9d1d9;&quot;&gt;NSLocalizedFailureReason=The file 'AuthKey_***.p8' could not be found in any of these locations: '~/work/ SampleApp/SampleApp/private_keys', '~/private_keys', '~/.private_keys', '~/.appstoreconnect/private_keys'., NSLocalizedDescription=Failed to load AuthKey file.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Apple-Actions/upload-testflight-build/issues/27&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Apple-Actions/upload-testflight-build/issues/27&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660054014113&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Error uploading after updating to XCode 13 &amp;middot; Issue #27 &amp;middot; Apple-Actions/upload-testflight-build&quot; data-og-description=&quot;I'm getting this error even though I can see my .p8 file in ~/private_keys Is there something I need to change or do on my mac action runner machine? Error message: The file 'AuthKey_***.p8...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Apple-Actions/upload-testflight-build/issues/27&quot; data-og-url=&quot;https://github.com/Apple-Actions/upload-testflight-build/issues/27&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dPo828/hyPol7rcd4/BZpDHRd81MfIQylQiuThck/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Apple-Actions/upload-testflight-build/issues/27&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Apple-Actions/upload-testflight-build/issues/27&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dPo828/hyPol7rcd4/BZpDHRd81MfIQylQiuThck/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Error uploading after updating to XCode 13 &amp;middot; Issue #27 &amp;middot; Apple-Actions/upload-testflight-build&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I'm getting this error even though I can see my .p8 file in ~/private_keys Is there something I need to change or do on my mac action runner machine? Error message: The file 'AuthKey_***.p8...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아본 결과 &lt;span style=&quot;background-color: #000000; color: #c9d1d9;&quot;&gt;~/work/ SampleApp/SampleApp/private_keys', '~/private_keys', '~/.private_keys', '~/.appstoreconnect/private_keys'&amp;nbsp; &lt;/span&gt;해당 경로를 순서대로 찾아가며 AuthKey 파일 을 찾는다고 한다. 그래서 나의 경우는 현재 경로에서 private_keys 폴더를 만들고 그 안에 echo 명령으로 secrets 에 등록했던 api private key 를 파일로 만들어서 넣었다.&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1660054321724&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- name: Install private API key P8   &amp;amp;&amp;amp; Upload app to TestFlight  
        # key 파일 생성 &amp;amp; TestFlight 업로드 
        env:
          PRIVATE_API_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
          API_KEY: ${{ secrets.APPSTORE_API_KEY_ID }}
          ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
        run: | 
          mkdir -p ./private_keys
          echo &quot;$PRIVATE_API_KEY&quot; &amp;gt; ./private_keys/AuthKey_$API_KEY.p8
          ls -al
          xcrun altool --output-format xml --upload-app -f SampleApp.ipa --type ios --apiKey $API_KEY --apiIssuer $ISSUER_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 현재 경로는 Root Project 의 경로이다. (~/work/SampleApp/SampleApp)&amp;nbsp; 결과물의 경로는 (~/work/SampleApp/SampleApp/private_keys/AuthKey_***.p8) 이 만들어 진다 여기서 새로 알게 된 명령 ex) echo Hello &amp;gt; test.txt 하면 해당 내용이 파일로 만들어 진다는 것.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우는 소개해준 포스팅에서 사용한 &lt;b&gt;apple-actions/upload-testflight-build@v1&amp;nbsp;&lt;/b&gt;요것을 사용하지 않고 수동으로 작성하였다어차피 같은 명령을 실행하게 되긴함&lt;/p&gt;
&lt;pre id=&quot;code_1660054627607&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Upload app to TestFlight
        uses: apple-actions/upload-testflight-build@v1
        with:
          app-path: 'HappyChuseok.ipa'
          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
          api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XyxgN/btrJmvWllZ1/IgMFAgP7gkN9bOivEr6kEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XyxgN/btrJmvWllZ1/IgMFAgP7gkN9bOivEr6kEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XyxgN/btrJmvWllZ1/IgMFAgP7gkN9bOivEr6kEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXyxgN%2FbtrJmvWllZ1%2FIgMFAgP7gkN9bOivEr6kEK%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;1964&quot; height=&quot;336&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파란 부분의 command가 &lt;b&gt;apple-actions/upload-testflight-build@v1&amp;nbsp;&amp;nbsp;&lt;/b&gt;사용했을 때 자동으로 실행되는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아래 코드 처럼 수동으로 TestFlight 업로드 명령 실행함)&lt;/p&gt;
&lt;pre id=&quot;code_1660054867771&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;run: | 
          mkdir -p ./private_keys
          echo &quot;$PRIVATE_API_KEY&quot; &amp;gt; ./private_keys/AuthKey_$API_KEY.p8
          ls -al
          xcrun altool --output-format xml --upload-app -f SampleApp.ipa --type ios --apiKey $API_KEY --apiIssuer $ISSUER_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 ipa 파일 경로는 위에서 실행한 내용 export 에 따라 현재 경로(~/work/SampleApp/SampleApp/SampleApp.ipa)에 있어서 SampleApp.ipa 를 적었음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 이렇게 하면 권한문제를 해결하고 다음으로 넘어갈 수 있었다. 이렇게 하면 이제 완료인가 싶었지만..몇 번의 간단한 오류들이 더 나고 성공하였다..!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자잘한 오류들..&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- AppIcon 을 만들지 않고 TestFlight에 등록 하려고 한 문제.(AppIcon 등록하여 해결)&lt;br /&gt;&amp;nbsp;- 카메라 권한 요청 시 사용 설명이 없어 에러 (설명 추가 해결)&lt;br /&gt;&amp;nbsp;- 오류 없이 success 가 됐다는걸 확인하고 TestFlight를 확인했지만 없는 경우 (이 경우 메일로 문제가 된 부분이 메일로 날라옴 해당 부분 수정 후 해결)&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;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>설치</category>
      <category>CI/CD</category>
      <category>Github Actions</category>
      <category>ios</category>
      <category>testflight</category>
      <category>오류</category>
      <category>자동화</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/48</guid>
      <comments>https://phant0m.tistory.com/48#entry48comment</comments>
      <pubDate>Tue, 9 Aug 2022 23:47:12 +0900</pubDate>
    </item>
    <item>
      <title>CSS 단위 em 과 rem 정리</title>
      <link>https://phant0m.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;절대적인 속성&lt;/b&gt;에 사용하는 단위에는 px를 사용하고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;상대적인 속성&lt;/b&gt;에는 %, v*(뷰포트), em, rem을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;단위를 사용하는 기준&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;첫 번째&lt;/b&gt;로 나누는 기준은&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;부모 요소의 사이즈&lt;/b&gt;에 따라서 사이즈가 변경이 돼야 한다면&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;&amp;nbsp; %, em 을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부모 요소와는 상관없이 &lt;b&gt;브라우저 사이즈&lt;/b&gt;에 대해서 반응해야 한다면&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt; v*(뷰포트), rem을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;두 번째&lt;/b&gt;로 나누는 기준은&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;요소의 너비와 높이&lt;/b&gt;에 따라서 사이즈가 변경되어야 한다면&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt; %, v*(뷰포트) 를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;font 사이즈&lt;/b&gt;에 따라서 사이즈가 변경되어야 한다면&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt; em, rem을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;rem&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;rem을 사용해서 스타일을 적용하게 되면 &lt;b&gt;root 요소 (html)의 font size에 따라&lt;/b&gt;서 크기가 결정되기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;페이지 내에 어느곳에서 사용하여도 동일한 크기를 유지합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 이유는 부모 컨테이너에 있는 font size와는 상관없이 루트 요소의 font size 기준으로 하기 때문에 &lt;b&gt;어느 박스 안에서 사용하여도 동일한 크기를 유지&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;em&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;em을 사용해서 스타일을 적용하게 되면 페이지 내에 크기가 다르게 적용될 수 있습니다. 그 이유는 em 요소는 부묘 요소의 font size에 따라 상대적으로 변하기 때문입니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해당 컴포넌트가 페이지 어디에서도 동일한 크기로 적용&lt;/b&gt;되어야 한다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;rem&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 사용하고 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;반대로 해당 컴포넌트가 어디에 사용되냐에 따라서 즉&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부모 요소에 따라서 사이즈가 유동적으로 변경되어야 한다면&lt;/b&gt;(font size에 따라 발생할 수 있는 padding, margin 등)&amp;nbsp;&lt;b&gt;em&lt;/b&gt;을 사용해서 만드는 게 좋습니다&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결론&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;box 자체를 사이즈를 결정할 때는 %, v*, flex를 이용하여 유동적으로 만드는 것이 좋고 요소의 font size를 결정할 때는 root를 상대로 변경되어야 한다면 rem을 부모 요소 상대로 변경되어야 한다면 em을 사용하면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Tip&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;font-size를 정할 때는 부모 요소 기준으로 계산되는 &lt;b&gt;rem을&lt;/b&gt; 사용하는 것을 추천합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추천 사이트: px to em, rem&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://codebeautify.org/rem-to-px-converter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codebeautify.org/rem-to-px-converter&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1653274553842&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;REM to PX Converter Online&quot; data-og-description=&quot;REM to PX Converter online tool is helps to convert REM value into PX value.&quot; data-og-host=&quot;codebeautify.org&quot; data-og-source-url=&quot;https://codebeautify.org/rem-to-px-converter&quot; data-og-url=&quot;https://codebeautify.org/rem-to-px-converter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HttQX/hyOvJHsfvH/vyyihRezVqPGCpn4ORjti0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/iODMP/hyOujKoc0d/iSRieknSgvKcZmu9Ut5c70/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://codebeautify.org/rem-to-px-converter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codebeautify.org/rem-to-px-converter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HttQX/hyOvJHsfvH/vyyihRezVqPGCpn4ORjti0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/iODMP/hyOujKoc0d/iSRieknSgvKcZmu9Ut5c70/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;REM to PX Converter Online&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;REM to PX Converter online tool is helps to convert REM value into PX value.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codebeautify.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;출처: &amp;nbsp;&amp;nbsp;&lt;a title=&quot;em 과 rem 정리&quot; href=&quot;https://www.youtube.com/watch?v=xWMKz9NCD0k&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/watch?v=xWMKz9NCD0k&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=xWMKz9NCD0k&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bNw2Fq/hyOuokD4RX/u2sP4FmEAlyj3uXJwf47O1/img.jpg?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480&quot; data-video-width=&quot;640&quot; data-video-height=&quot;480&quot; data-video-origin-width=&quot;640&quot; data-video-origin-height=&quot;480&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/xWMKz9NCD0k&quot; width=&quot;640&quot; height=&quot;480&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>CSS</category>
      <category>em</category>
      <category>em과rem차이</category>
      <category>rem</category>
      <category>반응형</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/46</guid>
      <comments>https://phant0m.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 23 May 2022 11:53:05 +0900</pubDate>
    </item>
    <item>
      <title>BOM (Browser Object Model)</title>
      <link>https://phant0m.tistory.com/38</link>
      <description>&lt;h1&gt;BOM을 알아야 하는 이유&lt;/h1&gt;
&lt;p&gt;❗유저에게 경고창을 띄우고 싶은 경우,&lt;br /&gt;❗유저의 yes or no와 같은 선택에 따라 다른 응답을 보여주고 싶은 경우&lt;br /&gt;❗유저가 브라우저 창을 닫기 전에 정말 떠날 것인지 확인하고 싶은 경우&lt;br /&gt;❗유저가 접속한 환경을 알고 싶은 경우&lt;br /&gt;❗현재 url 위치 및, 접속 history를 알고 싶은 경우&lt;/p&gt;
&lt;p&gt;여러 ui를 직접 만들고 자바스크립트로 직접 제어할 수도 있지만, 브라우저에서 제공하는 기본 API를 사용한다면 훨씬 빠로고 간단하게 프로토타이핑을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;BOM(Browser Object Model)은 웹 브라우저 환경의 다양한 기능을 객체처럼 다루는 모델입니다. 대부분의 브라우저에서 구현은 되어있지만, 정의된 표준이 없어 브라우저 제작사 마다 세부사항이 다르고 다소 한정적이라는 특징이 있습니다. BOM의 역할은 웹 브라우저의 버튼, URL 주소 입력 창, 타이틀 바 등 웹브라우저 윈도우 및 웹페이지의 일부분을 제어할수 있게끔 하는 것입니다. window 객체를 통해 접근이 가능합니다. 아래는 대표적인 BOM 객체들입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;1) window&lt;/b&gt;: Global Context. 브라우저 창 객체&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2) screen&lt;/b&gt;: 사용자 환경의 디스플레이 정보 객체&lt;/li&gt;
&lt;li&gt;&lt;b&gt;3) location&lt;/b&gt;: 현재 페이지의 url을 다루는 객체&lt;/li&gt;
&lt;li&gt;&lt;b&gt;4) navigator&lt;/b&gt;: 웹브라우저 및 브라우저 환경 정보 객체&lt;/li&gt;
&lt;li&gt;&lt;b&gt;5) history&lt;/b&gt;: 현재의 브라우저가 접근했던 URL history&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;beforeunload이벤트를 사용하면 페이지를 벗어날 때 확인 메세지를 표시할 수 있습니다. 단, 크롬에서는 경고창에 문구`가 나타나진 않습니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1608605770443&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.onbeforeunload = function() { return '작성 중인 메시지가 있습니다.' }&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;BOM은 웹 브라우저의 기능들을 객체처럼 다루는 모델이다.&lt;/li&gt;
&lt;li&gt;정의된 표준이 없어 브라우저 제작사마다 구현이 다르다.&lt;/li&gt;
&lt;li&gt;BOM에서 제공하는 API를 적극 활용하면, 사용자 경험을 향상시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>HTML</category>
      <category>BOM</category>
      <category>BOM 이란?</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/38</guid>
      <comments>https://phant0m.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 22 Dec 2020 11:57:11 +0900</pubDate>
    </item>
    <item>
      <title>JS 이벤트 위임</title>
      <link>https://phant0m.tistory.com/37</link>
      <description>&lt;p&gt;이벤트 리스너를 지정하는 요소가 많으면 많을수록 페이지의 실행 속도는 느려집니다. 그래서 효율적으로 이벤트를 관리하기 위해서 이벤트의 흐름을 이용합니다. 이벤트는 이벤트가 발생한 엘리먼트를 포함하고 있는 부모 요소에도 영향을 미치기 때문에 자식 요소를 포함할 수 있는 요소에 이벤트 핸들러를 지정하고 이벤트의 흐름을 이용해 다룰 수 있습니다.&lt;br&gt;즉, 이벤트 리스너가 실행할 작업을 요소의 부모 요소에게 위임(Delegation)할 수 있다는 것입니다.&lt;/p&gt;
&lt;h3&gt;이벤트 위임의 장점들&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;동적으로 추가되는 요소들에도 동작한다.&lt;ul&gt;
&lt;li&gt;DOM트리에 새로운 요소를 추가하더라도 이벤트에 대한 처리는 부모 요소에게 위임되었기 때문에 새로운 요소에 이벤트 핸들러를 다시 지정할 필요가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드의 간결&lt;ul&gt;
&lt;li&gt;이 기법을 이용하면 함수를 많이 작성할 필요가 없으며 DOM과 코드간의 연결이 간소해져 결과적으로 유지보수에 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul id=&amp;quot;parent-list&amp;quot;&amp;gt;
    &amp;lt;li id=&amp;quot;item1&amp;quot;&amp;gt;Item 1&amp;lt;/li&amp;gt;
    &amp;lt;li id=&amp;quot;item2&amp;quot;&amp;gt;Item 2&amp;lt;/li&amp;gt;
    &amp;lt;li id=&amp;quot;item3&amp;quot;&amp;gt;Item 3&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;//상위 노드에 이벤트 설정
document.getElementById(&amp;quot;parent-list&amp;quot;).addEventListener(&amp;quot;click&amp;quot;, function (e) {            
   if (e.target &amp;amp;&amp;amp; e.target.nodeName == &amp;quot;LI&amp;quot;) {                
      console.log(`List item  ${e.target.id} was clicked!`);
    }
 });&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JavaScript</category>
      <category>evnet 위임</category>
      <category>JavaScript</category>
      <category>javascript 이벤트 위임</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/37</guid>
      <comments>https://phant0m.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 22 Dec 2020 11:53:59 +0900</pubDate>
    </item>
    <item>
      <title>꼭! 알아야하는 JavaScript 최신문법 (ES6, ES11) 사용법 정리 (2)</title>
      <link>https://phant0m.tistory.com/36</link>
      <description>&lt;h2&gt;이번 포스팅에서는 자주 쓰이는 ES11 최신 문법 사용법에 대해 알아보겠습니다.&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://phant0m.tistory.com/35&quot;&gt;이전 포스트 자주쓰는 ES6 문법 사용법 정리(1)&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;h2&gt;ES11 2020&lt;/h2&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Optional chainning&lt;/li&gt;
&lt;li&gt;Nullish coalescing operator&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;들어가기에 앞서 ES6 이후부터 추가된 많은 문법들이 익스플로러에서는 지원되지 않기 때문에 &lt;b&gt;Babel&lt;/b&gt;을 이용해야 합니다.&lt;/p&gt;
&lt;h3&gt;1. Optional chainning&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const person1 = {
  name: 'dan',
  job: {
    title: 'Engineer',
    manager: {
          name: 'Bob',
    },
  },
};


const person2 = {
  name: 'Bob'
}


//  
{
  function printManager(person) {
    console.log(person.job.manager.name);
  }

  printManager(perseon1); // Bob
  printManager(perseon2); // Error  
}

// ✨
{
  function printManager(person) {
    console.log(person.job?.manager?.name);
  }

  printManager(perseon1); // Bob
  printManager(perseon2); // undefined  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;//  &lt;br /&gt;코드블럭에 있는 코드처럼 작성하면 전달받은 객체에 해당 키가 없다면 error 가 나서 사용하지 못합니다&lt;br /&gt;또는 해당 객체에 키가 있는지 확인하는 조건문을 작성해 주어야 하는데 이럴 때 Optional chainning을 사용합니다.&lt;/p&gt;
&lt;p&gt;// ✨&lt;/p&gt;
&lt;p&gt;&lt;code&gt;console.log(person.job?.manager?.name);&lt;/code&gt; 코드의 의미는 perseon.job 이 있으면? manager가 있으면? name을 출력하는 식입니다. 해당 키가 없다면 undefined로 출력됩니다.&lt;/p&gt;
&lt;h3&gt;2. Nullish coalescing operator&lt;/h3&gt;
&lt;p&gt;처음에 이 문법을 봤을 때 OR 연산자와 의미가 비슷해서 헷갈렸었는데요.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt; // Logical OR operator
 // false: false, '', 0, null, undefined

{
  const name = 'dan';
  const userName = name || 'Guest';
  console.log(userName);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OR 연산자는 앞의 값이 false 일 때만 뒤에('Guest') 것이 실행되는 성질을 가지고 있는데 현재 코드에서 name에 값이 들어있으므로 true가 반환되어서 결과 값은 &lt;b&gt;'dan'으로&lt;/b&gt; 출력되는 걸 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;보통 or연산자는 값을 할당할 때 해당 값에 값이 설정되어 있는 여부를 확인하고 없다면 기본값을 설정해주기 위해 많이 사용합니다. 그런데 이러한 코드들은 버그를 유발할 수 있는 코드가 될 가능성이 높다고 하네요 예를 들면&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;//  
// ex1)
const name = '';
const userName = name || 'Guest';
console.log(userName);

// ex2)
const num = 0;
const message = num || 'undefined';
console.log(message);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ex1) 경우 name에 '' 이 할당되어 있는데 name은 false값으로 취급이 되어 Guest라는 값이 할당되게 됩니다.&lt;br /&gt;만약 사용자가 이름을 사용하고 싶지 않아도 값이 할당되어 버리는 경우가 생기는 상황을 의미합니다.&lt;/p&gt;
&lt;p&gt;ex2) 같은 경우 num을 0으로 할당했는데 false값으로 인식해 출력은 undefined로 출력이 되는 경우 의도치 않은 출력 값이 나오는 경우입니다.&lt;/p&gt;
&lt;p&gt;OR연산자는 이러한 특징을 잘 알고 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;그래서 조금 더 명확하게 코딩하고 싶을 때는 &lt;b&gt;Nullish coalescing operator를&lt;/b&gt; 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;// ✨

// ex1)
const name = '';
const userName = name ?? 'Guest';
console.log(userName);

// ex2)
const num = 0;
const message = num ?? 'undefined';
console.log(message);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;Nullish coalescing operator를&lt;/b&gt; 사용하면 값이 할당되어 있는지의 유무를 가지고 판단합니다.&lt;br /&gt;ex1) 경우 name에 ''가 할당되어 있으니 userName은 ''로 빈 문자열이 출력이 됩니다.&lt;br /&gt;ex2) 경우 num에 0 이 할당되어 있으니 message 출력은 0이 출력됩니다.&lt;br /&gt;값이 할당되어 있지 않은 경우에만 뒤의 것이 실행됩니다.&lt;/p&gt;
&lt;p&gt;지금 까지 ES11 최신 문법에 대해서 알아봤습니다.&lt;br /&gt;오타나 틀린 내용이 있다면 댓글로 공유해주시면 감사합니다 &lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>es11</category>
      <category>JavaScript</category>
      <category>Nullish coalescing operator</category>
      <category>Optional chainning</category>
      <category>최신JS문법</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/36</guid>
      <comments>https://phant0m.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 22 Dec 2020 11:41:56 +0900</pubDate>
    </item>
    <item>
      <title>꼭! 알아야하는 JavaScript 최신문법 (ES6, ES11) 사용법 정리 (1)</title>
      <link>https://phant0m.tistory.com/35</link>
      <description>&lt;h2&gt;이번 포스팅에서는 자주 쓰이는 JS 최신 문법 사용법에 대해 알아보겠습니다.&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://phant0m.tistory.com/36&quot;&gt;다음 포스트 자주쓰는 ES11 문법 사용법 정리(2)&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;h2&gt;~ES6 2015&lt;/h2&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Shorthand property names&lt;/li&gt;
&lt;li&gt;Destructuring assignment&lt;/li&gt;
&lt;li&gt;Spread syntax&lt;/li&gt;
&lt;li&gt;Default parameters&lt;/li&gt;
&lt;li&gt;Ternary Operator&lt;/li&gt;
&lt;li&gt;Template Literals&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1. Shorthand property names&lt;/h3&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const person = {
  name: 'dev_min',
  age: '19',
}  &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;javascript 에서 object는 항상 key와 value로 이루어져 있습니다.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;const name = 'dev_min';
const age = '19';

const person1 = {
  name: name,
  age: age,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;perseon1 객체에서 name, age는 위에서 const 로 선언한 name, age 변수를 참조한 벨류 값 일 것입니다.&lt;/p&gt;
&lt;p&gt;이렇게 key 와 value 가 동일하다면 축약형으로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;const name = 'dev_min';
const age = '19';

const person2 = {
  name,
  age
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Destructuring assignment&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const student = {
  name: 'Anna',
  level: 3,
}

console.log(student.name, student.level);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Object의 Key와value에 접근하기 위해서는 연결 연산자를 이용하여 객체의 key와 value에 접근을 하였는데요&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;student.name, student.level&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Destructuring assignment을 이용하면 편리하게 Object에서 꺼내서 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const { name, level } = student;
console.log(name, level); &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사용 방법은 할당연산자(=) 오른편에는 사용하는 Object를 적어주고 왼편에는 꺼내서 사용할 key의 이름을 {} 안에 적어주게 되면 student의 key와 value들이 각각 name과 level에 맞게 할당이 됩니다.&lt;/p&gt;
&lt;p&gt;추가로 객체의 original Name이 아닌 새로운 이름을 사용하고 싶은경우는 아래의 코드처럼&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const { name: studentName, level: studentLevel } = student;
console.log(studentName, studentLevel); &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;key 옆에 콜론(:)을 붙인 뒤 새로운 name을 적어서 사용하시면 됩니다.&lt;/p&gt;
&lt;p&gt;이것은 객체에서 뿐만 아니라 배열에서도 사용이 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;// array
const ['apple', 'banana'] = fruits;


// 기존 코드  
{  
  const apple = fruits[0];
  const bananna = fruits[1];

  console.log(apple, banana);

}

// Destructuring assignment
{  
  const [apple, banana] = fruits;
  console.log(apple, banana);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Point! Object를 Destructuring 할 때는 {} 를 사용하고 Array를 할 때는 []를 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3. Spread syntax&lt;/h3&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;const obj1 = { key: 'key1' };
const obj2 = { key: 'key2' };
const array = [obj1, obj2];

// array copy
const arrayCopy = [...array];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spread syntax(...) 는 array에 들어있는 아이템들 하나하나씩을 각각 낱개로 가져와서 복사해 오는 것 을 의미합니다.&lt;/p&gt;
&lt;p&gt;배열을 복사하면서 복사한 배열에 아이템을 추가하고 싶다면? 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt; // array copy
const arrayCopy = [...array, { key: 'obj3' }];

console.log(arrayCopy);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spread syntax를 사용한 다음에 추가할 아이템을 작성해주면 됩니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;주의할 점! Spread syntax는 해당 객체의 값을 복사하는 게 아닌 주소 값을 참조하기 때문에 객체의 값을 수정하게 되면 참조하던 객체의 값도 변하기 때문에 이점 유의해서 코딩하시기 바랍니다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Spread syntax 는 array 뿐만 아니라 object에서도 사용 가능합니다&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;const obj3 = { ...obj1 };
console.log(obj3);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;차이점은 array에서는 [...items] 배열의 괄호를 이용하고 , object는 {... items} object의 괄호를 이용해서 하시면 됩니다.&lt;br /&gt;한 가지 참고하실 점은 &lt;b&gt;object의 key가 동일한 경우&lt;/b&gt; 뒤에 있는 key가 앞에 있는 key를 덮습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const obj1 = { animal: '개' };
const obj2 = { animal: '고양이' };
const obj3 = {...obj1, obj2};

console.log(obj3); // 결과: {animal: '고양이'}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.Default parameters&lt;/h3&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;function printMessage(message) {
  if (message == null) {
    message = 'default message';
  }

  console.log(message);
}

printMessage('hello'); // hello
printMessage(); // default message&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존에는 파라미터로 전달받은 값이 null, undefinded 라면 분기문을 통해 기본값을 할당해주었습니다.&lt;br /&gt;이제 &lt;i&gt;Default parameters를&lt;/i&gt; 이용하면 간단하게 작성이 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function printMessage(message = 'default message') {
  console.log(message);
}

printMessage('hello'); // hello
printMessage(); // default message&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;인자 다음에 초기값을 지정하면 됩니다 인자가 전달될 때는 인자의 값을 사용하고 인자가 전달되지 않는다면 설정해 둔&lt;br /&gt;default parameter 값을 이용하게 됩니다.&lt;/p&gt;
&lt;h3&gt;5.Ternary Operator&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const isCat = true;

// 기존
{
  let component;  
  if (isCat) {
    component = 'cat';
  } else {
       component = 'dog'; 
  }

  console.log(component);
}


// Ternary Operator
{
  const component = isCat ? 'cat' : 'dog';
  console.log(component);
  console.log(isCat ? 'cat' : 'dog');    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저는 간단한 조건문 같은 경우 자주 사용하는 편 입니다.&lt;/p&gt;
&lt;h2&gt;6.Template Literals&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const pageNumber = 1;
const pageSize = 10;

// 기존
console.log('현재 페이지 번호는: ' + pageNumber +  ' 이고 페이지 사이즈는: ' + pageSize + '입니다.');

// Template Literals
console.log(`현재 페이지 번호는: ${pageNumber} 이고 페이지 사이지는: ${pageSize} 입니다.`);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;편리함과 가독성이 좋아 Template Literals 사용하는 것을 추천드립니다.&lt;/p&gt;
&lt;p&gt;지금 까지 ES6 최신 문법에 대해서 알아봤는데요 다음 포스터는 ES11 최신 문법으로 뵙겠습니다.&lt;/p&gt;
&lt;p&gt;오타나 틀린 내용이 있다면 댓글로 공유해주시면 감사합니다 &lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>default parameters</category>
      <category>Destructuring assignment</category>
      <category>ES6</category>
      <category>javascript 최신문법</category>
      <category>Shorthand property names</category>
      <category>Spread syntax</category>
      <category>ternary operator</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/35</guid>
      <comments>https://phant0m.tistory.com/35#entry35comment</comments>
      <pubDate>Tue, 22 Dec 2020 10:53:22 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript 30 Drum Kit 정리</title>
      <link>https://phant0m.tistory.com/34</link>
      <description>&lt;h2&gt;요구사항&lt;/h2&gt;
&lt;h4&gt;KeyBoard 입력을 받아 애니메이션 효과와 Drum 소리가 출력되게 vanilla JS를 이용해서 구현한다&lt;/h4&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;기본 소스코드&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;div class=&amp;quot;keys&amp;quot;&amp;gt;
    &amp;lt;div data-key=&amp;quot;65&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;A&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;clap&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;83&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;S&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;hihat&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;68&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;D&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;kick&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;70&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;F&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;openhat&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;71&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;G&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;boom&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;72&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;H&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;ride&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;74&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;J&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;snare&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;75&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;K&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;tom&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div data-key=&amp;quot;76&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;L&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;tink&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;audio data-key=&amp;quot;65&amp;quot; src=&amp;quot;sounds/clap.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;83&amp;quot; src=&amp;quot;sounds/hihat.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;68&amp;quot; src=&amp;quot;sounds/kick.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;70&amp;quot; src=&amp;quot;sounds/openhat.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;71&amp;quot; src=&amp;quot;sounds/boom.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;72&amp;quot; src=&amp;quot;sounds/ride.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;74&amp;quot; src=&amp;quot;sounds/snare.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;75&amp;quot; src=&amp;quot;sounds/tom.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;
  &amp;lt;audio data-key=&amp;quot;76&amp;quot; src=&amp;quot;sounds/tink.wav&amp;quot;&amp;gt;&amp;lt;/audio&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;처리과정 &amp;amp; 코드분석&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;keydown 이 될때마다 로그를 찍어 봤음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;keydown 됐을 때 실행할 익명함수 작성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;evnet를 찍었을 때 keyCode 속성이 나옴&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;data-key를 이용함, event.keyCode 속성을 이용하여 동적으로 타겟을 잡음 key, audio 변수에 할당함&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const audio = document.querySelect(`audio[data-key=&amp;quot;${e.keyCode}&amp;quot;]`);

const key = document.querySelect(`key[data-key=&amp;quot;${e.keyCode}&amp;quot;]`);&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;currentTime 을 사용하여 시간을 0초로 설정함 (키보드 누를때 마다 audio 출력하기 위해)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt; audio.currentTime = 0;&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;play() 실행이 되면서 애니메이션 효과를 주기 위해&lt;br&gt;key에 classList.add(&amp;#39;playing&amp;#39;) 추가함&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;key.classList.add(&amp;#39;playing&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;각 key에 적용된 CSS 효과를 지우기 위해 &lt;em&gt;모든&lt;/em&gt; key를 타겟 잡음 (querySelectAll로 타겟을 잡은 keys는 &lt;del&gt;배열x&lt;/del&gt; &lt;em&gt;유사배열로(o)&lt;/em&gt; 값이 떨어짐)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const keys = document.querySelectAll(&amp;#39;.key&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;keys를 forEach를 통해 반복하고 css 효과가 끝났을 때 실행 될 removeTransition 함수를 작성함&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;keys.forEach(key =&amp;gt; key.addEventListener(&amp;#39;transitionend&amp;#39;, removeTransition));&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;function removeTransition(e) {
    if (e.propertyName !== &amp;#39;transform&amp;#39;) return;
    this.classList.remove(&amp;#39;playing&amp;#39;);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;8-1. removeTransition 함수에서 event를 찍어보니 css 전환이 완료된 evnet들이 찍혔고 거기에 propertyName 속성이 있었음 그 name이 &amp;#39;transform&amp;#39; 이 아닌 envet들은 return값을 주어 반환 시킴&lt;/p&gt;
&lt;p&gt;8-2. removeTransition 안에서의 &lt;em&gt;this&lt;/em&gt; 가 가리키는 곳은 이벤트가 발생한 &lt;em&gt;key&lt;/em&gt; 이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;key =&amp;gt; key.addEventListener(...)

&amp;lt; this가 가리키는 부분 &amp;gt;
&amp;lt;div data-key=&amp;quot;65&amp;quot; class=&amp;quot;key&amp;quot;&amp;gt;
      &amp;lt;kbd&amp;gt;A&amp;lt;/kbd&amp;gt;
      &amp;lt;span class=&amp;quot;sound&amp;quot;&amp;gt;clap&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt; ...&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;

function playSound(e) {
  const audio = document.querySelector(`audio[data-key=&amp;quot;${e.keyCode}&amp;quot;]`);
  const key   = document.querySelector(`.key[data-key=&amp;quot;${e.keyCode}&amp;quot;]`);

  if (!audio) return; // stop the function from running all together
  audio.currentTime = 0;
  audio.play();
  key.classList.add(&amp;quot;playing&amp;quot;);
}

function removeTransition(e) {
    if (e.propertyName !== &amp;#39;transform&amp;#39;) return;
    this.classList.remove(&amp;quot;playing&amp;quot;);
}

const keys = document.querySelectorAll(&amp;#39;.key&amp;#39;);
keys.forEach(key =&amp;gt; key.addEventListener(&amp;#39;transitionend&amp;#39;, removeTransition));
window.addEventListener(&amp;#39;keydown&amp;#39;, playSound);

&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://github.com/minsangkimme/DevLog/raw/master/JavaScript30/01/001.JPG&quot; alt=&quot;결과&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;찾아본 내용&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;data-key&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;custom data attribute 이다. 사용자가 임의로 만들고 생성한 속성 (data-*)
이 때문에 각각의 div 는 data-key 값을 인덱스처럼 사용할 수 있음.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;currentTime&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTML audio/video DOM  currentTime 속성
currentTime 속성은 audio/video 재생의 현재 초를 설정하거나 반환함. 이 속성을 설정하면 재생이 지정된 위치로 이동함.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;addEventListener(&amp;quot;transitionend&amp;quot;)&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CSS 전환이 완료되면 transitionend 이벤트가 발생합니다.
참고 : CSS transition-property 속성이 제거 된 경우와 같이 완료 전에 전환이 제거되면 transitionend 이벤트가 발생하지 않습니다.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;kbd&amp;gt; 태그는 키보드 입력을 지정할 때 사용함&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;유사배열&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;유사배열은 []로 감싸져있지만 배열이 아닌 것을 유사배열이라 부른다.
배열과 유사배열의 큰 차이는 유사배열의 경우 배열의 메서드를 사용할 수 없습니다.
ex)forEach,map,filter..

유사배열이 배열의 메서드를 사용하려면 (call, apply, bind)이용
ex) Array.prototype.forEach.call(유사배열, 함수);
하면 유사배열도 배열의 메서드를 사용할 수 있게 됩니다.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;code&gt;querySelectAll&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;이 코드에서 keys가 (call, apply, bind) 하지않고 forEach를 사용할수 있었던 것은 querySelectAll이 정적 NodeList를 반환하기 때문 입니다. NodeList는 Array는 아니지만 forEach를 사용하여 반복할 수 있습니다.&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JavaScript</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/34</guid>
      <comments>https://phant0m.tistory.com/34#entry34comment</comments>
      <pubDate>Tue, 22 Dec 2020 09:26:46 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 동작 원리</title>
      <link>https://phant0m.tistory.com/33</link>
      <description>&lt;h1&gt;브라우저 동작 원리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글의 Chrome V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경인 Node.js의 등장으로 자바스크립트는 웹 브라우저를 벗어나 서버 사이드 어플리케이션 개발에서도 사용되는 범용 개발 언어가 되었다. 하지만 자바스크립트가 가장 많이 사용되는 분야는 역시 웹 브라우저 환경에서 동작하는 웹 페이지/ 어플리케이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프로그래밍 언어는 운영체제(O/S) 위에서 실행되지만 웹 어플리케이션의 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행된다. 따라서 브라우저 환경을 고려할 때 보다 효율적인 자바스크립트 프로그래밍이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저의 핵심 기능은 사용자가 참고하고자 하는 웹페이지를 서버에 요청(Request)하고 서버의 응답(Response)을 받아 브라우저에 표시하는 것이다. 브라우저는 서버로부터 HTML, CSS, JavaScript, 이미지 파일 등을 응답 받는다. HTML, CSS 파일은 렌더링 엔진의 HTML파서와 CSS 파서에 의해 파싱되어 DOM, CSSOM트리로 변환되고 렌더 트리로 결합된다. 이렇게 생성된 렌더 트리를 기반으로 브라우저는 웹페이지를 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는&lt;/p&gt;
&lt;p&gt;&lt;del&gt;렌더링 엔진이&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닌 &lt;b&gt;자바스크립트 엔진&lt;/b&gt;이 처리한다. HTML 파서는 script태그를 만나면 자바스크립트 코드를 실행하기 위해 DOM 생성 프로세스를 중지하고 자바스크립트 엔진으로 제어 권한을 넘긴다. 제어 권한을 넘겨 받은 자바스크립트 엔진은 script태그 내의 자바스크립트 코드 또는 script태그의 src 어트리뷰터에 정의된 자바스크립트 파일을 로드하고 파싱하여 실행한다. 자바스크립트의 실행이 완료되면 다시 HTML 파서로 제어 권한을 넘겨서 브라우저가 중지했던 시점부터 DOM 생성을 재개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 브라우저는 동기적으로 HTML, CSS, JavaScript을 처리한다. 이것은 scirpt 태그의 위치에 따라 블로킹이 발생하여 DOM의 생서이 지연될 수 있다는 것을 의미한다. 따라서 태그의 위치는 중요한 의미를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body 요소의 가장 아래에 자바스크립트를 위치시키는 것은 좋은 아이디어이다. 그 이유는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 요소들이 스크립트 로딩 지연으로 인해 렌더링에 지장 받는 일이 발생하지 않아 페이지 로딩 시간이 단축된다.&lt;/li&gt;
&lt;li&gt;DOM이 완성되지 않은 상태에서 자바스크립트가 DOM을 조작한다면 에러가 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-browser&quot;&gt;브라우저 동작 원리&lt;/a&gt;&lt;/p&gt;</description>
      <category>HTML</category>
      <category>브라우저 동작 원리</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/33</guid>
      <comments>https://phant0m.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 22 Dec 2020 09:23:45 +0900</pubDate>
    </item>
    <item>
      <title>CSS Variables</title>
      <link>https://phant0m.tistory.com/32</link>
      <description>&lt;h2&gt;요구사항&lt;/h2&gt;
&lt;p&gt;css에서 변수를 사용하여 값을 설정 해보고 javascript로 css를 동적으로 컨트롤 해보자.&lt;/p&gt;
&lt;p&gt;기본 소스코드&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h2&amp;gt;Update CSS Variables with &amp;lt;span class=&amp;quot;hl&amp;quot;&amp;gt;JS&amp;lt;/span&amp;gt;&amp;lt;/h2&amp;gt;

&amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;spacing&amp;quot;&amp;gt;Spacing:&amp;lt;/label&amp;gt;
  &amp;lt;input
    id=&amp;quot;spacing&amp;quot;
    type=&amp;quot;range&amp;quot;
    name=&amp;quot;spacing&amp;quot;
    min=&amp;quot;10&amp;quot;
    max=&amp;quot;200&amp;quot;
    value=&amp;quot;10&amp;quot;
    data-sizing=&amp;quot;px&amp;quot;
  /&amp;gt;

  &amp;lt;label for=&amp;quot;blur&amp;quot;&amp;gt;Blur:&amp;lt;/label&amp;gt;
  &amp;lt;input
    id=&amp;quot;blur&amp;quot;
    type=&amp;quot;range&amp;quot;
    name=&amp;quot;blur&amp;quot;
    min=&amp;quot;0&amp;quot;
    max=&amp;quot;25&amp;quot;
    value=&amp;quot;10&amp;quot;
    data-sizing=&amp;quot;px&amp;quot;
  /&amp;gt;

  &amp;lt;label for=&amp;quot;base&amp;quot;&amp;gt;Base Color&amp;lt;/label&amp;gt;
  &amp;lt;input id=&amp;quot;base&amp;quot; type=&amp;quot;color&amp;quot; name=&amp;quot;base&amp;quot; value=&amp;quot;#ffc600&amp;quot; /&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;img src=&amp;quot;https://source.unsplash.com/7bwQXzbF6KE/800x500&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&amp;lt;style&amp;gt;
    body {
        text-align: center;
        background: #193549;
        color: white;
        font-family: &amp;#39;helvetica neue&amp;#39;, sans-serif;
        font-weight: 100;
        font-size: 50px;
      }

      .controls {
        margin-bottom: 50px;
      }

      input {
        width: 100px;
      }
    &amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;처리과정 &amp;amp; 코드분석&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;css에서 :root 가상클래스에 변수를 추가해 주었다.&lt;/p&gt;
&lt;p&gt;(:root 에 추가해주는 것이 모든 요소에 변수를 정의하는 데에 도움이 된다.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;커스텀 속성 정의 부분 (--* 패밀리)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  --base: #ffc600;
  --spacing: 10px;
  --blur: 10px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;p&gt;img태그에 선언한 변수들을 이용해서 값을 설정함&lt;/p&gt;
&lt;p&gt;캐스케이딩 변수 사용 부분 (var()함수)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;img {
  padding: var(--spacing);
  background: var(--base);
  filter: blur(var(--blur));
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;p&gt;input 요소들을 감싸고있는 controls 클래스, input 요소들을 다 선택하기 위해 querySelectorAll로 잡아서 inputs에 할당 함&lt;/p&gt;
&lt;p&gt;전부 타겟으로 잡는 이유는 ? 다음 작업에서 각 input요소에 addEventListener를 해주기 위해서임&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const inputs = document.querySelectorAll(&amp;#39;.controls input&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;p&gt;inputs은 querySelectorAll로 잡았기 때문에 배열이 아닌 NodeList로 반환이 된다. NodeList에 forEach메서드가 있기 때문에 다른 설정없이 forEach를 사용해서 각 input 요소에 addEventListener를 추가했다.&lt;/p&gt;
&lt;p&gt;range 설정을 실시간으로 변하는것을 확인하기 위해 mousemove와 change를 addEventListener로 주었다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;inputs.forEach(input =&amp;gt; input.addEventListener(&amp;#39;change&amp;#39;, handleUpdate));

inputs.forEach(input =&amp;gt; input.addEventListener(&amp;#39;mousemove&amp;#39;, handleUpdate));&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;handleUpdate 함수 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function handleUpdate() {
  const suffix = this.dataset.sizing || &amp;#39;&amp;#39;;
  document.documentElement.style.setProperty(
    `--${this.name}`,
    this.value + suffix
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;6-1. &lt;code&gt;this.dataset&lt;/code&gt;은 html에 input 태그에 작성된 data-* 들을 가리킨다. 그 중에 sizing을 선택해서 &lt;code&gt;this.dataset.sizing&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;6-2. &lt;code&gt;suffix&lt;/code&gt;를 생성한 이유는 값을 설정할 때 --spacing, --blur가 px단위 때문에 input태그에서 설정한 &lt;code&gt;data-sizing=&amp;#39;px&amp;#39;&lt;/code&gt; 이기 때문에 suffix로 할당함&lt;/p&gt;
&lt;p&gt;6-3. &lt;code&gt;document.documentElement.style.setProperty(`--${this.name}`, this.value + suffix);&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.documentElement는 읽기 전용 속성으로 document 의 루트 요소인 Element를 반환한다.

style.setProperty()는 CSS 스타일 선언 객체의 속성에 대한 새 값을 설정한다.

.setProperty ( propertyName , value, priority ) 의 구문이다.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;6-4. &lt;code&gt;(`--${this.name}`, this.value + suffix)&lt;/code&gt; propertyName은 --변수네임, value는 값+suffix 로 set 해주었다.&lt;/p&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&amp;lt;style&amp;gt;
      :root {
        --base: #ffc600;
        --spacing: 10px;
        --blur: 10px;
      }

      img {
        padding: var(--spacing);
        background: var(--base);
        filter: blur(var(--blur));
      }

      .hl {
        color: var(--base);
      }

      body {
        text-align: center;
        background: #193549;
        color: white;
        font-family: &amp;#39;helvetica neue&amp;#39;, sans-serif;
        font-weight: 100;
        font-size: 50px;
      }

      .controls {
        margin-bottom: 50px;
      }

      input {
        width: 100px;
      }
    &amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;script&amp;gt;
      const inputs = document.querySelectorAll(&amp;#39;.controls input&amp;#39;);

      function handleUpdate() {
        const suffix = this.dataset.sizing || &amp;#39;&amp;#39;;
        document.documentElement.style.setProperty(`--${this.name}`, this.value + suffix);
      }

      inputs.forEach(input =&amp;gt; input.addEventListener(&amp;#39;change&amp;#39;, handleUpdate));
      inputs.forEach(input =&amp;gt; input.addEventListener(&amp;#39;mousemove&amp;#39;, handleUpdate));
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/minsangkimme/DevLog/raw/master/JavaScript30/03/blur01.JPG&quot; alt=&quot;결과&quot;&gt;&lt;br&gt;&lt;img src=&quot;https://github.com/minsangkimme/DevLog/raw/master/JavaScript30/03/blur02.JPG&quot; alt=&quot;결과&quot;&gt;&lt;/p&gt;
&lt;h2&gt;찾아본 내용&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;css filter 속성&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;filter CSS 속성은 흐림 효과나 색상 변형 등 그래픽 효과를 요소에 적용합니다. 보통 필터는 이미지 렌더링, 배경, 테두리 등의 조정에 쓰입니다.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;:root 의사 클래스&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:root 의사 클래스는 문서의 루트요소를 나타낸다 HTML에서는 보통 html 요소를 지칭한다 CSS 커스텀 속성 정의 부분은 대시(-) 두개로 시작하며 그 뒤의 이름은 대소문자를  구분한다.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Document.documentElement&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;읽기 전용 속성으로 document 의 루트 요소인 Element를 반환한다 (가령, HTML 문서의 &amp;lt;html&amp;gt; 요소).&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;setProperty&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.setProperty() 방법 인터페이스는 CSS 스타일 선언 객체의 속성에 대한 새 값을 설정합니다
.setProperty ( propertyName , value, priority );&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;참고자료&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/filter&quot;&gt;css filter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://brunch.co.kr/@techhtml/27&quot;&gt;:root&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Document/documentElement&quot;&gt;Document.documentElement&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/setProperty&quot;&gt;setProperty&lt;/a&gt;&lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>css variables</category>
      <category>javascript30</category>
      <author>freddy</author>
      <guid isPermaLink="true">https://phant0m.tistory.com/32</guid>
      <comments>https://phant0m.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 22 Dec 2020 09:19:59 +0900</pubDate>
    </item>
  </channel>
</rss>