<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>wazac</title>
    <link>https://wazacs.tistory.com/</link>
    <description>퍼블리싱, 개발 등등 공부하면서 배운 것들을 공유합니다.</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 02:47:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JOTOKKI</managingEditor>
    <image>
      <title>wazac</title>
      <url>https://tistory1.daumcdn.net/tistory/3200660/attach/97cd572ab0324a4684c0f0e34e5e154e</url>
      <link>https://wazacs.tistory.com</link>
    </image>
    <item>
      <title>mongooseerror: model.findone() no longer accepts a callback</title>
      <link>https://wazacs.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;특정강의를 그냥 따라하게되면 mongooseerror: model.findone() no longer accepts a callback 이런 에러를 마주할때가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 mongoose findOne 함수에서 더이상 콜백함수 방식을 지원하지 않는다는 메세지가 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 async/await 으로 꺼내주기만 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1727248308208&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존 에러가 나던 방식
User.findOne({
            userId: userId.toLocaleLowerCase(),
        }, (err, user) =&amp;gt; {
            if(err) return done(err);
            if(!user) {
                return done(null, false, {message: `UserId ${userId} not found`})
            }
            user.comparePassword(password, (error, isMatch) =&amp;gt; {
                if(err) return done(err);
                if(isMatch) {
                    // 비번 맞틈
                    return done(null, user);
                } else {
                    // 비번 틀림
                    return done(null, flase, {msg: &quot;Invalid userId or password&quot;})
                }
            })
        })
}&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;아래와 같이 수정해주면된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1727248443566&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
    const user = await User.findOne({ userId: userId.toLowerCase() });
    if (!user) {
        return done(null, false, { message: `UserId ${userId} not found` });
    }
    user.comparePassword(password, (error, isMatch) =&amp;gt; {
        if (error) return done(error);
        if (isMatch) {
            // 비번 맞음
            return done(null, user);
        } else {
            // 비번 틀림
            return done(null, false, { msg: &quot;Invalid userId or password&quot; });
        }
    });
} catch (err) {
   return done(err);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>model.findone() no longer accepts a callback</category>
      <category>mongooseerror: model.findone()</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/81</guid>
      <comments>https://wazacs.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 25 Sep 2024 16:50:16 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript 배포후 copy 안되는 문제</title>
      <link>https://wazacs.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;링크공유와&amp;nbsp;같은&amp;nbsp;기능을&amp;nbsp;구현하기&amp;nbsp;위해&amp;nbsp;버튼&amp;nbsp;클릭시&amp;nbsp;링크를&amp;nbsp;복사하는&amp;nbsp;기능을&amp;nbsp;은근히&amp;nbsp;많이&amp;nbsp;사용하게&amp;nbsp;되는데요. &lt;br /&gt;이를&amp;nbsp;구현할때&amp;nbsp;예전에는&amp;nbsp;execCommand&amp;nbsp;함수를&amp;nbsp;많이&amp;nbsp;쓰다가&amp;nbsp;deprecated&amp;nbsp;되고나서&amp;nbsp;(리액트&amp;nbsp;환경)&amp;nbsp;navigator.clipboard.writeText&amp;nbsp;함수를&amp;nbsp;많이&amp;nbsp;쓰게됩니다.&amp;nbsp; &lt;br /&gt;그런데&amp;nbsp;분명&amp;nbsp;local&amp;nbsp;에서&amp;nbsp;잘&amp;nbsp;동작되다가&amp;nbsp;개발&amp;nbsp;서버에&amp;nbsp;배포만&amp;nbsp;하면&amp;nbsp;복사가&amp;nbsp;되지&amp;nbsp;않습니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;보통&amp;nbsp;개발서버는&amp;nbsp;https&amp;nbsp;가&amp;nbsp;아닌&amp;nbsp;http로&amp;nbsp;동작하기&amp;nbsp;때문에,&amp;nbsp;보안&amp;nbsp;정책에&amp;nbsp;걸리기&amp;nbsp;때문인&amp;nbsp;것&amp;nbsp;같더라구요.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;참조:&amp;nbsp;&lt;a href=&quot;https://stackoverflow.com/questions/51805395/navigator-clipboard-is-undefined&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/51805395/navigator-clipboard-is-undefined&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;그래서&amp;nbsp;이를&amp;nbsp;해결하기&amp;nbsp;위해&amp;nbsp;https&amp;nbsp;환경일때만&amp;nbsp;안정적인&amp;nbsp;navigator.clipboard.writeText&amp;nbsp;를&amp;nbsp;사용하고&amp;nbsp;그렇지&amp;nbsp;않을때는&amp;nbsp;deprecated&amp;nbsp;되었던&amp;nbsp;execCommand&amp;nbsp;함수를&amp;nbsp;사용했더니&amp;nbsp;잘&amp;nbsp;동작이&amp;nbsp;되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1724292103857&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const copyText = &quot;your url&quot;;
if(navigator.clipboard &amp;amp;&amp;amp; window.isSecureContext) {
    navigator.clipboard
        .writeText(copyText)
        .then(() =&amp;gt; {alert(&quot;copied!&quot;)})
        .catch((err) =&amp;gt; { console.log(&quot;err -&amp;gt; &quot;, err) });
} else {
    try {
        console.log('copy exec command use');

        const textarea = document.createElement(&quot;textarea&quot;);
        textarea.value = copyText;
        textarea.style.top = 0;
        textarea.style.left = 0;
        textarea.style.position = &quot;fixed&quot;;
        document.body.appendChild(textarea);
        textarea.focus();
        textarea.select();
        const copyExec = document.execCommand('copy');
        document.body.removeChild(textarea);
        if(copyExec) alert(&quot;copied!&quot;);
        else { console.log(&quot;error&quot;) }
    } catch (err) {
        console.log(&quot;error&quot;, err)
    }
}&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;/p&gt;</description>
      <category>개발  /Error</category>
      <category>execCommand</category>
      <category>navigator.clipboard</category>
      <category>navigator.clipboard not working</category>
      <category>개발서버 navigator.clipboard</category>
      <category>배포 후 navigator.clipboard</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/80</guid>
      <comments>https://wazacs.tistory.com/80#entry80comment</comments>
      <pubDate>Thu, 22 Aug 2024 11:03:08 +0900</pubDate>
    </item>
    <item>
      <title>nextjs _app.js useEffect에서 두번 호출 하는 문제</title>
      <link>https://wazacs.tistory.com/79</link>
      <description>&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;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8180939788074664&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;!-- [디스플레이, 반응형] 최상단 고정 --&gt;
&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-client=&quot;ca-pub-8180939788074664&quot; data-ad-slot=&quot;7247661249&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&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;a href=&quot;https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.to/jahid6597/why-useeffect-is-running-twice-in-react-18c6&lt;/a&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;React18 버전이후 StrictMode일때 useEffect 가 두번씩 호출된다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번만 호출하고 싶다면, next.config.js 에서 strictMode를 끌 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701242676935&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// next.config.js

const nextConfig = {
  reactStrictMode: false,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>StrictMode</category>
      <category>useEffect call twice</category>
      <category>useEffect 두 번 호출</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/79</guid>
      <comments>https://wazacs.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 29 Nov 2023 16:39:42 +0900</pubDate>
    </item>
    <item>
      <title>개인 사이드 프로젝트 배포까지 완료 후기</title>
      <link>https://wazacs.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발자로써 회사에서 일하는 것도 즐겁지만(정말?!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 돈, 스펙, 서비스를 하나도 생각하지 않고 오로지 내가 만들고 싶은 것들을 재미로 만들고 싶은 욕심이 있었습니다. 그래서 시작한 &lt;b&gt;&lt;span style=&quot;background-color: #c1bef9; color: #5733b1;&quot;&gt;&quot;조토끼아케이드 프로젝트&quot;&lt;/span&gt;.&lt;/b&gt;  &amp;nbsp;&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;제목처럼 재미와 즐거움만 존재하는 아케이드 컨셉입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 1차로 만든 서비스는 게임2개와 랜덤을 소재로한 서비스 1개 입니다.&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;React로 개발을 할지, 그냥 javascript로 할지 고민이 많았는데, 아무리 생각해도 내가 생각하는 서비스들은 너무 단순한 것들이여서 react는 오버스펙이라는 결론을 내렸습니다. 혹여라도 추후 서비스가 커진다면, 리액트로 변경하면 되니깐요.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 강건너기 게임&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhftAp/btszKzNGYIp/6kyZHXeHFuia2JMRR1amOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhftAp/btszKzNGYIp/6kyZHXeHFuia2JMRR1amOK/img.png&quot; data-alt=&quot;JOTOKKI&amp;quot;S ARCADE - 강건너기 게임&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhftAp/btszKzNGYIp/6kyZHXeHFuia2JMRR1amOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhftAp%2FbtszKzNGYIp%2F6kyZHXeHFuia2JMRR1amOK%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;1169&quot; height=&quot;720&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JOTOKKI&quot;S ARCADE - 강건너기 게임&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;어렸을적 풀어봤을 그 유명한 강건너기 게임입니다. 옛날에는 종이를 찢어서 풀어봤던 게임이였는데, 이렇게 온라인으로 구현하니까 감회가 새로웠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 한번도 게임을 만들어본적이 없었기 때문에,비록 unity나 canvas 같은 거창한 기술을 사용한게 아니지만, 나름 게임처럼 보이기 위해 열심히 노력했습니다. ㅎㅎ&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 랜덤숫자맞추기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7sq2N/btszKCDqlsN/hPYbFWj8hV75NGoUeak0O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7sq2N/btszKCDqlsN/hPYbFWj8hV75NGoUeak0O0/img.png&quot; data-alt=&quot;JOTOKKI&amp;quot;S ARCADE - 랜덤 숫자 맞추기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7sq2N/btszKCDqlsN/hPYbFWj8hV75NGoUeak0O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7sq2N%2FbtszKCDqlsN%2FhPYbFWj8hV75NGoUeak0O0%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;668&quot; height=&quot;622&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JOTOKKI&quot;S ARCADE - 랜덤 숫자 맞추기&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;이것도 어렸을적 많이 했던 게임입니다. 사실 이 로직은 예전에 심심해서 만들어 뒀기때문에, 이미지를 비롯해 추가 작업만 덧붙였습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 마법소라에게 물어보세요&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sx6Qo/btszKCcnASe/4RElGZENHkISVpBzjuKKZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sx6Qo/btszKCcnASe/4RElGZENHkISVpBzjuKKZK/img.png&quot; data-alt=&quot;JOTOKKI&amp;quot;S ARCADE - 마법소라에게 물어보세요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sx6Qo/btszKCcnASe/4RElGZENHkISVpBzjuKKZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSx6Qo%2FbtszKCcnASe%2F4RElGZENHkISVpBzjuKKZK%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;695&quot; height=&quot;263&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JOTOKKI&quot;S ARCADE - 마법소라에게 물어보세요&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 본의아니게 배운게 있다면, animation으로 display의 opacity를 조정할때, 이미지 저장시 opacity가 1이 되기전에 사진이 저장되더라구요...  setTimeout도 추가해보고, flag 변수로 제어를 해보려고 했지만, 결국엔 그냥 opacity 를 제거했습니다. ㅠvㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;/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;&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;/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;color: #6164c6;&quot;&gt;&lt;b&gt;사이트 구경해보기 : &lt;a href=&quot;https://jotokkiplayground.com/ &quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jotokkiplayground.com/ &lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발  /일하고,정리하기</category>
      <category>personal website</category>
      <category>side project</category>
      <category>강건너기 게임</category>
      <category>개인 사이트</category>
      <category>개인사이트 애드센스</category>
      <category>사이드프로젝트</category>
      <category>스핀오프 프로젝트</category>
      <category>애드센스 사이트</category>
      <category>웹개발</category>
      <category>토이프로젝트</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/78</guid>
      <comments>https://wazacs.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 3 Nov 2023 21:00:15 +0900</pubDate>
    </item>
    <item>
      <title>시스템 scroll 커스텀 하기</title>
      <link>https://wazacs.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트나&amp;nbsp;애플리케이션의&amp;nbsp;스크롤바는&amp;nbsp;중요한&amp;nbsp;사용자&amp;nbsp;인터페이스(UI)&amp;nbsp;요소&amp;nbsp;중&amp;nbsp;하나입니다.&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;/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;::scroll 이라는 선택자를 사용하면됩니다. 의외로 간단 하답니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697180374999&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.scroll {
  &amp;amp;::-webkit-scrollbar {
    width: 7px;
    height: 7px;
  }
  &amp;amp;::-webkit-scrollbar-track {
    background: transparent;
  }
  &amp;amp;::-webkit-scrollbar-thumb {
    background: #3c3c3c;
    border-radius: 45px;
  }
  &amp;amp;::-webkit-scrollbar-thumb:hover {
    background: #3c3c3c;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 WebKit 기반 브라우저 (예: Chrome, Safari)를 커스터마이징 하였습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. scrollbar : 스크롤바 전체를 가리킵니다. 이를 통해 스크롤바의 너비, 높이, 색상 등을 조정할 수 있습니다.&lt;br /&gt;2. scrollbar-track : 스크롤바의 &quot;트랙&quot; 또는 배경 부분을 가리킵니다. 스크롤바 썸네일이 움직이는 부분입니다.&lt;br /&gt;3. scrollbar-thumb : 스크롤바의 &quot;썸네일&quot; 또는 &quot;핸들&quot;이라고 부르는 부분입니다. 사용자가 직접 클릭하거나 드래그하여 스크롤하는 부분입니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjwpKW/btsynDSr8Cy/n3vd1ZPXDTJdKDjHBe6wc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjwpKW/btsynDSr8Cy/n3vd1ZPXDTJdKDjHBe6wc0/img.png&quot; data-alt=&quot;결과화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjwpKW/btsynDSr8Cy/n3vd1ZPXDTJdKDjHBe6wc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjwpKW%2FbtsynDSr8Cy%2Fn3vd1ZPXDTJdKDjHBe6wc0%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;449&quot; height=&quot;248&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;248&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;왼쪽이 기본 시스템 스크롤이고, 오른쪽이 커스텀된 스크롤입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 스크롤 하나도 신경써서 이쁘게 만들 수 있습니다. &lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>scroll</category>
      <category>scroll css</category>
      <category>scroll custom</category>
      <category>scroll customize</category>
      <category>scroll style</category>
      <category>스크롤 css</category>
      <category>스크롤 style</category>
      <category>스크롤 커스터마이징</category>
      <category>스크롤 커스텀</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/77</guid>
      <comments>https://wazacs.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 13 Oct 2023 20:08:17 +0900</pubDate>
    </item>
    <item>
      <title>[react-native Error] cli.init is not a function</title>
      <link>https://wazacs.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;0.69 버전 이후로 생기는 에러라고 하는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.68.2 로 설치해주니 해당 에러는 없어졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686573256059&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx react-native@0.68.2 init LottoApp --version 0.68.2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>cli.init is not a function</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/76</guid>
      <comments>https://wazacs.tistory.com/76#entry76comment</comments>
      <pubDate>Mon, 12 Jun 2023 21:34:32 +0900</pubDate>
    </item>
    <item>
      <title>mac 에서 rbenv install 에러 해결 : ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.</title>
      <link>https://wazacs.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;mac 에서 react-native 환경 세팅 과정에서 rbenv 를 설치해야하는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rbenv 까지 설치 이후 bundler를 설치하는 과정에서 다음과 같은 에러를 만났다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686381423449&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 끝에 찾은 방법은 설치이후 .zshrc 에 환경변수를 추가해줘야 한다는 걸 깨달았다. ㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686381610746&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vim ~/.zshrc // .zshrc 파일 열기 

// 해당 파일에서 아래 코드 추가 (추가: i, 저장+닫기는 : wq) 
[[ -d ~/.rbenv  ]] &amp;amp;&amp;amp; \
  export PATH=${HOME}/.rbenv/bin:${PATH} &amp;amp;&amp;amp; \
  eval &quot;$(rbenv init -)&quot;
  
  source ~/.zshrc // 변경된 코드 적용&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;이후 다시 번들러를 설치하면 잘 설치된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>FilePermissionError 에러</category>
      <category>rbenv install bundler error</category>
      <category>rbenv install bundler 에러</category>
      <category>rbenv install 에러</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/75</guid>
      <comments>https://wazacs.tistory.com/75#entry75comment</comments>
      <pubDate>Sat, 10 Jun 2023 16:21:51 +0900</pubDate>
    </item>
    <item>
      <title>Axios 로 파일과 JSON 파일 동시에 넘기기</title>
      <link>https://wazacs.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;보통 파일 업로드를 구현할때 Event 로 파일을 받은 후 formData에 담아서 multipart 로 보낸다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686187895225&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const files = e.target.files;
const formData = new FormData();

for (let file of files) formData.append('file', file);
// 혹은
formData.append('file', file[0]);&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;간혹 파일 이외에 추가 정보를 보낼때가 있는데, 그때에도 formData를 담아서 보내면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686187946159&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const fileInfo = &quot;user_type&quot;;
formData.append('type', fileInfo);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 Object 타입을 보내야할 때는 어떨까?&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;Object 타입은 보낼수 없으므로 우선 stringify를 사용하여 json 형태로 만들어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 뒤에 옵션을 붙여 application/json 타입으로 해당 데이터는 보내겠다고 추가를 해줘야 하는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가를 하려면 해당 데이터를 blob 타입으로 만들어야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blob(Binary Large Object)로 원래는 바이너리 형태로 객체를 저장하는 내장함수로, 주로 파일을 읽을때 쓰는데, 여기서는 multipart로 json 데이터를 보내기 위해 썼다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686188393707&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
let dataInfo = {
    fileType: &quot;user_type&quot;,
    categoryType: &quot;faq_file&quot;
}
formData.append('request', new Blob([JSON.stringify(dataInfo)], {type: 'application/json'}) );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 axios에서는 그냥 Content-Type을 multipart로 보내면 된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>form Data Object</category>
      <category>formData json 보내기</category>
      <category>multipart json</category>
      <category>multipart object</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/74</guid>
      <comments>https://wazacs.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 8 Jun 2023 10:41:35 +0900</pubDate>
    </item>
    <item>
      <title>node 버전을 여러개 쓰고싶다면 NVM을 써보자</title>
      <link>https://wazacs.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js는 다들 알다시파 웹 브라우저 외부에서 JavaScript 코드를 실행할 수 있는 JavaScript 런타임 환경이죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프론트엔드 개발자들은 NPM을 설치하여 node의 명령어를 사용합니다.&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;그렇다면 NVM은 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVM은 Node Version Manager의 약자로, 컴퓨터에서 여러 버전의 Node.js를 관리할 수 있는 도구입니다.&amp;nbsp;&lt;br /&gt;NVM을 사용하면 컴퓨터의 다른 Node.js 버전 간에 쉽게 전환하고, 특정 버전을 설치 및 제거하며, 글로벌 및 프로젝트별 Node.js 버전을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 버전의 Node.js를 서로 다른 디렉토리에 설치하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템의 PATH 환경 변수를 수정하여 서로 전환할 수 있도록 하는 방식으로 작동합니다.&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;이 기능은 다른 버전의 Node.js가 필요한 프로젝트에서 작업하거나 다른 버전의 Node.js에 대해 코드를 테스트하려는 경우에 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 저는 블록체인 개발할때 많이 유용했습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치하기 (window)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://github.com/coreybutler/nvm-windows/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/coreybutler/nvm-windows/releases&lt;/a&gt; 접속 후 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;nvm-setup.zip 파일을 다운받습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2. zip 파일을 풀고 install 한뒤, cmd 창을 열어 nvm version 명령어로 버전을 확인하세요.&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;color: #000000; text-align: start;&quot;&gt;만약, 설치가 제대로 안된 경우는 제 경우에는 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;window 에서 사용자 이름의 폴더가 한글도 되어있거나, 특수문자인 경우&lt;/span&gt;입니다. 이때는 매우 골치가 아픕니다.   저는 결국 다시 밀고 유저이름을 영문으로 바꾼뒤 재설치하니 잘 됬습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;또한 nvm 명령어를 막상 사용할때에는 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;관리자권한으로 실행해야 명령어가 먹었습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;자주사용하게되는 명령어 &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1. nvm install &amp;lt;version&amp;gt;&amp;nbsp; : node의&lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;특정 버전을 설치합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2. nvm uninstall &amp;lt;version&amp;gt;&amp;nbsp; : node&lt;span style=&quot;background-color: #fdfdfd; color: #000000; 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;color: #000000; text-align: start;&quot;&gt;3. nvm use &amp;lt;version&amp;gt; : &lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;node의 특정 버전을 사용합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;4. nvm ls : &lt;span style=&quot;background-color: #fdfdfd; color: #000000; text-align: start;&quot;&gt;시스템에 설치된 모든 node버전의 목록을 보여줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;5. nvm current : 현재 사용중인 node 버전을 확인합니다.&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;span style=&quot;color: #000000; text-align: start;&quot;&gt;사용예시&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1685978163755&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\WINDOWS\system32&amp;gt;nvm ls  // 목록확인

    18.3.0
    16.15.1
    15.14.0
  * 14.17.3 (Currently using 64-bit executable)
    10.15.3
    10.5.0
    8.10.0

C:\WINDOWS\system32&amp;gt;nvm install 16.18.0  // node 16.18.0 버전 설치
Downloading node.js version 16.18.0 (64-bit)...
Extracting...
Complete


Installation complete. If you want to use this version, type

nvm use 16.18.0

C:\WINDOWS\system32&amp;gt;nvm ls // 다시 목록확인

    18.3.0
    16.18.0  // &amp;lt;- 설치됨
    16.15.1
    15.14.0
  * 14.17.3 (Currently using 64-bit executable)
    10.15.3
    10.5.0
    8.10.0

C:\WINDOWS\system32&amp;gt;nvm use 16.18.0 // node 16.18.0  버전을 사용
Now using node v16.18.0 (64-bit)

C:\WINDOWS\system32&amp;gt;nvm current // 현재 사용중인 node 버전 확인 
v16.18.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /ETC</category>
      <category>node 버전 여러개</category>
      <category>nvm</category>
      <category>nvm 명령어</category>
      <category>nvm 버전 삭제</category>
      <category>nvm 버전 설치</category>
      <category>nvm 에서</category>
      <category>nvm 윈도우 설치</category>
      <category>nvm이란</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/73</guid>
      <comments>https://wazacs.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 6 Jun 2023 00:19:52 +0900</pubDate>
    </item>
    <item>
      <title>yarn : 이 시스템에서 스크립트를 실행할 수 없으므로 C:\Program Files\nodejs\yarn.ps1 파일을 로드할 수 없습니다.</title>
      <link>https://wazacs.tistory.com/72</link>
      <description>&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. 문제 발생&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn 으로 스크립트를 실행 하려고 하는데 다음과 같은 에러가 나타났다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Window 내에서 정책 설정이 제한되어있다면, yarn같은 외부 스크립트가 믿을 만한 스크립트인지 알 수 없기 때문에 실행을 안하는 것이다. 특히, .ps1xml, .psm1을 비롯한 스크립트 파일의 실행을 제한한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 기본값이 클라이언트용은 Restricted,&amp;nbsp; 서버용은 RemoteSigned로 되어있다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLVMnM/btrXmcQmcw8/mw4jyGzaJn3DjxTaG865Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLVMnM/btrXmcQmcw8/mw4jyGzaJn3DjxTaG865Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLVMnM/btrXmcQmcw8/mw4jyGzaJn3DjxTaG865Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLVMnM%2FbtrXmcQmcw8%2Fmw4jyGzaJn3DjxTaG865Kk%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;1048&quot; height=&quot;65&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 해결 방안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;windows 에서 스크립트 실행 정책을 변경해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 현재 상태가 Restricted로 제한이 되어있다면, yarn 에서 제공 되는 일부 스크립트가 실행이 안되는 듯 하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Restricted 를 RemotedSigned 로 변경 해준다면 yarn 도 실행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1674801139718&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 정책 확인
PS C:\Windows\system32&amp;gt; ExecutionPolicy 
Restricted

// 정책 변경 y 입력 후 Enter 
PS C:\Windows\system32&amp;gt; Set-ExecutionPolicy RemoteSigned
실행 규칙 변경
실행 정책은 신뢰하지 않는 스크립트로부터 사용자를 보호합니다. 실행 정책을 변경하면 about_Execution_Policies 도움말
항목(https://go.microsoft.com/fwlink/?LinkID=135170)에 설명된 보안 위험에 노출될 수 있습니다. 실행 정책을
변경하시겠습니까?
[Y] 예(Y)  [A] 모두 예(A)  [N] 아니요(N)  [L] 모두 아니요(L)  [S] 일시 중단(S)  [?] 도움말 (기본값은 &quot;N&quot;): Y

// 정책 확인 하면 RemoteSigned로 바뀐것을 확인 할 수 있다.
PS C:\Windows\system32&amp;gt; ExecutionPolicy
RemoteSigned&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.3&amp;amp;viewFallbackFrom=powershell-7.1&quot;&gt;참조 : https://learn.microsoft.com/ko-kr/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.3&amp;amp;viewFallbackFrom=powershell-7.1&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>poweshell 정책 변경</category>
      <category>window 정책 변경</category>
      <category>yarn error</category>
      <category>yarn run error</category>
      <category>yarn run 에러</category>
      <category>yarn start error</category>
      <category>yarn 에러</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/72</guid>
      <comments>https://wazacs.tistory.com/72#entry72comment</comments>
      <pubDate>Fri, 27 Jan 2023 16:11:13 +0900</pubDate>
    </item>
    <item>
      <title>React에서 Router state 사용</title>
      <link>https://wazacs.tistory.com/71</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router에서 navigate로 이동시 state에 객체 형태로 값을 넘겨서 주고 받을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 parameter 방식과는 달리 따로 router url 뒤에 값을 설정해주지 않아도 되고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url에 넘기는 값이 노출 되지 않아서 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674038031048&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 보낼때 
navigate(URL, {state: {id: &quot;user1&quot;}})

// 받을때 
import {useLocation} from &quot;react-router&quot;;

const location = useLocation();
const id = location.state ? location.state.id : null; // &amp;lt;= &quot;user1&quot;&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;기존 목록에서 상세이동할때 요긴하게 쓰긴 했는데,&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;&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;1. url에 값을 노출 시키지 않고 값을 넘길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. url 끝에 다수가 될 수도 있는 값이 붙지 않기 때문에, 혹시라도 pathname값을 계산해야하는 경우 일관되게 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Routes에서 path 값에 별도의 설정없이 바로 쓸 수 있어서 간편하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 아마.. 그외에도 더 많은 장점이 있을 것이다...&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;1. url로 바로 접근이 필요한 경우는 넘기는 state값이 없으므로 값을 알 수 없다.&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;2. 같은 url인 경우 해당값을 useEffect를 통해서 감지해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 특정 문제점 중에 다른페이지로 이동했을때 기존 스크롤의 위치가 그대로 남아있어서, 모든 페이지를 감싸는 부분에&amp;nbsp; pathname의 변화를 감지해서 window.scrollTo(0, 0)을 넣어줬었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상태에서 게시글 상세의 경우 다음이나 이전 페이지로 넘어가려면 같은 url에 아이디 값만 바뀌는 케이스기 때문에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 선언된 윈도우 스크롤을 상단으로 올리는 스크립트가 먹질 않아, 이럴때는 아이디 값이 결국 state이기 때문에 이를 useEffect로 감지해서 업데이트 해줘야 한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /일하고,정리하기</category>
      <category>react router state</category>
      <category>router state</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/71</guid>
      <comments>https://wazacs.tistory.com/71#entry71comment</comments>
      <pubDate>Wed, 18 Jan 2023 19:52:25 +0900</pubDate>
    </item>
    <item>
      <title>React amchart 를 하면서~!</title>
      <link>https://wazacs.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;버전은 amchart5 로 진행 했다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. axis의 x, y 컬럼사이즈 조정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 4개인데, 화면사이즈가 작다보니 amchart에서 자동으로 2개만 label 을 표시하는 현상이 있었다.&amp;nbsp;&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dG479a/btrVzuZsiJC/8KKQxePdKQZO1xH7iwvHI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dG479a/btrVzuZsiJC/8KKQxePdKQZO1xH7iwvHI0/img.png&quot; data-alt=&quot;예시 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dG479a/btrVzuZsiJC/8KKQxePdKQZO1xH7iwvHI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdG479a%2FbtrVzuZsiJC%2F8KKQxePdKQZO1xH7iwvHI0%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;520&quot; height=&quot;352&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;352&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;그래서 x의 사이즈를 줄이거나 해주는 옵션이 있을꺼 같아서 열심히 width나 min, max 를 열심히 찾아보았는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 render를 할때 최소 grid distance 값을 조정해주면 되었다~  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x뿐만아니라 y의 값을 조정하고 싶으면 마찬가지로 y의 데이터를 만들때 같은 옵션값을 넣어주면 된다~&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;let xAxis = chart.xAxes.push(
    am5xy.CategoryAxis.new(root, {
        renderer: am5xy.AxisRendererX.new(root, {
            minGridDistance: 25, // &amp;lt;- 25면 100/25 의 값만큼 보여준다.(즉, 25면 총 4개)
        }),
        categoryField: &quot;name&quot;,
    })
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. label의 사이즈조정, 높이 조정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이즈의 경우 theme을 따로 적용 시켰는데, theme을 따로 적용 하지 않고 바로 옵션을 적용 할 수 있는 방법이 있는지는 모르겠다. theme을 적용시키려면, 불러온 am5에서 Theme을 따로 불러와 변수에 선언하고 그안에서 조정하고자 하는 값을 set을 통해 적용 시켜주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참조: &lt;a href=&quot;https://www.amcharts.com/docs/v5/concepts/themes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.amcharts.com/docs/v5/concepts/themes/&lt;/a&gt; )&lt;/p&gt;
&lt;pre id=&quot;code_1672990688915&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let myTheme = am5.Theme.new(root); // theme 선언
myTheme.rule(&quot;Label&quot;).set(&quot;fontSize&quot;, &quot;0.8em&quot;); // label 폰트 크기 조정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axis X의 label 이 너무 붙어있어서 아래로 좀 띄고 싶었는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 별도 theme적용 없이 바로 labels에 접근하여 setting을 해주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672990835781&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;xAxis.get(&quot;renderer&quot;).labels.template.setAll({
    paddingTop: 20
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0ViI/btrVvusx1Cc/jDlBxAMcKwVg7tZSbwO51k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0ViI/btrVvusx1Cc/jDlBxAMcKwVg7tZSbwO51k/img.png&quot; data-alt=&quot;결과 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0ViI/btrVvusx1Cc/jDlBxAMcKwVg7tZSbwO51k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0ViI%2FbtrVvusx1Cc%2FjDlBxAMcKwVg7tZSbwO51k%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;478&quot; height=&quot;328&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;328&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;이렇게 해서 원하는 모양의 차트를 만들 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 차트를 적용하는 부분은 따로 정리해서 나중에 포스팅 해볼까 한다~&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;ref: &lt;a href=&quot;https://www.amcharts.com/docs/v5/charts/percent-charts/pie-chart/pie-series/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.amcharts.com/docs/v5/charts/percent-charts/pie-chart/pie-series/&lt;/a&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;Disabling pull-out 부분을 보면 되는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toggleKey값을 none으로 바꿔주면 된다. (기본은 true 설정 되어있다.)&lt;/p&gt;
&lt;pre id=&quot;code_1673405145832&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;series.slices.template.set(&quot;toggleKey&quot;, &quot;none&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 클릭시 pull-out되는 사이즈를 조정하고 싶다면&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673414399268&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pieSeries.slices.template.states.create(&quot;active&quot;, {
	shiftRadius: 10, // &amp;lt;- 기본이 20정도인것같다. 
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 조정하면 된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /일하고,정리하기</category>
      <category>amchart labels padding</category>
      <category>amchart mobile size issue</category>
      <category>amchart size</category>
      <category>amchart5</category>
      <category>amchart5 click prevent</category>
      <category>amchart5 column width</category>
      <category>amchart5 event disabled</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/70</guid>
      <comments>https://wazacs.tistory.com/70#entry70comment</comments>
      <pubDate>Fri, 6 Jan 2023 16:42:37 +0900</pubDate>
    </item>
    <item>
      <title>screenOptions로 헤더 꾸미기</title>
      <link>https://wazacs.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;react-router로 구조를 짜고 나면 자동으로 헤더에 name이 들어가 있고 기본적으로 흰색에 검은 글씨로 되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 커스텀하기 위해&amp;nbsp; screenOption값을 넣어줄 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통으로 처리하려면 Stack.Navigator에 일괄로 처리할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 navigator에 있는 screen들을 group으로 묶어서 따로 처리해 줄 수 도 있다.&amp;nbsp;&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;1. 공통으로 옵션 주기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;title을 옵션에 넣게 되면, HomeStack.Screen 안에 name&amp;nbsp; 대신 HomeStack.Navigator 안의 title을 보여준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일은 headerStyle 안에 작성해 주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659192369570&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;NavigationContainer&amp;gt;
    &amp;lt;HomeStack.Navigator
        screenOptions={{
            title: 'Fruits zone  ',
            headerStyle: {
                backgroundColor: 'lightgreen'
            }
        }}
    &amp;gt;
        &amp;lt;HomeStack.Screen name=&quot;Home&quot; component={Home} /&amp;gt;
        &amp;lt;HomeStack.Screen  name=&quot;ReviewDetails&quot; component={ReviewDetails} /&amp;gt;
    &amp;lt;/HomeStack.Navigator&amp;gt;
&amp;lt;/NavigationContainer&amp;gt;&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;552&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s7dtZ/btrItmAGOxV/SxCfSd2EW43fJK1khJB2RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s7dtZ/btrItmAGOxV/SxCfSd2EW43fJK1khJB2RK/img.png&quot; data-alt=&quot;적용된 Home 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s7dtZ/btrItmAGOxV/SxCfSd2EW43fJK1khJB2RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs7dtZ%2FbtrItmAGOxV%2FSxCfSd2EW43fJK1khJB2RK%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;552&quot; height=&quot;596&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적용된 Home 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lvqQx/btrIypbRByT/3llem6GadF4OvxzAutRYwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lvqQx/btrIypbRByT/3llem6GadF4OvxzAutRYwk/img.png&quot; data-alt=&quot;적용된 ReviewDetails 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lvqQx/btrIypbRByT/3llem6GadF4OvxzAutRYwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlvqQx%2FbtrIypbRByT%2F3llem6GadF4OvxzAutRYwk%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;542&quot; height=&quot;584&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적용된 ReviewDetails 화면&lt;/figcaption&gt;
&lt;/figure&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;2. 페이지별로 각각 주기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지별로 각각 커스텀을 하고 싶다면 Stack.Group을 사용하여 각각 묶을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659192639651&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;NavigationContainer&amp;gt;
    &amp;lt;HomeStack.Navigator&amp;gt;
        &amp;lt;HomeStack.Group 
            screenOptions={{
                title: 'Fruits List  ',
                headerStyle: {
                    backgroundColor: 'lightgreen'
                }
            }}
        &amp;gt;
            &amp;lt;HomeStack.Screen name=&quot;Home&quot; component={Home} /&amp;gt;
        &amp;lt;/HomeStack.Group&amp;gt;
        &amp;lt;HomeStack.Group
            screenOptions={{
                title: 'Fruits Details  ',
                headerStyle: {
                    backgroundColor: 'lightyellow'
                }
            }}
        &amp;gt;
            &amp;lt;HomeStack.Screen 
                name=&quot;ReviewDetails&quot; 
                component={ReviewDetails} 

            /&amp;gt;
        &amp;lt;/HomeStack.Group&amp;gt;
    &amp;lt;/HomeStack.Navigator&amp;gt;
&amp;lt;/NavigationContainer&amp;gt;&lt;/code&gt;&lt;/pre&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-origin-width=&quot;555&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bILsjj/btrIzD84Eq3/38UZcj5ixWuxOiIzMYN1C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bILsjj/btrIzD84Eq3/38UZcj5ixWuxOiIzMYN1C1/img.png&quot; data-alt=&quot;적용된 Home 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bILsjj/btrIzD84Eq3/38UZcj5ixWuxOiIzMYN1C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbILsjj%2FbtrIzD84Eq3%2F38UZcj5ixWuxOiIzMYN1C1%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;555&quot; height=&quot;636&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적용된 Home 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMvdA/btrIA1hAoiW/cNHSi5rBMx1RB9P3zSBtD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMvdA/btrIA1hAoiW/cNHSi5rBMx1RB9P3zSBtD0/img.png&quot; data-alt=&quot;적용된 ReviewDetails 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMvdA/btrIA1hAoiW/cNHSi5rBMx1RB9P3zSBtD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMvdA%2FbtrIA1hAoiW%2FcNHSi5rBMx1RB9P3zSBtD0%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;543&quot; height=&quot;601&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;601&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적용된 ReviewDetails 화면&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;headerStyle에 높이도 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글자색은 headerStyle에 들어가지 않고 따로 옵션으로 headerTintColor안에 넣는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659195446988&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{...}
&amp;lt;HomeStack.Group
    screenOptions={{
        title: 'Fruits Details  ', // 페이지 이름
        headerTintColor: 'brown', // 텍스트 컬러
        headerStyle: {
            backgroundColor: 'lightyellow', // 배경색 
            height: 120, // 높이
            
        }
    }}
&amp;gt;
{...}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발  /React Native</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/69</guid>
      <comments>https://wazacs.tistory.com/69#entry69comment</comments>
      <pubDate>Sat, 30 Jul 2022 23:51:23 +0900</pubDate>
    </item>
    <item>
      <title>React Router</title>
      <link>https://wazacs.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- react navigation:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://reactnavigation.org/docs/getting-started/&quot;&gt;https://reactnavigation.org/docs/getting-started/&lt;/a&gt;&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;b&gt;&quot;@react-navigation/native&quot;:&amp;nbsp;&quot;^6.0.11&quot;, &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&quot;@react-navigation/stack&quot;:&amp;nbsp;&quot;^6.2.2&quot;,&lt;/b&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;리액트 네이티브에서 사용하는 react-navigation은 stack 방식을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack 방식은 클릭을 했을때 화면이 하나씩 stack에 쌓이는데 그중 최상위 것을 보여주는 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 back 버튼을 누르면 최상단의 화면을 제거하므로써 이전 화면을 보여준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20220719_233634636_01.jpg&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YHs5I/btrHKnS6IR7/XLd16YC7WqljdsWzmevfKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YHs5I/btrHKnS6IR7/XLd16YC7WqljdsWzmevfKK/img.jpg&quot; data-alt=&quot;나름이해가 쉽게 그려보았다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YHs5I/btrHKnS6IR7/XLd16YC7WqljdsWzmevfKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYHs5I%2FbtrHKnS6IR7%2FXLd16YC7WqljdsWzmevfKK%2Fimg.jpg&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;1018&quot; height=&quot;1440&quot; data-filename=&quot;KakaoTalk_20220719_233634636_01.jpg&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;1440&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 설치&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658931461483&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;expo install @react-navigation/native
expo install @react-navigation/stack&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 화면 이동하기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Home 과 Detail 페이지 이동하기&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;createStackNavigator() 로 Stack을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack.Navigator 안에 Stack.Screen을 사용하여 연결할 screen을 생성한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack.screen에 name 에 URL을 넣고 component를 연결한다.&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;화면에서 props으로 navigation을 받을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;console로 어떤 값이 넘어오는지 확인해보면 다양한 기능들이 있다.&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;navigate(url) 을 통하여 url로 상세페이지로 이동 할 수 있고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세 화면에서는 goBack()을 사용하여 다시 Home 페이지로 돌아갈 수 있다.&amp;nbsp;&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;참고로 navigate 대신 push를 사용해도 좋다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;navigate와 goBack도 같이 쓸수 있지만, 위의 그림처럼 navigate는 위로 페이지를 새로 쌓는방식일 것 같고, goBack은 최상단 페이지를 없애는 방식일 것 같다. (정확히 소스를 분석해보거나 테스트를 해보지 않아서 100% 확실하지는 않다! )&lt;/p&gt;
&lt;pre id=&quot;code_1658930968673&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;navigation &amp;gt;&amp;gt;  Object {
  &quot;addListener&quot;: [Function addListener],
  &quot;canGoBack&quot;: [Function canGoBack],
  &quot;dispatch&quot;: [Function dispatch],
  &quot;getId&quot;: [Function getId],
  &quot;getParent&quot;: [Function getParent],
  &quot;getState&quot;: [Function anonymous],
  &quot;goBack&quot;: [Function anonymous],
  &quot;isFocused&quot;: [Function isFocused],
  &quot;navigate&quot;: [Function anonymous],
  &quot;pop&quot;: [Function anonymous],
  &quot;popToTop&quot;: [Function anonymous],
  &quot;push&quot;: [Function anonymous],
  &quot;removeListener&quot;: [Function removeListener],
  &quot;replace&quot;: [Function anonymous],
  &quot;reset&quot;: [Function anonymous],
  &quot;setOptions&quot;: [Function setOptions],
  &quot;setParams&quot;: [Function anonymous],
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1658930452873&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HomeNavigator.js
import { createStackNavigator } from &quot;@react-navigation/stack&quot;;
import { NavigationContainer } from '@react-navigation/native';

// components
import Home from &quot;../screens/Home&quot;;
import ReviewDetails from '../screens/ReviewDetails'

const HomeStack = createStackNavigator()

export default function HomeNavigator() {
    return(
        &amp;lt;NavigationContainer&amp;gt;
            &amp;lt;HomeStack.Navigator&amp;gt;
                &amp;lt;HomeStack.Screen name=&quot;Home&quot; component={Home} /&amp;gt;
                &amp;lt;HomeStack.Screen name=&quot;ReviewDetails&quot; component={ReviewDetails} /&amp;gt;
            &amp;lt;/HomeStack.Navigator&amp;gt;
        &amp;lt;/NavigationContainer&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1658930636119&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Home.js
import { StyleSheet, View, Text, Button } from 'react-native'
import React, { useState } from 'react'
import { globalStyles } from '../styles/global'

export default function Home({ navigation }) {
  const pressHandler = () =&amp;gt; {
    navigation.navigate('ReviewDetails')
    // navigation.push('ReviewDetails')
  }

  return (
    &amp;lt;View style={globalStyles.container}&amp;gt;
      &amp;lt;Text style={globalStyles.titleText}&amp;gt;Home Screen&amp;lt;/Text&amp;gt;
      &amp;lt;Button title='Go to review2' onPress={pressHandler} /&amp;gt;
    &amp;lt;/View&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1658930673020&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { StyleSheet, View, Text, Button } from 'react-native'
import React, { useEffect } from 'react'

export default function ReviewDetails({navigation}) {

  const pressHandler = () =&amp;gt; {
    // navigation.navigate('Home')
    navigation.goBack()
  }
  
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Button title=&quot;Go back to Home&quot; onPress={pressHandler} /&amp;gt;
    &amp;lt;/View&amp;gt;
  )
}


const styles = StyleSheet.create({
    container: {
        padding: 24
    }
})&lt;/code&gt;&lt;/pre&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;rn-navigation01.gif&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D62xl/btrIks1tu3z/1yTa2EtNWhi09aIJs0HrzK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D62xl/btrIks1tu3z/1yTa2EtNWhi09aIJs0HrzK/img.gif&quot; data-alt=&quot;페이지 이동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D62xl/btrIks1tu3z/1yTa2EtNWhi09aIJs0HrzK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/D62xl/btrIks1tu3z/1yTa2EtNWhi09aIJs0HrzK/img.gif&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;536&quot; height=&quot;548&quot; data-filename=&quot;rn-navigation01.gif&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;548&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;&amp;nbsp;&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;2. 페이지에서 파라미터로 데이터 받기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에서 props으로 navigation를 받아서 페이지 이동하는 함수를 쓸 수 있었는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 route를 props으로 받아서 data를 전달 할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlatList 에 item을 클릭시 상세 페이지로 이동하면서 데이터를 같이 넘겨 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1658931255650&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Home.js
import { StyleSheet, View, Text, Button, FlatList, TouchableOpacity } from 'react-native'
import React, { useState } from 'react'
import { globalStyles } from '../styles/global'

export default function Home({ navigation }) {

  const [reviews, setReviews] = useState([
    { title: 'Apple', rating: 5, body: 'Apple is yam', key: '1' },
    { title: 'Banana', rating: 3, body: 'Banana is sweet', key: '2' },
    { title: 'Orange', rating: 2, body: 'Orange is great', key: '3' },
  ])

  const pressHandler = () =&amp;gt; {
    navigation.navigate('ReviewDetails')
    // navigation.push('ReviewDetails')
  }

  return (
    &amp;lt;View style={globalStyles.container}&amp;gt;
      &amp;lt;Text style={globalStyles.titleText}&amp;gt;Home Screen&amp;lt;/Text&amp;gt;
      &amp;lt;Button title='Go to review2' onPress={pressHandler} /&amp;gt;
      &amp;lt;FlatList
        data={reviews}
        renderItem={({ item }) =&amp;gt; (
          // navigate('link', obj) 2번째 파라미터로 오브젝트를 보낼수 있다. 
          &amp;lt;TouchableOpacity onPress={() =&amp;gt; navigation.navigate('ReviewDetails', item)}&amp;gt;
            &amp;lt;Text
               style={globalStyles.titleText}
            &amp;gt;{item.title}&amp;lt;/Text&amp;gt;
          &amp;lt;/TouchableOpacity&amp;gt;
        )}
      /&amp;gt;
    &amp;lt;/View&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1658931270021&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { StyleSheet, View, Text, Button } from 'react-native'
import React, { useEffect } from 'react'

export default function ReviewDetails({route, navigation}) {

  const {title, body, rating} = route.params

  const pressHandler = () =&amp;gt; {
    // navigation.navigate('Home')
    navigation.goBack()
  }

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Text&amp;gt;{title}&amp;lt;/Text&amp;gt;
      &amp;lt;Text&amp;gt;{body}&amp;lt;/Text&amp;gt;
      &amp;lt;Text&amp;gt;{rating}&amp;lt;/Text&amp;gt;
      &amp;lt;Button title=&quot;Go back to Home&quot; onPress={pressHandler} /&amp;gt;
    &amp;lt;/View&amp;gt;
  )
}


const styles = StyleSheet.create({
    container: {
        padding: 24
    }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rn-navigation02.gif&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/onf6Q/btrIjJh1Ej8/fTf8k2tVaXtkIXqvY1BoXk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/onf6Q/btrIjJh1Ej8/fTf8k2tVaXtkIXqvY1BoXk/img.gif&quot; data-alt=&quot;목록 클릭 상세이동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/onf6Q/btrIjJh1Ej8/fTf8k2tVaXtkIXqvY1BoXk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/onf6Q/btrIjJh1Ej8/fTf8k2tVaXtkIXqvY1BoXk/img.gif&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;536&quot; height=&quot;548&quot; data-filename=&quot;rn-navigation02.gif&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;548&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 구현할 수 있다!&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;* 번외&lt;/b&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;그리고 react-navigation/stack 를 같이 설치해 주는데, 이는 공식문서에 따르면 stack의 최상위 있는 컴포넌트간의 스크린 전환을 위해 사용되는 듯하다. (Stack Navigator provides a way for your app to transition between screens where each new screen is placed on top of a stack.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- react-navigation/stack :&amp;nbsp;&lt;a href=&quot;https://reactnavigation.org/docs/stack-navigator/&quot;&gt;https://reactnavigation.org/docs/stack-navigator/&lt;/a&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;&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;&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /React Native</category>
      <category>react native navigation</category>
      <category>react-native</category>
      <category>react-native navigation</category>
      <category>react-navigation</category>
      <category>react-navigation/stack</category>
      <category>react-navitve route</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/68</guid>
      <comments>https://wazacs.tistory.com/68#entry68comment</comments>
      <pubDate>Tue, 19 Jul 2022 23:41:57 +0900</pubDate>
    </item>
    <item>
      <title>[expo] 웹폰트 webfont 적용하기</title>
      <link>https://wazacs.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사실 expo 공식 문서에 다 나와있다. 근데 공식문서 뒤지기도 귀찮으니 정리해 두기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서 url : &lt;a href=&quot;https://docs.expo.dev/guides/using-custom-fonts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.expo.dev/guides/using-custom-fonts/&lt;/a&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;h3 data-ke-size=&quot;size23&quot;&gt;1. 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;@expo-google-fonts/inter 를 먼저 설치해 준다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-text=&quot;true&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;$ expo install expo-font @expo-google-fonts/inter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 적용할 폰트 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fonts.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://fonts.google.com/&lt;/a&gt; 사이트에 들어가서 원하는 폰트를 찾고 다운로드한다. 혹은 이름만 알아도 되는 모양이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 눈에 띄게 비교하기 위한 폰트를 찾았다. &quot;Edu SA Beginner&quot; 라는 글씨체를 찾았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4syD6/btrHCWIR0aG/GCTXk18e7ZfkquZUWRiktK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4syD6/btrHCWIR0aG/GCTXk18e7ZfkquZUWRiktK/img.png&quot; data-alt=&quot;폰트 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4syD6/btrHCWIR0aG/GCTXk18e7ZfkquZUWRiktK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4syD6%2FbtrHCWIR0aG%2FGCTXk18e7ZfkquZUWRiktK%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;1500&quot; height=&quot;1189&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1189&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. App에 component가 render되기전에 적용하기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import { useFonts } from 'expo-font 를 통해 useFont를 불러온 뒤 font load가 되기전엔 statusbar를 띄워주고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 불러와졌다면 컴포넌트를 render 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658148179670&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import { Text, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { useFonts } from 'expo-font';
import Home from './screens/Home'

export default props =&amp;gt; {
  let [fontsLoaded] = useFonts({
    'Edu-SA-Beginner-regular': require('./assets/fonts/EduSABeginner-Regular.ttf'),
    'Edu-SA-Beginner-medium': require('./assets/fonts/EduSABeginner-Medium.ttf'),
    'Edu-SA-Beginner-bold': require('./assets/fonts/EduSABeginner-Bold.ttf'),
    'Edu-SA-Beginner-semibold': require('./assets/fonts/EduSABeginner-SemiBold.ttf')
  });

  if (!fontsLoaded) {
    return &amp;lt;StatusBar /&amp;gt;;
  }

  return (
    &amp;lt;Home/&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. component에서 써보기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 불러오고나면 전역으로 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 그냥 기존처럼 fontFamily에 사용하면된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658148214647&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Home.js
import { StyleSheet, View, Text } from 'react-native'
import React, { useState } from 'react'


export default function Home() {
  return (
    &amp;lt;View&amp;gt;
      &amp;lt;Text style={{fontFamily: 'Edu-SA-Beginner-semibold'}}&amp;gt;Home&amp;lt;/Text&amp;gt;
      &amp;lt;Text&amp;gt;Home&amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&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-origin-width=&quot;486&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vM8fD/btrHFH4LY11/Y5ObOE3bi7Ew7oDQwiOH1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vM8fD/btrHFH4LY11/Y5ObOE3bi7Ew7oDQwiOH1k/img.png&quot; data-alt=&quot;적용 완료된 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vM8fD/btrHFH4LY11/Y5ObOE3bi7Ew7oDQwiOH1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvM8fD%2FbtrHFH4LY11%2FY5ObOE3bi7Ew7oDQwiOH1k%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;486&quot; height=&quot;401&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;401&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;잘 적용된것을 알 수 있다.  &lt;/p&gt;</description>
      <category>개발  /React Native</category>
      <category>expo font</category>
      <category>expo 웹폰트적용</category>
      <category>react native download font</category>
      <category>react native google font</category>
      <category>react native 기초</category>
      <category>리액트네이티브 웹폰트</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/67</guid>
      <comments>https://wazacs.tistory.com/67#entry67comment</comments>
      <pubDate>Mon, 18 Jul 2022 21:45:30 +0900</pubDate>
    </item>
    <item>
      <title>TextInput 입력시 Keyboard 창 닫기</title>
      <link>https://wazacs.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;입력을 해야하는 화면에서 TextInput에 focus 가 되면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 Keyboard가 올라오는데, 아무곳이나 클릭했을때 이를 다시 내려가게 하고싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TouchableWithoutFeedback, Keyboard 를 사용하여 간단하게 구현할 수 있다. \&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-origin-width=&quot;560&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oRsz6/btrHqoy2fCK/0NWF7iktyc5RVdQbed4290/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oRsz6/btrHqoy2fCK/0NWF7iktyc5RVdQbed4290/img.png&quot; data-alt=&quot;해당 부분을 내리고 싶다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oRsz6/btrHqoy2fCK/0NWF7iktyc5RVdQbed4290/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoRsz6%2FbtrHqoy2fCK%2F0NWF7iktyc5RVdQbed4290%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;560&quot; height=&quot;825&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;825&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TouchableWithoutFeedback 태그로 전체 영역을 감싼되 onPress 함수에 Keyboard.dismiss() 함수만 호출하면 간단하게 해제된다.&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;pre id=&quot;code_1657979763976&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return (  
    &amp;lt;TouchableWithoutFeedback onPress={() =&amp;gt; {
      console.log('dismissed keyboard')
      Keyboard.dismiss()
    }}&amp;gt;
      &amp;lt;View style={styles.container}&amp;gt;
        &amp;lt;Header/&amp;gt;
        &amp;lt;TextInput
            style={styles.input} 
            placeholder='Write something...'
            onChangeText={chageHandler}
        /&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/TouchableWithoutFeedback&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&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;습관적으로 javascript로만 해결하려고 했는데... 그냥 웬만하면 다 불러오면 된다. 허허 머쓱코쓱 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /React Native</category>
      <category>mobile keyboard close</category>
      <category>react native expo keyboard</category>
      <category>react native keyboard</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/66</guid>
      <comments>https://wazacs.tistory.com/66#entry66comment</comments>
      <pubDate>Sat, 16 Jul 2022 22:59:16 +0900</pubDate>
    </item>
    <item>
      <title>expo로 시작할때 기본 설지 목록 정리</title>
      <link>https://wazacs.tistory.com/65</link>
      <description>&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 디렉터리에 설치하는 경우 디렉터리명 대신 . 를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1657377441821&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; expo init (디렉터리명)
 expo init .&lt;/code&gt;&lt;/pre&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;##&amp;nbsp;react&amp;nbsp;navigation&lt;/b&gt;&amp;nbsp;:&amp;nbsp;&lt;a href=&quot;https://reactnavigation.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://reactnavigation.org/&lt;/a&gt; &lt;/p&gt;
&lt;pre id=&quot;code_1657377562981&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @react-navigation/native
expo install react-native-screens react-native-safe-area-context ( for expo )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;stack&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657377612229&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @react-navigation/stack
npm install react-native-gesture-handler&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;##&amp;nbsp;icons&lt;/b&gt;&amp;nbsp;:&amp;nbsp;&lt;a href=&quot;https://www.npmjs.com/package/@expo/vector-icons&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/@expo/vector-icons&lt;/a&gt;&amp;nbsp; &lt;/p&gt;
&lt;pre id=&quot;code_1657377625273&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i @expo/vector-icons&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /React Native</category>
      <category>리액트 네이티브 기본 설치</category>
      <category>리액트 네이티브 네비게이션</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/65</guid>
      <comments>https://wazacs.tistory.com/65#entry65comment</comments>
      <pubDate>Sat, 9 Jul 2022 23:42:21 +0900</pubDate>
    </item>
    <item>
      <title>[React-native] While trying to resolve module `idb` from file ...</title>
      <link>https://wazacs.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;firebase 연동 후 실행시 이런 에러가 발생하였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655561751320&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;While trying to resolve module 'idb' from file&amp;hellip;.this package itself specifies a `main` module field that could not be resolved&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;firebase 버전 문제인것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@9.6.11로 재설치 해주니 에러가 사라졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655561852316&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm uninstall firebase
npm install firebase@9.6.11&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재설치 후 재실행 해주면 된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>expo firebase error</category>
      <category>react-native firebase error</category>
      <category>`idb` from file</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/64</guid>
      <comments>https://wazacs.tistory.com/64#entry64comment</comments>
      <pubDate>Sat, 18 Jun 2022 23:18:45 +0900</pubDate>
    </item>
    <item>
      <title>TODOLIST 만들기 step by step - Vanilla JavaScript</title>
      <link>https://wazacs.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;본 포스팅은 javascript에 입문하고자 하려고 할때 가볍게 학습 할 수 있도록&amp;nbsp;나름의 커리큘럼을 짜본 것입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;todo-list 프로젝트는 다른 게시글이나 유투브에도 많이 나와있기는 하지만,&amp;nbsp;초보자가 좀 더 접근하기 쉽도록 순차적으로 진행 과정을 쪼개 보았습니다.&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;순서&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 글 추가하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 글 삭제하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. 글 수정하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. localStorage에 저장하기&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;* &lt;span&gt;html과 css는 대충 만들어 놓았습니다. (정말 대충 ㅎ..)&lt;/span&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;이 소스를 다운받아 시작하면 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/dtErmI/btrvFAxsi3s/TkrUlwMi0WBUfLjh9qEPUk/1-initial.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;1-initial.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;span&gt;기능 목록을 참조하여 미리 소스를 만들어본 뒤,&amp;nbsp;&lt;/span&gt;&lt;span&gt;완성된 소스와 비교해 보세요!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 글 추가하기&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;구현할 기능 목록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;-&amp;nbsp;input&amp;nbsp;입력&amp;nbsp;후&amp;nbsp;&quot;ADD&quot;&amp;nbsp;버튼&amp;nbsp;누르면&amp;nbsp;하단에&amp;nbsp;추가 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;input&amp;nbsp;이&amp;nbsp;빈값인&amp;nbsp;경우&amp;nbsp;&quot;ADD&quot;&amp;nbsp;버튼&amp;nbsp;클릭시&amp;nbsp;&quot;값을&amp;nbsp;입력하세요!&quot;&amp;nbsp;alert을&amp;nbsp;띄운&amp;nbsp;후&amp;nbsp;input으로&amp;nbsp;focus &lt;br /&gt;&amp;nbsp;-&amp;nbsp;목록에&amp;nbsp;아무것도&amp;nbsp;없을때는&amp;nbsp;&quot;데이터가&amp;nbsp;없습니다&quot;&amp;nbsp;출력&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;▶ 소스보기&lt;/span&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 html 내에 todo_item은 더이상 필요가 없으니 주석 처리 하거나, 삭제해 주세요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;javascript 에서 알아서 render를 할 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646972033217&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // index.html
    &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
        &amp;lt;div class=&quot;form&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; class=&quot;todo_input&quot;&amp;gt;
            &amp;lt;button class=&quot;btn_add&quot;&amp;gt;ADD&amp;lt;/button&amp;gt;
            &amp;lt;button class=&quot;btn_clear&quot;&amp;gt;CLEAR&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;todo_list_wrap&quot;&amp;gt;
            &amp;lt;ul class=&quot;todo_list&quot;&amp;gt;
                &amp;lt;!-- 편집기능 활성화 : todo_item + edit_wrap --&amp;gt;
                &amp;lt;!-- &amp;lt;li class=&quot;todo_item &quot;&amp;gt;
                    &amp;lt;div class=&quot;edit_wrap&quot;&amp;gt;
                        &amp;lt;input type=&quot;text&quot; class=&quot;edit_input&quot; /&amp;gt;
                        &amp;lt;button class=&quot;button btn_edit&quot;&amp;gt;EDIT&amp;lt;/button&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;p class=&quot;text&quot;&amp;gt;
                        Hello world!
                    &amp;lt;/p&amp;gt;
                    &amp;lt;button class=&quot;button btn_amend&quot;&amp;gt;Amend&amp;lt;/button&amp;gt;
                    &amp;lt;button class=&quot;button btn_delete&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;
                &amp;lt;/li&amp;gt; --&amp;gt;
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 상태를 관리한 배열을 하나 생성합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 필요한 변수를 선언합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646972171919&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let listArray = []; // 할일이 담길 배열
const todoList = document.querySelector('.todo_list'); // 배열의 목록을 뿌려줄 부모 
const todoInput = document.querySelector('.todo_input'); // 할일의 내용을 입력하는 input
const btnAdd = document.querySelector('.btn_add'); // 입력 완료후 추가를 하기위한 버튼&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 함수 두 개를 만들어줄 텐데 하나는 실제 추가 버튼을 눌렀을때 추가가 이루어지는 함수이고, 다른 하나는 배열에 무언가가 추가되었을때 html에 뿌려주는 함수입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646972296422&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// add list
const addTodo = () =&amp;gt; {
	// 할일을 listArray 배열에 추가합니다.
}


// show list
const showList = () =&amp;gt; {
	// html에 뿌려줍니다.
}

btnAdd.addEventListener('click', addTodo);&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;addTodo 에서 만약 input의 value 값이 비어있다면 alert을 띄워준 후 input으로 다시 focus해줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 다음동작이 하지 않기 위해서 return false를 해줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 비어있지 않다면, listArray에 해당 value값을 push하여 추가해 준 뒤,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;input의 value값을 빈값으로 초기화 해줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선을 화면을 그리는 함수는 아직 정의가 안되었으므로, console로 배열을 찍어서 확인해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1646972660750&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const addTodo = () =&amp;gt; {
    const inputValue = todoInput.value; // input의 value값을 가져옵니다
    if(inputValue === &quot;&quot;) { // input 의 value 값이 비어있다면
        alert(&quot;값을 입력하세요!&quot;); // alert을 띄우고
        todoInput.focus(); // input에 다시 focus를 시켜줍니다.
        return false; // 뒤의 동작을 멈춥니다. 
    }
    
    // 만약 value값이 있다면 
    listArray.push(inputValue);  // listArray에 value값을 추가해 줍니다. 
    // 그리고 input의 값을 비워줍니다. 만약 해주지 않으면 전에 입력한 값이 그대로 남아있게 됩니다.
    todoInput.value = &quot;&quot;; 
    console.log(listArray) // 잘 추가가 되었는지 확인해봅니다!
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rYxza/btrvE5kffe3/kj8wnT0gVsRoGkKnswMkSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rYxza/btrvE5kffe3/kj8wnT0gVsRoGkKnswMkSK/img.png&quot; data-alt=&quot;결과화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rYxza/btrvE5kffe3/kj8wnT0gVsRoGkKnswMkSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrYxza%2FbtrvE5kffe3%2Fkj8wnT0gVsRoGkKnswMkSK%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;672&quot; height=&quot;308&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과화면&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아주 잘 되네요. 그러면 실제로 html에 그려줍니다.&amp;nbsp; 전에 주석해놓았던 todo_item 을 그래도 그려줄 건데, 텍스트 대신 데이터를 연결해 줄 것입니다. listArray의 length값을 체크하여 0 인경우는 데이터가 아직 할일 목록이 아무것도 없다는 뜻이니 &quot;데이터가 없습니다&quot;를 출력해 주고, 1개 이상인 경우 데이터를 그려줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646972966639&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// show list
const showList = () =&amp;gt; {
    if(listArray.length &amp;gt; 0) { // 데이터가 0개 이상인 경우 즉, 데이터가 있는 경우
        todoList.innerHTML = &quot;&quot;; // 우선 기존 데이터를 모두 삭제하여 초기화 시켜줍니다. 
        listArray.forEach(function(text, idx){ // 그리고 forEach를 통하여 배열의 개수만큼 그립니다.
            const itemTag = '&amp;lt;li class=&quot;todo_item &quot;&amp;gt;' + 
                                '&amp;lt;div class=&quot;edit_wrap&quot;&amp;gt;' + 
                                    '&amp;lt;input type=&quot;text&quot; class=&quot;edit_input&quot; value=&quot;'+text+'&quot; /&amp;gt;' +
                                    '&amp;lt;button class=&quot;button btn_edit&quot; onClick=&quot;amendTodo('+idx+')&quot;&amp;gt;EDIT&amp;lt;/button&amp;gt;' +
                                '&amp;lt;/div&amp;gt;' +
                                '&amp;lt;p class=&quot;text&quot;&amp;gt;'+ text +'&amp;lt;/p&amp;gt;' +
                                '&amp;lt;button class=&quot;button btn_amend&quot; onClick=&quot;activeEdit('+idx+')&quot;&amp;gt;Amend&amp;lt;/button&amp;gt;' +
                                '&amp;lt;button class=&quot;button btn_delete&quot; onClick=&quot;deleteTodo('+idx+')&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;' +
                            '&amp;lt;/li&amp;gt;'
            todoList.innerHTML += itemTag // innerHTML로 하나씩 추가하여 그립니다. 
        });
    } else { // 데이터가 없는 경우 
        todoList.innerHTML = &quot;&amp;lt;li&amp;gt;데이터가 없습니다&amp;lt;/li&amp;gt;&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 한 뒤 추가할때에도 console 대신 showList 함수를 불러오면 됩니다.&amp;nbsp;&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;* 최종소스&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646973022477&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let listArray = [];
const todoList = document.querySelector('.todo_list');
const todoInput = document.querySelector('.todo_input');
const btnAdd = document.querySelector('.btn_add');



// add list
const addTodo = () =&amp;gt; {
    const inputValue = todoInput.value;
    if(inputValue === &quot;&quot;) {
        alert(&quot;값을 입력하세요!&quot;);
        todoInput.focus();
        return false;
    }
    listArray.push(inputValue);
    todoInput.value = &quot;&quot;;
    showList()
}


// show list
const showList = () =&amp;gt; {
    if(listArray.length &amp;gt; 0) {
        todoList.innerHTML = &quot;&quot;;
        listArray.forEach(function(text, idx){
            const itemTag = '&amp;lt;li class=&quot;todo_item &quot;&amp;gt;' + 
                                '&amp;lt;div class=&quot;edit_wrap&quot;&amp;gt;' + 
                                    '&amp;lt;input type=&quot;text&quot; class=&quot;edit_input&quot; value=&quot;'+text+'&quot; /&amp;gt;' +
                                    '&amp;lt;button class=&quot;button btn_edit&quot; onClick=&quot;amendTodo('+idx+')&quot;&amp;gt;EDIT&amp;lt;/button&amp;gt;' +
                                '&amp;lt;/div&amp;gt;' +
                                '&amp;lt;p class=&quot;text&quot;&amp;gt;'+ text +'&amp;lt;/p&amp;gt;' +
                                '&amp;lt;button class=&quot;button btn_amend&quot; onClick=&quot;activeEdit('+idx+')&quot;&amp;gt;Amend&amp;lt;/button&amp;gt;' +
                                '&amp;lt;button class=&quot;button btn_delete&quot; onClick=&quot;deleteTodo('+idx+')&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;' +
                            '&amp;lt;/li&amp;gt;'
            todoList.innerHTML += itemTag
        });
    } else {
        todoList.innerHTML = &quot;&amp;lt;li&amp;gt;데이터가 없습니다&amp;lt;/li&amp;gt;&quot;;
    }
}


btnAdd.addEventListener('click', addTodo);
showList()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 글 삭제하기&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;기능목록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;-&amp;nbsp;&quot;Delete&quot;&amp;nbsp;버튼&amp;nbsp;클릭&amp;nbsp;시&amp;nbsp;해당&amp;nbsp;아이템&amp;nbsp;삭제 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;&quot;CLEAR&quot;&amp;nbsp;버튼&amp;nbsp;클릭&amp;nbsp;시&amp;nbsp;모든&amp;nbsp;아이템&amp;nbsp;삭제&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;▶ &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;소스보기&lt;/span&gt;&lt;/span&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans 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 Sans Demilight', 'Noto Sans KR';&quot;&gt;클릭했을때 삭제하는 기능, 클릭 했을때 전부 삭제하는 기능 2가지만 추가해 주면됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans 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 Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 글을 찾기위해서 id가 필요한데, 여기서는 간단하게 index값을 사용하였습니다.&amp;nbsp;&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;한 개의 글만 잘라내기 위해 splice 함수를 사용하였습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;splice(num1, num2) 은 num1 번째 (0번부터 시작해서 세어야 합니다) 부터 num2 만큼 잘라내겠다는 뜻입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646973726824&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var list = [1, 2, 3, 4, 5]
console.log(list.splice(2, 2)) // 잘려나간 값 [3, 4]
console.log(list) // splice 이후 list 의 값 [1, 2, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;* 최종소스&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646973587163&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{...}
const btnClear = document.querySelector('.btn_clear'); // 초기화 버튼을 가져옵니다.

{ ... }

// delete todo - 하나만 삭제
const deleteTodo = (idx) =&amp;gt; {
    listArray.splice(idx, 1); // 배열중 idx 번째 1개를 잘라 내겠다는 뜻입니다. 
    showList() // 그리고 바뀐 값이 다시 그려질 수 있도록 showList를 호출 합니다.
}

// clear All - 모두 삭제
const clearAll = () =&amp;gt; {
    listArray = [];  // 배열을 초기화 시킨 뒤
    showList(); // 바뀐 값이 다시 그려질 수 있도록 showList를 호출 합니다.
}


// show list
const showList = () =&amp;gt; {
	// btn_delete 버튼 부분에 onClick 함수를 추가합니다. idx를 파라미터로 넘겨줍니다.
	{...}
         '&amp;lt;button class=&quot;button btn_delete&quot; onClick=&quot;deleteTodo('+idx+')&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;' +
	{...}
        });
    } else {
        todoList.innerHTML = &quot;&amp;lt;li&amp;gt;데이터가 없습니다&amp;lt;/li&amp;gt;&quot;;
    }
}


btnAdd.addEventListener('click', addTodo);
btnClear.addEventListener('click', clearAll); 

showList()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. 글 수정하기&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;기능목록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-&amp;nbsp;&quot;Amend&quot;&amp;nbsp;버튼&amp;nbsp;클릭&amp;nbsp;시&amp;nbsp;edit&amp;nbsp;화면&amp;nbsp;출력&amp;nbsp;및&amp;nbsp;edit&amp;nbsp;input에&amp;nbsp;기존&amp;nbsp;값&amp;nbsp;출력 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;-&amp;nbsp;edit&amp;nbsp;input이&amp;nbsp;빈값인&amp;nbsp;경우&amp;nbsp;&quot;edit&quot;버튼&amp;nbsp;클릭시&amp;nbsp;&quot;값을&amp;nbsp;입력하세요!&quot;&amp;nbsp;alert을&amp;nbsp;띄운후&amp;nbsp;input으로&amp;nbsp;focus&lt;/span&gt;&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edit.gif&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWfYwR/btrvAVhIz1j/Zwrvl6kC513kKTdMH3vVKK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWfYwR/btrvAVhIz1j/Zwrvl6kC513kKTdMH3vVKK/img.gif&quot; data-alt=&quot;참고 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWfYwR/btrvAVhIz1j/Zwrvl6kC513kKTdMH3vVKK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bWfYwR/btrvAVhIz1j/Zwrvl6kC513kKTdMH3vVKK/img.gif&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;502&quot; height=&quot;203&quot; data-filename=&quot;edit.gif&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;203&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;&lt;span&gt;▶&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;소스보기&lt;/span&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;span&gt;showList 안에 그려주는 태그 안에 btn_amend에 activeEidt함수를 추가하고 idx값을 파라미터로 넘깁니다.&amp;nbsp;&lt;/span&gt;&lt;span&gt;그리고 activeEdit 함수에서는 단순하게 todo_item에 edit_active 클래스를 추가함으로써 편집 화면을 활성화 해줍니다.&amp;nbsp;&lt;/span&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;span&gt;실제 수정이 저장 되는 역활을 수행 하는 것은 btn_edit 입니다. 여기에 amendTodo함수를 추가하고 idx를 파라미터로 넘깁니다. 그리고 마찬가지로 splice를 통하여 값을 수정 합니다. &lt;/span&gt;&lt;span&gt;이번에는 3개의 인자를 받는데 마찬가지로 (num1, num2, num3) 이 있을때&amp;nbsp; num1번째의 값 num2만큼 삭제하고, num3을 추가하겠다는 뜻입니다. &lt;/span&gt;&lt;span style=&quot;caret-color: auto; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;
&lt;pre id=&quot;code_1646974607760&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var list = [1, 2, 3, 4, 5]
console.log(list.splice(1, 2, 3)) // 1번째 두개의 값을 삭제하고 3을 넣겠다는 뜻입니다.
console.log(list)

// 결과
[2, 3]
[1, 3, 4, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;그리고 마찬가지로 편집화면의 input인 edit_input에도 빈값 처리를 해주면 됩니다.&amp;nbsp;&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 Sans Demilight', 'Noto Sans KR';&quot;&gt;* 최종소스&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646974767022&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{...}

// edit_wrap active : 편집 팝업 활성화 
const activeEdit = (idx) =&amp;gt; {
    const items = document.querySelectorAll('.todo_item');
    const item = items[idx];
    item.classList.add('edit_active');
}

// 실제 수정 하는 함수 
const amendTodo = (idx) =&amp;gt; {
    const items = document.querySelectorAll('.todo_item');
    const item = items[idx];
    const newInput = item.querySelector('.edit_input');
    const newValue = newInput.value;

    if(newValue === &quot;&quot;) {  // 빈값인 경우 체크!
        alert(&quot;값을 입력하세요&quot;);
        newInput.focus();
    } else {
        listArray.splice(idx, 1, newValue); // idx번째 1개의 값을 newValue로 교체
        item.classList.remove('edit_active'); // 그리고 자동으로 edit 화면을 종료합니다.
        showList(); // 다시 그려주면 수정된 내역이 반영됩니다!
    }
}


// show list
const showList = () =&amp;gt; {
    if(listArray.length &amp;gt; 0) {
        todoList.innerHTML = &quot;&quot;;
        listArray.forEach(function(text, idx){
            const itemTag = '&amp;lt;li class=&quot;todo_item &quot;&amp;gt;' + 
                                '&amp;lt;div class=&quot;edit_wrap&quot;&amp;gt;' + 
                                    '&amp;lt;input type=&quot;text&quot; class=&quot;edit_input&quot; value=&quot;'+text+'&quot; /&amp;gt;' +
                                    '&amp;lt;button class=&quot;button btn_edit&quot; onClick=&quot;amendTodo('+idx+')&quot;&amp;gt;EDIT&amp;lt;/button&amp;gt;' +
                                '&amp;lt;/div&amp;gt;' +
                                '&amp;lt;p class=&quot;text&quot;&amp;gt;'+ text +'&amp;lt;/p&amp;gt;' +
                                '&amp;lt;button class=&quot;button btn_amend&quot; onClick=&quot;activeEdit('+idx+')&quot;&amp;gt;Amend&amp;lt;/button&amp;gt;' +
                                '&amp;lt;button class=&quot;button btn_delete&quot; onClick=&quot;deleteTodo('+idx+')&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;' +
                            '&amp;lt;/li&amp;gt;'
            todoList.innerHTML += itemTag
        });
    } else {
        todoList.innerHTML = &quot;&amp;lt;li&amp;gt;데이터가 없습니다&amp;lt;/li&amp;gt;&quot;;
    }
}


btnAdd.addEventListener('click', addTodo);
btnClear.addEventListener('click', clearAll);

showList()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 하면 배열로 데이터를 관리할 수 있는 Vanila JavaScript 로 만든 소스가 완성 되었습니다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/9njfevwk/2/embedded/js,html,css,result/dark/&quot; width=&quot;100%&quot; height=&quot;600&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&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;</description>
      <category>개발  /JavaScript</category>
      <category>array javascript</category>
      <category>array.splice</category>
      <category>javascript beginner</category>
      <category>javascript tutorial</category>
      <category>todo List</category>
      <category>todo-list javascript</category>
      <category>todolist array</category>
      <category>todolist javascript</category>
      <category>vanila javascript</category>
      <category>자바스크립트 입문</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/63</guid>
      <comments>https://wazacs.tistory.com/63#entry63comment</comments>
      <pubDate>Fri, 11 Mar 2022 20:15:36 +0900</pubDate>
    </item>
    <item>
      <title>메타마스크 테스트 네트워크 활성화 + 테스트 이더리움 받기 (Ropsten)</title>
      <link>https://wazacs.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;처음 메타마스크를 설치하면 &quot;이더리움 메인넷&quot; 이외에 다른 테스트넷이 나타나지 않습니다.&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;설정 &amp;gt; 고급 &amp;gt; Show test networks 를 활성해 해주면 됩니다.&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-origin-width=&quot;356&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzNZEE/btrs8BkPQgu/zQY1ytWBkNTBYbWiP9YR2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzNZEE/btrs8BkPQgu/zQY1ytWBkNTBYbWiP9YR2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzNZEE/btrs8BkPQgu/zQY1ytWBkNTBYbWiP9YR2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzNZEE%2Fbtrs8BkPQgu%2FzQY1ytWBkNTBYbWiP9YR2k%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;356&quot; height=&quot;588&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WNuVc/btrs67rqbSU/kDnqKuysMuhZyITacmkkS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WNuVc/btrs67rqbSU/kDnqKuysMuhZyITacmkkS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WNuVc/btrs67rqbSU/kDnqKuysMuhZyITacmkkS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWNuVc%2Fbtrs67rqbSU%2FkDnqKuysMuhZyITacmkkS1%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;352&quot; height=&quot;600&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ptcxL/btrs9LmTROZ/FMU2RTMLIxXgsyAUsvahi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ptcxL/btrs9LmTROZ/FMU2RTMLIxXgsyAUsvahi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ptcxL/btrs9LmTROZ/FMU2RTMLIxXgsyAUsvahi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FptcxL%2Fbtrs9LmTROZ%2FFMU2RTMLIxXgsyAUsvahi1%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;340&quot; height=&quot;683&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Blockchain</category>
      <category>metamask ropsten network</category>
      <category>metamask test network</category>
      <category>메타마스크 네트워크</category>
      <category>메타마스크 테스트 네트워크</category>
      <category>메타마스크 테스트 네트워크 활성화</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/61</guid>
      <comments>https://wazacs.tistory.com/61#entry61comment</comments>
      <pubDate>Sat, 12 Feb 2022 18:34:20 +0900</pubDate>
    </item>
    <item>
      <title>Microsoft Azure 간단 세팅</title>
      <link>https://wazacs.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 강의를 듣던중 azure 는 처음 써봐서 간단하게 세팅하는법을 정리해 보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(클라우드상에서 서버 세팅은 처음이라 혹시 잘못된 점 있으면 지적좀요.. )&amp;nbsp;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github 계정으로도 생성이 되서 계정가입하는것도 쉽지만, 역시나 신용카드 등록 절차를 진행해야하며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12개월 동안 별도 비용없이 제공되는 서비스와 200달러 크레딧을 사용할 수 있다고 하네요.&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;a href=&quot;https://azure.microsoft.com/ko-kr/free/search/?&amp;amp;ef_id=CjwKCAiA0KmPBhBqEiwAJqKK468_sCQDEX6B5XsYo9hA_xTLlWmU1Gt6Ml4u7bL4HKT-A_9iGdf8JBoCdGEQAvD_BwE:G:s&amp;amp;OCID=AID2200210_SEM_CjwKCAiA0KmPBhBqEiwAJqKK468_sCQDEX6B5XsYo9hA_xTLlWmU1Gt6Ml4u7bL4HKT-A_9iGdf8JBoCdGEQAvD_BwE:G:s&amp;amp;gclid=CjwKCAiA0KmPBhBqEiwAJqKK468_sCQDEX6B5XsYo9hA_xTLlWmU1Gt6Ml4u7bL4HKT-A_9iGdf8JBoCdGEQAvD_BwE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;서하면 되고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정생성 후 vm생성은 &lt;a href=&quot;https://portal.azure.com/#home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&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;우선 가상머신을 추가하기 위해 시작을 누르고 원하는 소프트웨어 검색 후 클릭합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 강의를 보면서 진행했던거라서 Window 10으로 진행했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Z905/btrrpVegZL1/AJ3ScervWIzlrciUHFUUSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Z905/btrrpVegZL1/AJ3ScervWIzlrciUHFUUSK/img.png&quot; data-alt=&quot;azure vm 생성하기01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Z905/btrrpVegZL1/AJ3ScervWIzlrciUHFUUSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Z905%2FbtrrpVegZL1%2FAJ3ScervWIzlrciUHFUUSK%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;1121&quot; height=&quot;818&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;azure vm 생성하기01&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dj5pGh/btrrmfE049g/xnz9PYOz3kKvJy64Kw8nD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dj5pGh/btrrmfE049g/xnz9PYOz3kKvJy64Kw8nD1/img.png&quot; data-alt=&quot;azure vm 생성하기02&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dj5pGh/btrrmfE049g/xnz9PYOz3kKvJy64Kw8nD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdj5pGh%2FbtrrmfE049g%2Fxnz9PYOz3kKvJy64Kw8nD1%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;1067&quot; height=&quot;632&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;azure vm 생성하기02&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;리소스 그룹 이름&lt;/b&gt;에서 새로만들기를 클릭한 후 원하는 이름을 적어주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가상머신이름, 사용자이름, 암호, 인바운드포트&lt;/b&gt; 설정까지 해준뒤&amp;nbsp;&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;&quot;검토 + 만들기&quot; 버튼을 클릭해 주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSS5t0/btrrotJKnrQ/65fke5T3H0HCsSvWfVA5D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSS5t0/btrrotJKnrQ/65fke5T3H0HCsSvWfVA5D0/img.png&quot; data-alt=&quot;azure vm 생성하기03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSS5t0/btrrotJKnrQ/65fke5T3H0HCsSvWfVA5D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSS5t0%2FbtrrotJKnrQ%2F65fke5T3H0HCsSvWfVA5D0%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;1153&quot; height=&quot;1238&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;azure vm 생성하기03&lt;/figcaption&gt;
&lt;/figure&gt;
&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;/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;알림으로 남은 크레딧도 체크해 주네요.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;azure시작하기05-생성완료.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kD8bI/btrroFb67F7/wy8Z47ZtlA6HRpkHhyxV3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kD8bI/btrroFb67F7/wy8Z47ZtlA6HRpkHhyxV3k/img.png&quot; data-alt=&quot;azure vm 생성하기04&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kD8bI/btrroFb67F7/wy8Z47ZtlA6HRpkHhyxV3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkD8bI%2FbtrroFb67F7%2Fwy8Z47ZtlA6HRpkHhyxV3k%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;1038&quot; height=&quot;493&quot; data-filename=&quot;azure시작하기05-생성완료.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;azure vm 생성하기04&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;RDP로 연결을 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2_start.png&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9ywOH/btrroFb7sBq/w1sLCSGcLW4OISXMBgyyVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9ywOH/btrroFb7sBq/w1sLCSGcLW4OISXMBgyyVk/img.png&quot; data-alt=&quot;connect&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9ywOH/btrroFb7sBq/w1sLCSGcLW4OISXMBgyyVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9ywOH%2FbtrroFb7sBq%2Fw1sLCSGcLW4OISXMBgyyVk%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;543&quot; height=&quot;285&quot; data-filename=&quot;2_start.png&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;connect&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 RDP 파일을 다운받는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3_start.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bODgmv/btrrn26LH3p/R0ygFOIvykgw6rGzkxPTy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bODgmv/btrrn26LH3p/R0ygFOIvykgw6rGzkxPTy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bODgmv/btrrn26LH3p/R0ygFOIvykgw6rGzkxPTy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbODgmv%2Fbtrrn26LH3p%2FR0ygFOIvykgw6rGzkxPTy1%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;946&quot; height=&quot;570&quot; data-filename=&quot;3_start.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;그러면 우리가 만든 가상머신이 실행된다! : )&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날에 VMware 설치해서 OS 이미지 파일 구해서 설치 하고 난리(?) 쳤던거에 비하면 정말 간편한것같다.  &lt;/p&gt;</description>
      <category>개발  /ETC</category>
      <category>Azure</category>
      <category>azure vm 생성</category>
      <category>azure 무료체험</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/57</guid>
      <comments>https://wazacs.tistory.com/57#entry57comment</comments>
      <pubDate>Sat, 22 Jan 2022 12:19:17 +0900</pubDate>
    </item>
    <item>
      <title>[React, css] textarea 사용시 여러가지 처리</title>
      <link>https://wazacs.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 입력폼을 다룰때 input말고도 textarea도 상당히 많이 다루는 편인데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textarea도 input과 별개로 은근히 많은 커스텀을 해줘야 합니다.&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;기본 textarea 같은 경우 resize 옵션이 기본으로 들어가 있고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input과 달리 textarea 는 여러 줄의 글을 길게 입력하는데 사용되기 때문에,&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. textarea resize 사용하지 않기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. textarea placeholder 에서 줄바꿈 처리하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. textarea 에서 tab 사용하기&amp;nbsp;&lt;/b&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 기본 소스&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JSX&lt;/p&gt;
&lt;pre id=&quot;code_1640515799227&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react'
import './app.css'

function App() {

  const [textValue, setTextValue] = useState(&quot;&quot;);
  const handleSetValue = (e) =&amp;gt; {
    setTextValue(e.target.value);
  };

  return (
    &amp;lt;div className=&quot;app&quot;&amp;gt;
      &amp;lt;textarea
        placeholder=&quot;여기에 입력하세요&quot;
        value={textValue}
        onChange={(e) =&amp;gt; handleSetValue(e)}
      &amp;gt;&amp;lt;/textarea&amp;gt;
      &amp;lt;hr /&amp;gt;
      &amp;lt;p className=&quot;paragraph&quot;&amp;gt;{textValue}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CSS&lt;/p&gt;
&lt;pre id=&quot;code_1640515814625&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.app {
    padding: 10px
}

textarea {
    width: 250px;
    height: 200px;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/seRvy/btroSWOqHPM/OgKLDhv4SVEjE3c3lPfPb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/seRvy/btroSWOqHPM/OgKLDhv4SVEjE3c3lPfPb0/img.png&quot; data-alt=&quot;placeholder, 입력 값, 출력 값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/seRvy/btroSWOqHPM/OgKLDhv4SVEjE3c3lPfPb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FseRvy%2FbtroSWOqHPM%2FOgKLDhv4SVEjE3c3lPfPb0%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;1096&quot; height=&quot;504&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;placeholder, 입력 값, 출력 값&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. textarea resize 사용하지 않기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 마우스 드래그로 textarea의 사이즈를 자유롭게 조절 할 수 있는 옵션이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 css 로 간단하게 처리할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640516245287&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;textarea { resize: none; }&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;3. textarea placeholder 에서 줄바꿈 처리하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;placehoder 에서 여러 줄로 표시를 하고 싶다면 특수문자를 표현하는 코드를 추가해주면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 차이는 모르겠으나, &lt;b&gt;&amp;amp;#13;&lt;/b&gt; 혹은 &lt;b&gt;&amp;amp;#10;&lt;/b&gt; 또는 두개를 같이 입력해줘도 먹힙니다. &amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640516650683&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &amp;lt;textarea placeholder=&quot;여기에&amp;amp;#13;&amp;amp;#10;입력하세요&quot;&amp;gt;&amp;lt;/textarea&amp;gt;&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;4. textarea 에서 tab 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textarea에서 tab을 입력하게 되면 일반적으로 다음 Element로 focus 가 넘어가 버리기때문에 tab입력이 불가합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현하기 위해 keyCode값을 받아 9(tab의 keyCode값)인 경우 기본 기능인 다음 Element에 포커스 이동을 방지처리를 해준 뒤, select가 된 시작점과 끝점을 구해 그 사이에 &quot;\t&quot; 값을 넣어주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\t를 입력하는것이 tab을 입력하는 것과 같은 처리를 해준다고 보면 됩니다.&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;color: #ee2323;&quot;&gt;&lt;b&gt;※ 참고로 keyCode값은 onChange로 받지 않고 onKeyDown으로 받습니다.&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;b&gt;1. 포커스 기능 방지&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640516937200&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; e.preventDefault();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. selection 기준으로 시작과 끝 구하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640516977609&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let start = e.target.selectionStart;
let end = e.target.selectionEnd;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. \t 삽입하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640517051309&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;value = val.substring(0, start) + &quot;\t&quot; + val.substring(end);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는&amp;nbsp; React에서 구현되었지만, javascript나 jquery로 구현할때도 똑같은 원리입니다.&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;b&gt;4. 출력시 \t, \n 처리하기&lt;/b&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;이는 일일이 split을 하여 코드를 삽입하는 경우도 있지만 css를 통해 쉽게 처리할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640517253178&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;white-space: pre-wrap;&lt;/code&gt;&lt;/pre&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-origin-width=&quot;627&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Gnk9/btroStZYhvZ/RpK8BoNIrABVNRxd9HK9FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Gnk9/btroStZYhvZ/RpK8BoNIrABVNRxd9HK9FK/img.png&quot; data-alt=&quot;결과화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Gnk9/btroStZYhvZ/RpK8BoNIrABVNRxd9HK9FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Gnk9%2FbtroStZYhvZ%2FRpK8BoNIrABVNRxd9HK9FK%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;576&quot; height=&quot;601&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;654&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/react-in-textarea-psgks?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;650px&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react textarea</category>
      <category>textarea enter</category>
      <category>textarea resize</category>
      <category>textarea tab</category>
      <category>textarea \n</category>
      <category>textarea \t</category>
      <category>textarea \t \n</category>
      <category>textarea 에서 탭</category>
      <category>textarea 엔터</category>
      <category>textarea 출력</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/52</guid>
      <comments>https://wazacs.tistory.com/52#entry52comment</comments>
      <pubDate>Sun, 26 Dec 2021 20:17:50 +0900</pubDate>
    </item>
    <item>
      <title>[React Error] Invalid options object. React Refresh Plugin has been initialized using an options object that does not match the API schema.</title>
      <link>https://wazacs.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 아무것도 안하고 npx로 리액트 설치 후 npm start를 하면 이런 에러가 난다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639293365372&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Would you like to run the app on another port instead? ... yes
Invalid options object. React Refresh Plugin has been initialized using an options object that does not match the API schema.

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! memory-game@0.1.0 start: `react-scripts start`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the memory-game@0.1.0 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\userName\AppData\Roaming\npm-cache\_logs\2021-12-09T06_17_48_232Z-debug.log
PS D:\personal\!_study\netninja_memoryGame\memory-game&amp;gt;&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;바보같고 어이없게도 경로에 !(exclamation) 가 들어가 있으면 에러가 나는데&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;
&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;b&gt;아무튼 그래서 해결책은 경로에 !를 삭제하면 된다!&lt;/b&gt;  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>Invalid options object</category>
      <category>npm error</category>
      <category>npm start error</category>
      <category>react error</category>
      <category>리액트에러</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/51</guid>
      <comments>https://wazacs.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 12 Dec 2021 16:16:53 +0900</pubDate>
    </item>
    <item>
      <title>나 자신이외에 클릭 하면 .. ! (feat. 셀렉트 박스 커스텀 연장선)</title>
      <link>https://wazacs.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 포스팅되었던 글 중&amp;nbsp; &lt;a href=&quot;https://wazacs.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;select 태그 커스텀 하기, 혹은 직접 만들기 (feat. javascript)&lt;/b&gt;&lt;/a&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;꼭 SelectBox에만 사용되는게 아니니 알아두면 좋을 것 같습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  참고로 여기서 지칭하는 selectbox는 html 태그가 아니라 div로 커스터마이징 된 selectbox를 의미합니다.&amp;nbsp;&lt;/b&gt;&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;p data-ke-size=&quot;size16&quot;&gt;원리는 window 자체에 addeventListener를 등록하고 클릭된 event 값을 받아서 비교를 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 클릭된 event.target이 Select 자신인지 아닌지 비교한 뒤 본인이 아니면 Select를 닫아주면 됩니다.&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;size18&quot;&gt;&lt;b&gt;1. window 에 addeventListener 걸기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634870468129&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.addEventListener('click', e =&amp;gt; 비교해서닫을함수(e))&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;size18&quot;&gt;&lt;b&gt;2. 비교해서 Select를 닫아주는 함수 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.target 에서 class값을 가져와 label이나 optionItem 을 클릭하는경우는 닫히면 안되니까 이 두가지가 아닌 경우에만 무조건 Select를 닫아주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1634870515299&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleClose = e =&amp;gt; {
    // select 가 아닌경우 닫기 
    if(!e.target.classList.contains('label') &amp;amp;&amp;amp; !e.target.classList.contains('optionItem')) {
        label.forEach(function(lb){
            lb.parentNode.classList.remove('active')
        })
    } 
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PLUS. 멀티 selectbox에서 나 자신이 아닌 경우에 닫기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 selectbox가 여러개인경우의 소스 코드도 추가됬었는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 selecbox를 열어놓은 상태로 다른 selectbox를 클릭했을때 다른 열려 있는 selectbox를 닫아주는 코드를 더 추가해보았습니다.&amp;nbsp;label을 forEach로 돌려 클릭된 label이 아니면 부모를 찾아 active 클래스를 제거합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1634870855959&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const clickLabel = (lb, optionItems) =&amp;gt; {

    // ****************** 추가된 부분 START
    // 내가 아닌 다른 select 닫기 
    label.forEach(function(itemLb){
        if(lb !== itemLb) {
            itemLb.parentNode.classList.remove('active')
        }
    })
    // ****************** 추가된 부분 END

    if(lb.parentNode.classList.contains('active')) {
        lb.parentNode.classList.remove('active');
        optionItems.forEach((opt) =&amp;gt; {
            opt.removeEventListener('click', () =&amp;gt; {
                handleSelect(lb, opt)
            })
        })
    } else {
        lb.parentNode.classList.add('active');
        optionItems.forEach((opt) =&amp;gt; {
            opt.addEventListener('click', () =&amp;gt; {
                handleSelect(lb, opt)
            })
        })
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종코드&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/xdvz4kco/embedded/js,html,css,result/dark/&quot; width=&quot;100%&quot; height=&quot;350&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /JavaScript</category>
      <category>javascript 자신외에 닫기</category>
      <category>selectbox custom</category>
      <category>selectbox multi</category>
      <category>selectbox 커스텀</category>
      <category>셀렉트박스 커스텀</category>
      <category>자신외에 닫기</category>
      <category>특정 태그 외에</category>
      <category>특정 태그 외에 클릭시</category>
      <category>특정 태그외 클릭</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/50</guid>
      <comments>https://wazacs.tistory.com/50#entry50comment</comments>
      <pubDate>Fri, 22 Oct 2021 13:10:01 +0900</pubDate>
    </item>
    <item>
      <title>props로 전달되는 텍스트 라인 줄바꿈 처리는 2가지 방법 !</title>
      <link>https://wazacs.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 props로 전달받은 텍스트의 줄 바꿈을 해야 하는 경우가 종종 있습니다.&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;먼저 텍스트를 입력받아 뿌려주는 초 간단 컴포넌트를 만들어 보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629977972199&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Item = ({ text }) =&amp;gt; {
  return &amp;lt;p&amp;gt;{text}&amp;lt;/p&amp;gt;;
};

export default function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Item text={&quot;안뇽하세요&quot;} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 text에 줄바꿈이 들어간 텍스트를 입력하려고 할 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 &lt;b&gt;br태그&lt;/b&gt;를 사용하거나 '&lt;b&gt;\n&lt;/b&gt;'를 사용하여 줄바꿈을 시도하실 수 있을 것 같습니다.&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-origin-width=&quot;947&quot; data-origin-height=&quot;321&quot; data-filename=&quot;1111.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osl5B/btrdkO7bgvQ/xKJaSAtYuTfODlKmKR4cy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osl5B/btrdkO7bgvQ/xKJaSAtYuTfODlKmKR4cy0/img.png&quot; data-alt=&quot;결과 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osl5B/btrdkO7bgvQ/xKJaSAtYuTfODlKmKR4cy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fosl5B%2FbtrdkO7bgvQ%2FxKJaSAtYuTfODlKmKR4cy0%2Fimg.png&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;321&quot; data-filename=&quot;1111.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;왜 이런 문제가 생기는 걸까요?  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이리저리 테스트 해본 결과 value의 값이 String과 섞이게 되면 String 타입으로 반환되는 것처럼 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마치 javascript에서 123(숫자) + 'hello'(문자열) = 123hello(문자열) 이 되어버리는 것처럼 말이에요.&lt;/b&gt; 결국에는 React도 javascript니까요! (개인적인 의견입니다)&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;참고로 prop에 태그만 보낼 경우에는 잘 동작됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;325&quot; data-filename=&quot;333.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cteHcU/btrdesY9KDE/7sFt71ZWHrHHYlNFI6CUo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cteHcU/btrdesY9KDE/7sFt71ZWHrHHYlNFI6CUo1/img.png&quot; data-alt=&quot;결과 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cteHcU/btrdesY9KDE/7sFt71ZWHrHHYlNFI6CUo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcteHcU%2FbtrdesY9KDE%2F7sFt71ZWHrHHYlNFI6CUo1%2Fimg.png&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;325&quot; data-filename=&quot;333.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. replace로 문자 치환 하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\n이나 &amp;lt;br/&amp;gt;과 같은 문자를 태그로 치환해 주는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받는 자식 요소에서 props를 받아 return 할 때 처리해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629978865254&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Item = ({ text }) =&amp;gt; {
  return &amp;lt;p&amp;gt;
    {text.split(&quot;\n&quot;).map((txt) =&amp;gt; (
        &amp;lt;&amp;gt;
          {txt}
          &amp;lt;br /&amp;gt;
        &amp;lt;/&amp;gt;
      ))}
  &amp;lt;/p&amp;gt;;
};

export default function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Item text={&quot;내 이름은 \n 고길동&quot;} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;445&quot; data-filename=&quot;2222.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfWluf/btrdfH9jaSz/Wp1LghgGEoWx9WkkTuJm50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfWluf/btrdfH9jaSz/Wp1LghgGEoWx9WkkTuJm50/img.png&quot; data-alt=&quot;결과 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfWluf/btrdfH9jaSz/Wp1LghgGEoWx9WkkTuJm50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfWluf%2FbtrdfH9jaSz%2FWp1LghgGEoWx9WkkTuJm50%2Fimg.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;445&quot; data-filename=&quot;2222.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 배열 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 값을 넘길 때 문자열을 타입별로 배열에 담아 보내줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629980527448&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Item = ({ text }) =&amp;gt; {
  return text;
};

export default function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Item text={[&quot;안녕&quot;, &amp;lt;br /&amp;gt;, &quot;친구들&quot;]} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;348&quot; data-filename=&quot;444.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baZA8J/btrdgR4TSPm/uqPOqnAyxYgCrXtcUvXzLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baZA8J/btrdgR4TSPm/uqPOqnAyxYgCrXtcUvXzLk/img.png&quot; data-alt=&quot;결과 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baZA8J/btrdgR4TSPm/uqPOqnAyxYgCrXtcUvXzLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaZA8J%2FbtrdgR4TSPm%2FuqPOqnAyxYgCrXtcUvXzLk%2Fimg.png&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;348&quot; data-filename=&quot;444.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;역시 잘 동작합니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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>개발  /ReactJS</category>
      <category>react prop br</category>
      <category>react prop 줄바꿈</category>
      <category>react props br</category>
      <category>react props html</category>
      <category>react props 줄바꿈</category>
      <category>react replace</category>
      <category>react \n</category>
      <category>react 줄바꿈</category>
      <category>리액트 br태그</category>
      <category>리액트 줄바꿈</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/49</guid>
      <comments>https://wazacs.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 26 Aug 2021 21:23:57 +0900</pubDate>
    </item>
    <item>
      <title>폰트를 한글과 영어,숫자 따로 적용하기 (feat. unicode-range)</title>
      <link>https://wazacs.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참조1: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range&lt;/a&gt; &lt;br /&gt;참조2:&amp;nbsp;&lt;a href=&quot;https://www.fileformat.info/info/unicode/char/26/index.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.fileformat.info/info/unicode/char/26/index.htm&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드문자표: &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_2000~2FFF&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C_2000~2FFF&lt;/a&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;한글을 나눔고딕으로 해주시고, 영문,숫자는 OpensSans로 해주세요!&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;아무것도 모르던 시절에는 숫자마다 클래스를 주어 font를 다르게 적용시켰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 정말 쉬운 방법이 있었더리구요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 unicode-range를 지정하는 방법입니다.&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;b&gt;unicode-range&lt;/b&gt;&amp;nbsp;는&amp;nbsp;CSS&amp;nbsp;디스크립터(discriptor)로써,&amp;nbsp; &lt;br /&gt;@font-face로&amp;nbsp;정의된&amp;nbsp;폰트를&amp;nbsp;특정&amp;nbsp;character의&amp;nbsp;범위를&amp;nbsp;지정해줄&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;딱 한 단어에 속하는 유니코드도 지정해 줄 수 있습니다.&amp;nbsp; &lt;br /&gt;예를&amp;nbsp;들어&amp;nbsp;U+26(U+0026)&amp;nbsp;같은&amp;nbsp;경우는&amp;nbsp;특수&amp;nbsp;문자&amp;nbsp;'&amp;amp;'를&amp;nbsp;나타내는데,&amp;nbsp; &lt;br /&gt;이를&amp;nbsp;unicode-range에&amp;nbsp;지정해&amp;nbsp;준다면,&amp;nbsp;&amp;amp;만&amp;nbsp;@font-face로&amp;nbsp;정의된&amp;nbsp;폰트가&amp;nbsp;적용됩니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;범위를&amp;nbsp;지정하고싶다면,&amp;nbsp;U+0025-00FF이런식으로&amp;nbsp;-&amp;nbsp;를&amp;nbsp;통해&amp;nbsp;start-end를&amp;nbsp;지정해&amp;nbsp;줄&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp; &lt;br /&gt;여기서는&amp;nbsp;U+0025&amp;nbsp;부터&amp;nbsp;U+00FF&amp;nbsp;까지를&amp;nbsp;의미하는걸로&amp;nbsp;보아&amp;nbsp;-&amp;nbsp;이후에&amp;nbsp;오는&amp;nbsp;유니코드에는&amp;nbsp;U+를&amp;nbsp;생략&amp;nbsp;하는걸로&amp;nbsp;보입니다.&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;예를들어보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확연한 차이를 위해 한글폰트를 &lt;a href=&quot;https://noonnu.cc/font_page/411&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;조선체&lt;/a&gt;로 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626678513676&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;안녕하세요 나&amp;amp;&amp;amp;&amp;amp;&amp;amp;너&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1626678533528&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@font-face {
    font-family: 'ChosunCentennial';
    src: url(../fonts/ChosunCentennial/ChosunCentennial.eot);
    src: url(../fonts/ChosunCentennial/ChosunCentennial.eot?#iefix) format('embedded-opentype'),
         url(../fonts/ChosunCentennial/ChosunCentennial.woff) format('woff'),
         url(../fonts/ChosunCentennial/ChosunCentennial.ttf) format('truetype');
         unicode-range: U+AC00-D7A3;
        }

@font-face {
    font-family: 'OpenSans';
    src: url(../fonts/OpenSans/OpenSans-Regular.eot);
    src: url(../fonts/OpenSans/OpenSans-Regular.eot?#iefix) format('embedded-opentype'),
         url(../fonts/OpenSans/OpenSans-Regular.woff) format('woff'),
         url(../fonts/OpenSans/OpenSans-Regular.ttf) format('truetype');
         unicode-range: U+26;
}


body {
    font-size: 35px;
    font-family: 'ChosunCentennial', 'OpenSans';
}

* {
    font-family: inherit;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해주면 한글(U+AC00-D7A3)은 ChosunCentennial체가 적용되고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특수기호 &amp;amp;(U+26)만 OpenSans로 적용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;97&quot; data-filename=&quot;font_unicode_range1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQc6vc/btq90ltW3Sq/5sRX4dehixeGTHHDdq5HC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQc6vc/btq90ltW3Sq/5sRX4dehixeGTHHDdq5HC0/img.png&quot; data-alt=&quot;결과화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQc6vc/btq90ltW3Sq/5sRX4dehixeGTHHDdq5HC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQc6vc%2Fbtq90ltW3Sq%2F5sRX4dehixeGTHHDdq5HC0%2Fimg.png&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;97&quot; data-filename=&quot;font_unicode_range1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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;size18&quot;&gt;&lt;b&gt;* 자주 사용할 만한 유니코드 범위&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글&amp;nbsp;범위:&amp;nbsp;&amp;nbsp;U+AC00-D7A3 &lt;br /&gt;영어&amp;nbsp;대문자&amp;nbsp;범위&amp;nbsp;:&amp;nbsp;&amp;nbsp;U+0041-005A &lt;br /&gt;영어&amp;nbsp;소문자&amp;nbsp;범위&amp;nbsp;:&amp;nbsp;&amp;nbsp;&amp;nbsp;U+0061-007A &lt;br /&gt;숫자&amp;nbsp;범위&amp;nbsp;:&amp;nbsp;U+0030-0039 &lt;br /&gt;특수&amp;nbsp;문자:&amp;nbsp;U+0020-002F,&amp;nbsp;U+003A-0040,&amp;nbsp;U+005B-0060,&amp;nbsp;U+007B-007E&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>different font</category>
      <category>font-face</category>
      <category>unicode-range</category>
      <category>폰트 각각</category>
      <category>폰트 다르게</category>
      <category>폰트 숫자만</category>
      <category>폰트 영어만</category>
      <category>폰트 한글만</category>
      <category>한글 숫자 폰트 다르게</category>
      <category>한글 영문 폰트 다르게</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/48</guid>
      <comments>https://wazacs.tistory.com/48#entry48comment</comments>
      <pubDate>Mon, 19 Jul 2021 16:27:30 +0900</pubDate>
    </item>
    <item>
      <title>하나씩 나타나는 퀵 메뉴 만들기 (SCSS &amp;amp; CSS)</title>
      <link>https://wazacs.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클릭했을 때 하위 메뉴가 하나씩 나타나는 사이드 메뉴를 만들어 보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;317&quot; data-filename=&quot;menu_animation02.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lEuk5/btq9lULbopl/XDdQnbFcjZDz8GH1W28Uz1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lEuk5/btq9lULbopl/XDdQnbFcjZDz8GH1W28Uz1/img.gif&quot; data-alt=&quot;결과 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lEuk5/btq9lULbopl/XDdQnbFcjZDz8GH1W28Uz1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lEuk5/btq9lULbopl/XDdQnbFcjZDz8GH1W28Uz1/img.gif&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;317&quot; data-filename=&quot;menu_animation02.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 alignLeft&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1024&quot; data-filename=&quot;1111.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dktN9I/btq9nCDj9Aw/P3mh12XIW65oaGxARBZIj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dktN9I/btq9nCDj9Aw/P3mh12XIW65oaGxARBZIj0/img.jpg&quot; data-alt=&quot;구조 잡기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dktN9I/btq9nCDj9Aw/P3mh12XIW65oaGxARBZIj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdktN9I%2Fbtq9nCDj9Aw%2FP3mh12XIW65oaGxARBZIj0%2Fimg.jpg&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1024&quot; data-filename=&quot;1111.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 메뉴들이 TOP에서 50픽셀 정도 위로 가있고 opacity가 0이라 영역은 있지만 보이지 않는 상태입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 display none을 하고 싶다면 활성화할 때에는 display block 후 top과 opacity의 값을 바꾸도록 해야 모션이 적용됩니다.&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;두 번째는 첫 번째 메뉴에 서브 메뉴들이 겹쳐있는데 하위 메뉴를 감싸고 있는 메뉴의 zIndex가 낮아야 합니다. 그래야 뒤로 가려져 보입니다. 그리고 활성화가 되면 각 메뉴별로 left값을 주면 됩니다.&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;먼저 마크업을 해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626065741029&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;wrap&quot;&amp;gt;
	&amp;lt;div class=&quot;menu&quot;&amp;gt;
		&amp;lt;button class=&quot;menuItem&quot;&amp;gt;Menu&amp;lt;/button&amp;gt;
		&amp;lt;ul class=&quot;submenu&quot;&amp;gt;
			&amp;lt;li class=&quot;subItem&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub1&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
			&amp;lt;li class=&quot;subItem&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub2&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
			&amp;lt;li class=&quot;subItem&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub3&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
			&amp;lt;li class=&quot;subItem&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub4&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
		&amp;lt;/ul&amp;gt;
	&amp;lt;/div&amp;gt;
	
	&amp;lt;hr style=&quot;margin:20px 0; &quot;/&amp;gt;

	&amp;lt;div class=&quot;menu2&quot;&amp;gt;
		&amp;lt;button class=&quot;menuItem&quot;&amp;gt;Menu2&amp;lt;/button&amp;gt;
		&amp;lt;ul class=&quot;submenu2&quot;&amp;gt;
			&amp;lt;li class=&quot;submenu2Item&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub1&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
			&amp;lt;li class=&quot;submenu2Item&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub2&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
			&amp;lt;li class=&quot;submenu2Item&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub3&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
			&amp;lt;li class=&quot;submenu2Item&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;sub4&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
		&amp;lt;/ul&amp;gt;
	&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저렇게 굳이 submenu를 한 번도 감싸주지 않아도 되지만, 수정과 변화가 많은 프로젝트들을 하다 보니 자연스럽게 그룹으로 되어있는 것은 꼭 한 번씩 감싸주게 되더라고요.&amp;nbsp;&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;스크립트는 간단하게 부모에게 active 클래스를 toggle 시켜주는 거라 어렵진 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626065914017&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let items = document.querySelectorAll('.menuItem');
items.forEach(function(item){
	item.addEventListener('click', function(){
		let parent = item.parentNode;
		if(parent.classList.contains('active')) {
			parent.classList.remove('active');
		} else {
			parent.classList.add('active');
		}
	})
})&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;SCSS를 사용해서 작성하였고, SASS환경이 아닐 수 도 있어서 CSS도 같이 올립니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626066013906&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$size: 50px;
@function submenuWidth(){
    @return $size + 10px;
};
@function getLeft($index){
    @return ($index - 1) * ($size + 10);
};
@function getDelay($index) {
    @return ($index / 10) + s;
};
/* 여기는 중요하지 않아요 start */
*,
body { margin: 0; padding: 0; font-size: 13px; color: #333; font-family: &quot;나눔고딕&quot;, NanumGothic;}
a { display: block; text-decoration: none; color: #333; width: inherit; height: inherit;}
ul { list-style-type: none;}
button { border: 0 none; cursor: pointer;  }
.wrap { width: calc(100% - 100px); margin: 50px;}
/* 여기는 중요하지 않아요 end */
.menuCommon {
    position: relative; 
    width: 100%; 
}
.menuItemCommon {
    width: $size; 
    height: $size;
    background: pink; 
    display: inline-block; 
    text-align: center;
}
.submenuCommon {
    position: absolute; 
    z-index: 1; 
}
.subItemCommon {
    position: absolute; 
    z-index: 1; 
    width: $size; 
    height: $size; 
    background: rgb(163, 163, 163);
    text-align: center; 
}
.menu { 
    @extend .menuCommon;
    .menuItem {
        @extend .menuItemCommon;
    }
    .submenu  {
        @extend .submenuCommon;
        top: 0; 
        left: submenuWidth(); 
        .subItem {
            @extend .subItemCommon;
            top: -50px; 
            opacity: 0; 
            transition: 0.4s ease-in;
            @for $i from 1 through 4 {
                &amp;amp;:nth-child(#{$i}) {
                    left: getLeft($i);
                }
            }
        }
    }
    &amp;amp;.active {
        .subItem {
           top: 0;
           opacity: 1; 
            @for $i from 1 through 4 {
                &amp;amp;:nth-child(#{$i}) {
                    transition-delay: getDelay($i);
                }
            }
        }
    }
}

.menu2 { 
    @extend .menuCommon;
    .menuItem {
        @extend .menuItemCommon;
    }
    .submenu2  {
        @extend .submenuCommon;
        top: 0; 
        left: 0; 
        z-index: -1;
        .submenu2Item {
            @extend .subItemCommon;
            top: 0; 
            left: 0;
            transition: 0.1s;
        }
    }
    &amp;amp;.active {
        .submenu2 {
            left: submenuWidth(); 
            z-index: 1;
            .submenu2Item {
                @for $i from 1 through 4 {
                     &amp;amp;:nth-child(#{$i}) {
                         left: getLeft($i);
                         z-index: $i;
                     }
                 }
             }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에 active가 붙었을 때에 나머지 스타일을 정의하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 subItem에는 top의 값이 모두 0이 되지만 transition-delay를 통해 순차적으로 딜레이를 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transition값을 0.1~0.4로 줘야 하기 때문에 10/index로 계산하면 됩니다. 뒤에 꼭 s(Second)를 붙여주세요!&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;두 번째 subITem에는 활성화 시 각각의 메뉴에 left 값을 적용하는데, 1~4번째 index를 파라미터로 넘겨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;($width + 간격) * (index -1) 만큼 리턴해 주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 SCSS와 Javascript의 다른 점 중 하나는 index가 0번이 아니라 1번부터 시작하는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 헷갈리네요..  &lt;/p&gt;
&lt;pre id=&quot;code_1626066577314&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@charset &quot;UTF-8&quot;;
/* 여기는 중요하지 않아요 start */
*,
body {
  margin: 0;
  padding: 0;
  font-size: 13px;
  color: #333;
  font-family: &quot;나눔고딕&quot;, NanumGothic;
}

a {
  display: block;
  text-decoration: none;
  color: #333;
  width: inherit;
  height: inherit;
}

ul {
  list-style-type: none;
}

button {
  border: 0 none;
  cursor: pointer;
}

.wrap {
  width: calc(100% - 100px);
  margin: 50px;
}

/* 여기는 중요하지 않아요 end */
.menuCommon, .menu, .menu2 {
  position: relative;
  width: 100%;
}

.menuItemCommon, .menu .menuItem, .menu2 .menuItem {
  width: 50px;
  height: 50px;
  background: pink;
  display: inline-block;
  text-align: center;
}

.submenuCommon, .menu .submenu, .menu2 .submenu2 {
  position: absolute;
  z-index: 1;
}

.subItemCommon, .menu .submenu .subItem, .menu2 .submenu2 .submenu2Item {
  position: absolute;
  z-index: 1;
  width: 50px;
  height: 50px;
  background: #a3a3a3;
  text-align: center;
}

.menu .submenu {
  top: 0;
  left: 60px;
}

.menu .submenu .subItem {
  top: -50px;
  opacity: 0;
  transition: 0.4s ease-in;
}

.menu .submenu .subItem:nth-child(1) {
  left: 0px;
}

.menu .submenu .subItem:nth-child(2) {
  left: 60px;
}

.menu .submenu .subItem:nth-child(3) {
  left: 120px;
}

.menu .submenu .subItem:nth-child(4) {
  left: 180px;
}

.menu.active .subItem {
  top: 0;
  opacity: 1;
}

.menu.active .subItem:nth-child(1) {
  transition-delay: 0.1s;
}

.menu.active .subItem:nth-child(2) {
  transition-delay: 0.2s;
}

.menu.active .subItem:nth-child(3) {
  transition-delay: 0.3s;
}

.menu.active .subItem:nth-child(4) {
  transition-delay: 0.4s;
}

.menu2 .submenu2 {
  top: 0;
  left: 0;
  z-index: -1;
}

.menu2 .submenu2 .submenu2Item {
  top: 0;
  left: 0;
  transition: 0.1s;
}

.menu2.active .submenu2 {
  left: 60px;
  z-index: 1;
}

.menu2.active .submenu2 .submenu2Item:nth-child(1) {
  left: 0px;
  z-index: 1;
}

.menu2.active .submenu2 .submenu2Item:nth-child(2) {
  left: 60px;
  z-index: 2;
}

.menu2.active .submenu2 .submenu2Item:nth-child(3) {
  left: 120px;
  z-index: 3;
}

.menu2.active .submenu2 .submenu2Item:nth-child(4) {
  left: 180px;
  z-index: 4;
}&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;아직은 저도 SASS를 공부하고 이리저리 적용해보고 있는 단계라서, 효율적이게 사용한 것 인지 모르겠습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/b7u29zrw/2/embedded/&quot; width=&quot;100%&quot; height=&quot;450&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /Playground</category>
      <category>CSS transition-delay</category>
      <category>quick menu</category>
      <category>SCSS function</category>
      <category>SCSS 반복문</category>
      <category>SCSS 순차</category>
      <category>scss 퀵메뉴</category>
      <category>애니메이션 하나씩</category>
      <category>퀵 메뉴</category>
      <category>퀵 메뉴 만들기</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/47</guid>
      <comments>https://wazacs.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 12 Jul 2021 14:50:50 +0900</pubDate>
    </item>
    <item>
      <title>키패드로 비밀번호 입력하기 (HTML+SASS+JavaScript)</title>
      <link>https://wazacs.tistory.com/46</link>
      <description>&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;자바스크립트는 OOP기반으로 작성하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;405&quot; data-filename=&quot;pwCheck_comp.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bipTso/btq85f3q9lV/Pqv3QdnmTIViPEyKMOI6L0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bipTso/btq85f3q9lV/Pqv3QdnmTIViPEyKMOI6L0/img.gif&quot; data-alt=&quot;결과화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bipTso/btq85f3q9lV/Pqv3QdnmTIViPEyKMOI6L0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bipTso/btq85f3q9lV/Pqv3QdnmTIViPEyKMOI6L0/img.gif&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;405&quot; data-filename=&quot;pwCheck_comp.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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;h3 data-ke-size=&quot;size23&quot;&gt;1. 마크업 스타일&lt;/h3&gt;
&lt;pre id=&quot;code_1625719878306&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;pwWrap&quot;&amp;gt;
    &amp;lt;div class=&quot;pwSection&quot;&amp;gt;
        &amp;lt;span class=&quot;dot&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;span class=&quot;dot&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;span class=&quot;dot&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;span class=&quot;dot&quot;&amp;gt;&amp;lt;/span&amp;gt;
        &amp;lt;p class=&quot;message&quot;&amp;gt;&amp;amp;nbsp;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;numberSection&quot;&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;1&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;2&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;3&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;4&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;5&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;6&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;7&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;8&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;9&amp;lt;/button&amp;gt;
        &amp;lt;button class=&quot;number&quot;&amp;gt;0&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-1. SCSS사용시&lt;/h4&gt;
&lt;pre id=&quot;code_1625719916726&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$errorColor: red;
$confirmColor: green; 

.pwWrap {
  width: 80%; 
  max-width: 450px;
  background: lightGrey; 
  margin: 20px auto;
  
  .pwSection {
    position: relative; 
    display: flex; 
    justify-content: center; 
    align-items: center; 
    width: 100%; 
    height: 150px; 
    .dot {
      display: block; 
      width: 10px; 
      height: 10px; 
      background: darkgrey;
      border-radius: 100%; 
      margin: 0 5px;
      &amp;amp;.active {
        background: rgba(0,0,0,0.7);
      }
    }
    .message {
      position: absolute; 
      bottom: 5px; 
      left: 0; 
      z-index: 1; 
      min-width: 100%;
      text-align: center; 
      font-size: 14px;
      font-weight: bold;
      letter-spacing: -0.03em;
      opacity: 0; 
      transition: .2s ease-out;
    }
  }
  .numberSection {
    overflow: hidden;
    .number {
      float: left; 
      width: 33.33%;
      border: 1px solid rgba(0,0,0,0.1); 
      padding: 12px 0;
      cursor: pointer; 
      background: #F8F2F2;
      &amp;amp;:last-child {
        margin-left: 33.33%;
      }
    }
  }
  
  &amp;amp;.error {
     .message {
        opacity: 1; 
        color: $errorColor; 
      }
  }
  &amp;amp;.confirm {
     .message {
        opacity: 1; 
        color: $confirmColor; 
      }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-2. CSS 사용 시&lt;/h4&gt;
&lt;pre id=&quot;code_1625719937867&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.pwWrap {
  width: 80%;
  max-width: 450px;
  background: lightGrey;
  margin: 20px auto;
}

.pwWrap .pwSection {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 150px;
}

.pwWrap .pwSection .dot {
  display: block;
  width: 10px;
  height: 10px;
  background: darkgrey;
  border-radius: 100%;
  margin: 0 5px;
}

.pwWrap .pwSection .dot.active {
  background: rgba(0, 0, 0, 0.7);
}

.pwWrap .pwSection .message {
  position: absolute;
  bottom: 5px;
  left: 0;
  z-index: 1;
  min-width: 100%;
  text-align: center;
  font-size: 14px;
  font-weight: bold;
  letter-spacing: -0.03em;
  opacity: 0;
  transition: .2s ease-out;
}

.pwWrap .numberSection {
  overflow: hidden;
}

.pwWrap .numberSection .number {
  float: left;
  width: 33.33%;
  border: 1px solid rgba(0, 0, 0, 0.1);
  padding: 12px 0;
  cursor: pointer;
  background: #F8F2F2;
}

.pwWrap .numberSection .number:last-child {
  margin-left: 33.33%;
}

.pwWrap.error .message {
  opacity: 1;
  color: red;
}

.pwWrap.confirm .message {
  opacity: 1;
  color: green;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 스크립트 작성하기&lt;/h3&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;b&gt;1. 숫자 버튼 클릭, 클릭된 숫자를 배열에 담기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 버튼 클릭 횟수 체크&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &lt;b&gt;버튼 클릭 횟수가 4일 때 넘어온 비밀번호와 입력한 값 비교&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 3번 비교가 틀리면 리셋 및 에러 메시지 출력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. &lt;b&gt;3번 비교가&lt;/b&gt; 맞으면 성공 메세지 출력&lt;/b&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;pwCheck라는 함수를 생성한 뒤, 내부에 필요한 변수를 지정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 객체들도 불러옵니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625720070135&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function PwCheck(pw) { 
    const _this = this; 
    _this.pwStr = pw.toString(); // 문자, 숫자열을 모두 허용하기 위해 무조건 한가지 타입으로 맞춤
    _this.password = []; // 지정된 패스워드
    _this.passwordNumber = []; // 입력할 패스워드
    _this.cnt = 0; // 입력횟수 체크
    _this.compChk = false; // 입력완료 체크 
    _this.msg = [
        'Wrong Password! Try Again!  ',
        'Success!  '
    ]; 
    _this.parent = document.querySelector('.pwWrap');
    _this.dots = document.querySelectorAll('.dot');
    _this.numbers = document.querySelectorAll('.number');
    _this.message = document.querySelector('.message');
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 this는 pwCheck함수를 가리키며, this를 _this안에 담았기 때문에,&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PwCheck에서 넘겨받는 pw를 실제 비밀번호라고 가정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pw를 굳이 string으로 받는 이유는 넘어오는 pw가 숫자인 경우 배열에 넣지 못하기 때문입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 받은 pw값을 받자마자 배열화 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625722704731&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 비밀번호를 배열에 넣음 
    _this.getPw = function(){
        for(let i=0; i&amp;lt;_this.pwStr.length; i++) {
            _this.password[i] = _this.pwStr[i];
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 선언한 뒤 &lt;b&gt;즉시 실행시켜주기&lt;/b&gt; 위해 init함수를 하나 더 만들 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;init내부 함수들은 모두 즉시 호출됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625722835716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    _this.init = function(){
        _this.getPw();
    }();&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;그리고 즉시 실행되어야 할 함수가 하나&amp;nbsp; 더 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 클릭 이벤트입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625722981167&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{...}    
    
    // 숫자버튼 click이벤트 연동
    _this.handleListener = function(){
        if(!_this.compChk) {
            _this.numbers.forEach(function(number){
                number.addEventListener('click', function(){console.log(&quot;number&quot;)});
            })
        }
    }
    
    _this.init = function(){
        _this.handleListener();
        _this.getPw();
    }();&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;이때 원래는 id를 두거나 따로 attribute를 따로 설정하여 value값을 지정하지만, 저는 그냥 textContent로 가져왔습니다.&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;handleNumber라는 함수를 따로 두고 여기에 클릭한 숫자를 넘길 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭할 때마다 passwordNumber 배열에 하나씩 넣어주고 카운트를 증가시켜줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 카운트가 4가 된다면, handleResult함수로 넘겨 결과를 처리합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 UI를 담당하는 handleDotActive라는 함수를 두고 입력한 숫자는 동그라미 색을 바꿔줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625723339065&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 숫자키를 눌렀을때 이벤트 
    _this.handleNumber = function(number){
        _this.passwordNumber[_this.cnt] = number.textContent;
        _this.handleDotActive(true);
        _this.cnt++; //  _this.passwordNumber[_this.cnt] 넣어준 후에 증가시켜주세요!
        if(_this.cnt === 4) {
            _this.handleResult();
        }
    }
    // dot 활성화 
    _this.handleDotActive = function(type){
        if(type) {
            _this.dots.forEach(function(dot, i){
                if(i === _this.cnt) dot.classList.add('active'); 
            })
        } else {
            _this.dots.forEach(function(dot){
               dot.classList.remove('active'); 
            })
        }
    }
    // 결과처리 
    _this.handleResult = function(){
        // 결과처리하는 구간!
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleDotActive에서 type을 받는 이유는 비밀번호가 틀렸을 경우 리셋을 처리하는 부분을 같이 사용하기 위해서입니다. 이제 handleResult까지 마무리해봅시다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 비교만 하는 함수를 따로 두어 boolean값을 리턴하도록 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625723615401&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 비밀번호 비교
    _this.handleCheckPw = function(){
        let compare = JSON.stringify(_this.password) === JSON.stringify(_this.passwordNumber);
        return compare; 
    }
    // 결과처리 
    _this.handleResult = function(){
        if(_this.handleCheckPw()) { 
        	// 비밀번호 맞으면
            _this.parent.classList.add('confirm');
            _this.message.textContent = _this.msg[1];
            _this.compChk = true;
        } else {
        	// 비밀번호 틀리면
            _this.parent.classList.add('error');
            _this.message.textContent = _this.msg[0];
            // 입력상태 초기화 
            _this.passwordNumber = [];
            _this.cnt = 0; 
            setTimeout(function(){
                _this.parent.classList.remove('error');
                _this.handleDotActive(); // 동그라미 UI 초기화
            }, 800);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON.stringify로 전체 문자를 비교해야 순서까지 통째로 비교가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( for문으로도 비교할 수 있지만 이게 훨씬 간단하고 편해요! )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호가 틀렸을 때 setTimeout을 통해 에러 문구를 잠시 보여준 뒤 다시 입력할 수 있도록 상태 초기화를 시켜줍니다.&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;&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;그래서 에러 문구가 떠있는 동안에는 키 입력을 해도 동작이 되지 않도록 막아야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 _this.comp라는 변수를 정의해 놓았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625723989283&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{...}

    // 숫자버튼 click이벤트 연동
    _this.handleListener = function(){
        if(!_this.compChk) { // 추가!
            _this.numbers.forEach(function(number){
                {...}
            })
        }
    }
    
    // 숫자키를 눌렀을때 이벤트 
    _this.handleNumber = function(number){
        if(!_this.compChk) { // 추가
            _this.passwordNumber[_this.cnt] = number.textContent;
            {...}
        }
    }
    
        // 결과처리 
    _this.handleResult = function(){
        if(_this.handleCheckPw()) {
           {...}
            _this.compChk = true;
        } else {
            {...}
            // 일시적인 클릭 방지 
            _this.compChk = true;
            setTimeout(function(){
                _this.compChk = false; // 다시 false로 바꿔줘야 클릭이벤트가 작동되겠지요?
                {...}
            }, 800);
        }
    }

{...}&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;함수를 호출하기 위해 새로운 변수에 할당하고 비밀번호를 넘겨줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625724048018&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let pwCheck = new PwCheck(1234);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/7tLhzrq0/109/embedded/&quot; width=&quot;100%&quot; height=&quot;450&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;기본 로직은 모두 같고 버튼 클릭을 키보드 클릭으로 바꿔주기만 하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 키 입력을 표시하기 위해 키패드 UI도 추가해줍니다.&amp;nbsp;&lt;b&gt;&lt;/b&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;handleListener함수를 수정하고, &amp;nbsp;handleButtonActive를 만들어 키보드 클릭 시 키패드가 활성화되도록 해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625724657114&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 키보드 click이벤트 연동 (변경)
    _this.handleListener = function(){
        window.addEventListener('keydown', function(e){
            _this.handleButtonActive(e.key);      
            _this.handleNumber(e.key);  
        })
    }

    // 버튼활성화 (추가)
    _this.handleButtonActive = function(key){
        _this.numbers.forEach(function(number){
            if(number.textContent === key) number.style.background = &quot;lightblue&quot;;
            setTimeout(function(){ number.style.background = &quot;#F8F2F2&quot; }, 140);
        })
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 handleListener에서 e.key를 넘겼기 때문에 기존에 handleNumber에서는 number.textContent로 값을 가져왔지만, 여기서는 그냥 number로 수정해 줘야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625724798505&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 숫자키를 눌렀을때 이벤트 
    _this.handleNumber = function(number){
        if(!_this.compChk) { // 999
            // _this.passwordNumber[_this.cnt] = number.textContent;
            _this.passwordNumber[_this.cnt] = number;  // &amp;lt;- 이렇게 수정
            {...}
        }
    }&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/q3xpdhLk/4/embedded/&quot; width=&quot;100%&quot; height=&quot;450&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /Playground</category>
      <category>javascript keypad</category>
      <category>javascript OOP example</category>
      <category>javascript password check</category>
      <category>javascript 배열 비교</category>
      <category>javascript 키패드</category>
      <category>비밀번호 체크</category>
      <category>자바스크립트 비밀번호 체크</category>
      <category>자바스크립트 키패드</category>
      <category>키패드 만들기</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/46</guid>
      <comments>https://wazacs.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 8 Jul 2021 15:17:17 +0900</pubDate>
    </item>
    <item>
      <title>Live sass compiler 사용하기</title>
      <link>https://wazacs.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번 포스팅에서는 node에서 package.json으로 수작업(?)으로 자동 컴파일되는 환경을 세팅했었는데, (&lt;a href=&quot;https://wazacs.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;보러가기&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 vscode에서 제공되는 &lt;b&gt;Live Sass Compiler&lt;/b&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;VSCode를 열고 확장프로그램 다운로드하는 곳에서 sass를 검색하면 나타납니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;live-sass-compiler.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1dbkA/btq8YVWGP96/M0bB6nMCgSiDfTDzNwqOt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1dbkA/btq8YVWGP96/M0bB6nMCgSiDfTDzNwqOt1/img.png&quot; data-alt=&quot;Live Sass Compiler Plugin&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1dbkA/btq8YVWGP96/M0bB6nMCgSiDfTDzNwqOt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1dbkA%2Fbtq8YVWGP96%2FM0bB6nMCgSiDfTDzNwqOt1%2Fimg.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;live-sass-compiler.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;Live Sass Compiler Plugin&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;install을 클릭하여 설치합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 우측 하단에 Watch Sass를 클릭하면 실시간으로 컴파일이 됩니다.&amp;nbsp;&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 alignLeft&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;713&quot; data-filename=&quot;live-sass-compiler5555.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuVHrF/btq8YWV1ODt/NMTvE4HeddIGNKm9AAOiVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuVHrF/btq8YWV1ODt/NMTvE4HeddIGNKm9AAOiVK/img.png&quot; data-alt=&quot;자동 컴파일 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuVHrF/btq8YWV1ODt/NMTvE4HeddIGNKm9AAOiVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuVHrF%2Fbtq8YWV1ODt%2FNMTvE4HeddIGNKm9AAOiVK%2Fimg.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;713&quot; data-filename=&quot;live-sass-compiler5555.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CSS경로 설정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vscode setting 란에서 scss에서 css로 컴파일 시 css가 저장될 경로를 지정해 줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 하단 Manage 버튼에서 Setting을 들어갑니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sass를 검색하고 방금 설치한 Live Sass Complier를 선택하면 여러 가지 세팅할 수 있는 화면이 나타납니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;live-sass-compiler2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xYaEx/btq8SB63RsE/0cYkWV3dfkv1vKzTwo3fPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xYaEx/btq8SB63RsE/0cYkWV3dfkv1vKzTwo3fPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xYaEx/btq8SB63RsE/0cYkWV3dfkv1vKzTwo3fPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxYaEx%2Fbtq8SB63RsE%2F0cYkWV3dfkv1vKzTwo3fPK%2Fimg.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;live-sass-compiler2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중&lt;b&gt;Set your exported CSS styles, formats &amp;amp; save location. ( export 된 CSS 스타일들, 포맷, 저장 경로 설정 )&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;란의 Edit in settings.json을 클릭하면 설정 파일이 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625545825389&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    ...
    &quot;liveSassCompile.settings.formats&quot;: [
        
        {
            &quot;format&quot;: &quot;expanded&quot;,
            &quot;extensionName&quot;: &quot;.css&quot;,
            &quot;savePath&quot;: &quot;/dist/css&quot; // 컴파일된 css 파일이 저장될 경로
        }
    ],
    ...
}&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MappingURL&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS로 변환된 파일을 보면 css파일 외에 map파일이 생성된 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 css파일 내에 map 주소가 주석 처리되어 하단에 나와있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;229&quot; data-filename=&quot;live-sass-compiler7.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEkZjN/btq8TTfgy3j/7HDeoXkYHZaZM9NpFr1Lb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEkZjN/btq8TTfgy3j/7HDeoXkYHZaZM9NpFr1Lb0/img.png&quot; data-alt=&quot;css.map과 sourceMappingURL&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEkZjN/btq8TTfgy3j/7HDeoXkYHZaZM9NpFr1Lb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEkZjN%2Fbtq8TTfgy3j%2F7HDeoXkYHZaZM9NpFr1Lb0%2Fimg.png&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;229&quot; data-filename=&quot;live-sass-compiler7.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;css.map과 sourceMappingURL&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들의 용도는 바로 변환 이후에도 Chrome브라우저에서 디버깅 시 scss를 참조할 수 있게 각각의 정보를 mapping 해주는 용도입니다.&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;만약 map 정보를 모두 지우고 개발자 모드에서 확인한다면, css파일을 체크합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;271&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjsNGw/btq8U1p0QjL/7Qc97hPQmSsrw0sDzd7N1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjsNGw/btq8U1p0QjL/7Qc97hPQmSsrw0sDzd7N1k/img.png&quot; data-alt=&quot;mapping 정보 없음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjsNGw/btq8U1p0QjL/7Qc97hPQmSsrw0sDzd7N1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjsNGw%2Fbtq8U1p0QjL%2F7Qc97hPQmSsrw0sDzd7N1k%2Fimg.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;271&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&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;mapping 정보 없음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mapping 정보가 존재한다면 css와 scss의 각 라인을 매핑시켜 scss를 체크합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;376&quot; data-filename=&quot;scss22.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rQGUz/btq8U1jeGUj/tK7ysYBiK2F19ip2fKkbD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rQGUz/btq8U1jeGUj/tK7ysYBiK2F19ip2fKkbD1/img.png&quot; data-alt=&quot;mapping 정보 있음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rQGUz/btq8U1jeGUj/tK7ysYBiK2F19ip2fKkbD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrQGUz%2Fbtq8U1jeGUj%2FtK7ysYBiK2F19ip2fKkbD1%2Fimg.png&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;376&quot; data-filename=&quot;scss22.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;mapping 정보 있음&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;만약 프로젝트 전체가 SCSS기반이라면, mapping정보가 필요하겠지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 단독으로 쓰이고 납품하거나 배포 시에는 순수 CSS만 남겨야 한다면 mapping 정보가 필요 없을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 경우 setting에서 mapping 정보가 생기지 않도록 설정할 수 있습니다.&amp;nbsp;&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 alignLeft&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;live-sass-compiler3.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dha1ui/btq8ZqijGZO/YKxkrBKRmjLvAAtidxfLzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dha1ui/btq8ZqijGZO/YKxkrBKRmjLvAAtidxfLzk/img.png&quot; data-alt=&quot;setting화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dha1ui/btq8ZqijGZO/YKxkrBKRmjLvAAtidxfLzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdha1ui%2Fbtq8ZqijGZO%2FYKxkrBKRmjLvAAtidxfLzk%2Fimg.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;live-sass-compiler3.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;setting화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Set it as 'false' if you don't want `.map`file for compolied CSS.&amp;nbsp;&lt;/b&gt;라고 적힌 부분에 Edit in setting.json을 클릭합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625548112552&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    ...
    &quot;liveSassCompile.settings.generateMap&quot;: false, // true면 사용, false면 사용하지 않음 
    &quot;liveSassCompile.settings.formats&quot;: [
        ...
    ],
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 있다면 true를 false로 변경해주면 됩니다. ( 만약 없는 경우 저 한 줄을 추가해 주면 됩니다.&amp;nbsp; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 만들어진 scss에 대하여 더 이상 mapping 정보를 생성하지 않습니다.&lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>css.map</category>
      <category>Live Sass Compile</category>
      <category>sass 자동 컴파일</category>
      <category>scss auto compile</category>
      <category>scss compile</category>
      <category>scss mapping</category>
      <category>scss 자동 컴파일</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/45</guid>
      <comments>https://wazacs.tistory.com/45#entry45comment</comments>
      <pubDate>Tue, 6 Jul 2021 14:13:08 +0900</pubDate>
    </item>
    <item>
      <title>Toast 팝업 만들기</title>
      <link>https://wazacs.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Toast 팝업은 무언가 액션을 시작했을 때, 종료되었을 때, 상태가 바뀌었을 때 등등의 경우 알려주는 팝업입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavasScript환경에서 setTimeout을 통해 간단하게 구현이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;446&quot; data-filename=&quot;tooltip01.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjbiN2/btq8V9OnzhA/Ffu3ADelrKtpZ9unLHMmp1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjbiN2/btq8V9OnzhA/Ffu3ADelrKtpZ9unLHMmp1/img.gif&quot; data-alt=&quot;토스트 팝업&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjbiN2/btq8V9OnzhA/Ffu3ADelrKtpZ9unLHMmp1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cjbiN2/btq8V9OnzhA/Ffu3ADelrKtpZ9unLHMmp1/img.gif&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;446&quot; data-filename=&quot;tooltip01.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;pre id=&quot;code_1625533989044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.js
import { useState, useEffect } from &quot;react&quot;;
import &quot;./styles.css&quot;;
export default function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;div className=&quot;btnWrap&quot;&amp;gt;
        &amp;lt;button onClick={handleToast}&amp;gt;CLICK&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625534031252&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Toast.js
export default function Toast({ msg = &quot;메세지 없음&quot; }) {
  return &amp;lt;div className=&quot;toast&quot;&amp;gt;{msg}&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625534051844&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// styles.css
.App {
  font-family: sans-serif;
  text-align: center;
}

.btnWrap {
  padding-top: 50px;
}

button {
  background: lightseagreen;
  color: #fff;
  padding: 10px 15px;
  border-radius: 4px;
  border: 0 none;
  font-weight: bold;
  cursor: pointer;
}

button:hover {
  background: darkcyan;
}

.toast {
  position: absolute;
  top: 50%;
  left: 50%;
  padding: 11px;
  min-width: 200px;
  transform: translate(-50%, -50%);
  z-index: 3;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  border-radius: 4px;
  border: 1px solid #000;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Toast에 msg를 props값으로 받아 사용하기로 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 넘겨받는 msg값이 없다면 &quot;메세지 없음&quot;을 기본으로 출력하도록 합니다.&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;b&gt;Toast를 구현하는 방법은 다음과 같습니다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Toast state를 false를 기본값으로 둔다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 버튼 클릭시 Toast state를 true로 변경한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. useEffect에서 Toast 값을 리스닝하고, Toast가 true인 경우 1000ms후 다시 false로 변경한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625534341603&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.js
export default function App() {
  const [ToastStatus, setToastStatus] = useState(false);
  const handleToast = () =&amp;gt; {
    setToastStatus(true);
  };
  useEffect(() =&amp;gt; {
    if (ToastStatus) {
      setTimeout(() =&amp;gt; setToastStatus(false), 1000);
    }
  }, [ToastStatus]);
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;div className=&quot;btnWrap&quot;&amp;gt;
        &amp;lt;button onClick={handleToast}&amp;gt;CLICK&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      {ToastStatus &amp;amp;&amp;amp; &amp;lt;Toast msg=&quot;I am Toast  &quot; /&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}&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;여러개를 써야 하는 경우에는 상태 메시지를 객체로 따로 관리하면서 state로 같이 관리해 줄 수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625534839333&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useEffect } from &quot;react&quot;;
import &quot;./styles.css&quot;;
import Toast from &quot;./Toast&quot;;

// 메세지목록
const msgList = {
  complete: &quot;확인되었습니다.&quot;,
  amend: &quot;수정되었습니다.&quot;,
  cancel: &quot;취소되었습니다.&quot;
};

export default function App() {
  const [ToastStatus, setToastStatus] = useState(false);
  const [ToastMsg, setToastMsg] = useState(&quot;&quot;); // 토스트에 표시할 메세지

  const handleToast = (type) =&amp;gt; {
    setToastStatus(true);
    setToastMsg(msgList[type]);
  };

  useEffect(() =&amp;gt; {
    if (ToastStatus) {
      setTimeout(() =&amp;gt; {
        setToastStatus(false);
        setToastMsg(&quot;&quot;); 
      }, 1000);
    }
  }, [ToastStatus]);

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;div className=&quot;btnWrap&quot;&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; handleToast(&quot;complete&quot;)}&amp;gt;확인&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; handleToast(&quot;amend&quot;)}&amp;gt;수정&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; handleToast(&quot;cancel&quot;)}&amp;gt;취소&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      {ToastStatus &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;Toast msg={ToastMsg} /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}&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;이렇게 하면 끝난것 같지만 한 가지 문제점이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 1000ms 이내에 클릭할 때 문구만 실시간으로 바뀌게 됩니다.&amp;nbsp;&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 alignLeft&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;446&quot; data-filename=&quot;tooltip02.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nMlZs/btq8MGglbtE/uPnmGifkSVpKiaZTdfD0O0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nMlZs/btq8MGglbtE/uPnmGifkSVpKiaZTdfD0O0/img.gif&quot; data-alt=&quot;문제점&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nMlZs/btq8MGglbtE/uPnmGifkSVpKiaZTdfD0O0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nMlZs/btq8MGglbtE/uPnmGifkSVpKiaZTdfD0O0/img.gif&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;446&quot; data-filename=&quot;tooltip02.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해서는 토스트 팝업의 액션이 끝나면 동작이 가능하게 해 주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625535067060&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function App() {
 {...}
  const handleToast = (type) =&amp;gt; {
    if (!ToastStatus) { //  ToastStatus가 false일때만!
      setToastStatus(true);
      setToastMsg(msgList[type]);
    }
  };
  {...}
  return (
    ...
  );
}&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;그럼 ToastStatus가 true에서 false로 바뀌기 전까지는 새로운 상태가 업데이트되지 않습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/toast-popup-in-react-v9k4v?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;450px&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react message popup</category>
      <category>react toast</category>
      <category>react toast popup</category>
      <category>toast popup</category>
      <category>리액트 메세지 팝업</category>
      <category>리액트 토스트 팝업</category>
      <category>메세지 팝업</category>
      <category>토스트 팝업</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/44</guid>
      <comments>https://wazacs.tistory.com/44#entry44comment</comments>
      <pubDate>Tue, 6 Jul 2021 10:35:04 +0900</pubDate>
    </item>
    <item>
      <title>초간단 useContext + useReducer</title>
      <link>https://wazacs.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useContext에서 useReducer를 통해 redux처럼 사용해 보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능은 간단하게 admin이라는 state를 가지고 true일 때 false일 때 문구만 다르게 출력하도록 하였습니다.&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;예전에 useContext가 redux를 대체할 수 있을 만큼 편리하다고 하여 작은 프로젝트 하나를 redux를 사용하지 않고 useContex만 사용해서 만들어본 적이 있다.&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;먼저 context를 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625214397143&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createContext, useReducer } from &quot;react&quot;;

// reducer
const initialState = {
    admin: false
}

const reducer = (state, action) =&amp;gt; {
    switch(action.type) {
        case 'SET_ADMIN': 
        return {
            ...state,
            admin: !state.admin
        }
        default: 
            throw new Error(&quot;Doesn't have action type&quot;);
    }
}

// context
export const UserContext = createContext(null);

// provider
export const UserProvider = ({ children }) =&amp;gt; {
    const [state, dispatch] = useReducer(reducer, initialState);
    const { admin } = state;
    return (
        &amp;lt;UserContext.Provider value={{ dispatch, admin }}&amp;gt;
            { children }
        &amp;lt;/UserContext.Provider&amp;gt;
    )
}&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;SET_ADMIN을 호출하는 경우 admin의 상태 값을 반대로 set 하도록 설정하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provider를 통해 자식 컴포넌트에게 value 값으로 state와 dispatch를 전달해 줍니다.&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;components디렉터리를 생성하고 UserButton파일과 UserInfo파일을 생성합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App에서 두 파일을 모두 좀 전에 생성했던 Provider로 감싸줘야 합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625214620973&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// component
import UserInfo from './components/UserInfo'
import UserButton from './components/UserButton'

// provider
import { UserProvider } from './_context/UserContext'

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;UserProvider&amp;gt;
        &amp;lt;UserButton/&amp;gt;
        &amp;lt;UserInfo/&amp;gt;
      &amp;lt;/UserProvider&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&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;이렇게 하면 UserProvider 자식 요소들은 전달받은 value 값을 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때에는 useContext를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625214713280&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// components/UserInfo.js
import { useContext } from 'react';
import { UserContext } from '../_context/UserContext';
function UserInfo() {
    const { admin } = useContext(UserContext);
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;p&amp;gt;{ admin ? &quot;관리자입니다.&quot; : &quot;사용자입니다.&quot;}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}
export default UserInfo
&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625214760567&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// components/UserButton.js
import { useContext } from 'react';
import { UserContext } from '../_context/UserContext';

function UserButton() {
    const { admin, dispatch } = useContext(UserContext);
    const handleToggleUser = () =&amp;gt; {
        dispatch({ type: 'SET_ADMIN' })
    }
    return &amp;lt;button onClick={handleToggleUser}&amp;gt;{admin ? &quot;사용자&quot; : &quot;관리자&quot;}로 전환하기&amp;lt;/button&amp;gt;
}
export default UserButton
&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;이렇게 하면 다음과 같이 잘 동작되는 것을 확인 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;199&quot; data-filename=&quot;useContext.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2FFcE/btq8HsUV0p7/I7uS6K0ZAwHd8C42iOEJO0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2FFcE/btq8HsUV0p7/I7uS6K0ZAwHd8C42iOEJO0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2FFcE/btq8HsUV0p7/I7uS6K0ZAwHd8C42iOEJO0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/d2FFcE/btq8HsUV0p7/I7uS6K0ZAwHd8C42iOEJO0/img.gif&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;199&quot; data-filename=&quot;useContext.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/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;&amp;nbsp;&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;확실히 redux처럼 복잡한 setting을 거치지 않고 바로 사용할 수 있는 점이 좋긴 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날 순수한 redux만 계속해서 사용해야 한다면, 매력적인 대안이 될 수 있었겠지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redux-actions, redux-toolkit, redux-saga 등의 더 편하게 사용할 수 있는 라이브러리들이 추가로 생겨나면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;context만으로 모든걸 처리하는 것이 되려 더 불편하게 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 sample은 상관없지만 기존 플젝은 이거보단 규모가 있었기에, 결국엔 redux로 다시 바꾸는 작업을 했습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 아직 실력이 미천한 소인의 생각일 뿐입니다. &lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react context</category>
      <category>useContext</category>
      <category>useContext redux</category>
      <category>useContext useReducer</category>
      <category>useReducer</category>
      <category>리액트 useContext hook</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/43</guid>
      <comments>https://wazacs.tistory.com/43#entry43comment</comments>
      <pubDate>Fri, 2 Jul 2021 17:38:16 +0900</pubDate>
    </item>
    <item>
      <title>Node 기반 SASS 입문하기</title>
      <link>https://wazacs.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 html, css, javascript로 코딩을 해야 하다 보니, 영 기존처럼 하기에는 귀차니즘이 생겨버렸습니다.  &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;/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;a href=&quot;https://sass-lang.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sass-lang.com/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 환경세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 리 없겠지만 node가 없다면 &lt;a href=&quot;https://nodejs.org/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;설치&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 버전(node-sass, libsass)는 폐지되었다고 하니 Dart Sass를 사용하라고 하네요.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625123819343&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g sass


// 만약 써보고 다시 삭제하고 싶다면 
npm remove -g sass &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;sass-practice라는 디렉터리를 만들고 vscode를 열어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625187044571&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir sass-practice
cd sass-practice 
code .&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;그리고 src 디렉터리를 src 디렉터리에서 생성한 뒤 scss 파일을 만들어봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625187203861&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/style.scss
$color: red;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 파일을 브라우저가 읽을 수 있도록 css 파일로 변환시켜줘야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환하는 방법은 다음과 같은 명령어를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625187257439&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sass scss디렉터리명/css파일명:디렉터리명/파일명 // 일괄변환
sass --watch scss디렉터리명:css디렉터리명 // 실시간변환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 sass 뒤에 scss가 저장되어있는 디렉터리명 : css로 변환된 파일이 저장될 디렉터리명을 적어주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--watch 명령어를 사용하게 되면 저장할 때마다 실시간으로 변환돼서 편리하게 사용할 수 있습니다.&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;b&gt;만약 node를 통해 설정해서 쓰고 싶다면 package.json에서 실행 명령어를 추가하면 실행할 때마다 경로를 작성하지 않아도 됩니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; background-color: #8a3db6;&quot;&gt;npm init -y&lt;/span&gt;를 통해 package.json 파일을 생성해 주세요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 package.json 파일을 열어 실행 스크립트를 추가해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625187618067&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;sass-practice&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;,
    &quot;build:sass&quot;: &quot;sass src:dist&quot;, // 일괄 변환 추가
    &quot;watch:sass&quot;: &quot;sass --watch src:dist &quot; // 실시간 변환 추가
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 이제 우리는 저동 컴파일을 하기 위해 &lt;b&gt;&lt;span style=&quot;background-color: #8a3db6; color: #ffffff;&quot;&gt; &lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;n&lt;/span&gt;&lt;/b&gt;pm run watch:sass&lt;/span&gt;&amp;nbsp;&lt;/b&gt;라는 명령어만 치면 됩니다.&amp;nbsp;&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;2. 사용하기&lt;/h3&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;변수사용, mixin 사용, extend 사용, for / if 문 사용&lt;/b&gt;&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 alignLeft&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;367&quot; data-filename=&quot;sass.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9RnKe/btq8DeC0ANr/ka4sWAlKYnIYuQksLihVRk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9RnKe/btq8DeC0ANr/ka4sWAlKYnIYuQksLihVRk/img.gif&quot; data-alt=&quot;샘플화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9RnKe/btq8DeC0ANr/ka4sWAlKYnIYuQksLihVRk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b9RnKe/btq8DeC0ANr/ka4sWAlKYnIYuQksLihVRk/img.gif&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;367&quot; data-filename=&quot;sass.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;우선 마크업과 스크립트를 작성해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625189394006&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;wrap theme1&quot;&amp;gt;
  &amp;lt;div class=&quot;inner&quot;&amp;gt;
    &amp;lt;button class=&quot;changeTheme btn&quot;&amp;gt;Chagen theme&amp;lt;/button&amp;gt;
    &amp;lt;p&amp;gt;
    Lorem, ipsum dolor sit amet consectetur adipisicing elit. Odio quas, quos doloremque, temporibus eveniet sed explicabo, reiciendis ea assumenda quam amet vitae. Eveniet repellat ipsam adipisci, nostrum ipsum inventore totam?
    &amp;lt;/p&amp;gt;
    &amp;lt;ul class=&quot;list&quot;&amp;gt;
      &amp;lt;li class=&quot;item&quot;&amp;gt;html&amp;lt;/li&amp;gt;
      &amp;lt;li class=&quot;item&quot;&amp;gt;sass&amp;lt;/li&amp;gt;
      &amp;lt;li class=&quot;item&quot;&amp;gt;javascript&amp;lt;/li&amp;gt;
      &amp;lt;li class=&quot;item&quot;&amp;gt;react&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;span class=&quot;message&quot;&amp;gt;I am message &amp;lt;/span&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625189424975&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    const btn = document.querySelector('.changeTheme');
    const wrap = document.querySelector('.wrap');
    btn.addEventListener('click', function(){
        if(wrap.classList.contains('theme1')) {
            wrap.classList.replace('theme1', 'theme2');
        } else {
            wrap.classList.replace('theme2', 'theme1');
        }
    })
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트는 간단하게 wrap에 theme1 클래스명과 theme2 클래스명을 토글 시켜주는 기능입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scss에서 template를 만들고 각각 클래스 명일 때 parameter값을 주어 다르게 표현하려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 src/style.scss에서 template을 작성합니다.&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;b&gt;1. MIXIN&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625189623666&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$padding: 2rem;

@mixin template($theme_name, $theme_bg, $theme_color) {
    padding: $padding;
    background: $theme_bg;
    color: $theme_color;
}

.theme1 {
    @include template(&quot;theme1&quot;, darkred, lightpink);
}

.theme2 {
    @include template(&quot;theme2&quot;, darkblue, lightblue );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 재사용성이 없는 변수 선언은 지양하는 게 좋겠지만 다음과 같이 mixin내부에서도 $padding에 접근할 수 있다는 것을 보여주기 위해 다음과 같이 써보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mixin 선언은 @mixin, 사용 시에는 @include를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$theme_bg, $theme_color를 보면 mixin에서 파라미터 사용이 가능하다는 것을 알 수 있습니다.&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;b&gt;2. extends&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625190168081&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$msg_color: lightgreen;

{ ... }

.message {
    color: $msg_color;
    font-size: 1.2rem;
    font-weight: bold; 
    text-decoration: underline;
}

.theme1 {
    @include template(&quot;theme1&quot;, darkred, lightpink);
    .message {
        $msg_color: rgb(241, 146, 241);  // 변수 재선언
        @extend .message;  // extend
        color: $msg_color; // 재선언된 변수 적용
    } 
}
&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;또한 .message라는 스타일을 extend를 통해 가져다 사용할 수도 있네요.&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;b&gt;3. if문&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625191349884&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@mixin btn($theme_name) {
    border: 0 none; 
    padding: 4px 8px; 
    border-radius: 2px;
    cursor: pointer; 
    @if $theme_name == &quot;theme1&quot; {
        background: palevioletred;
        color: white;
    } @else {
        background: skyblue;
        color: darkblue;
    }
}

.theme1 {
    button {
        @include btn(&quot;theme1&quot;);
    }
    {...}
}

.theme2 {
    button {
        @include btn(&quot;theme2&quot;);
    }
    { ... }
}&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;함수 선언 시에는 @가 붙습니다. 만약 return 할 일이 있다면 @return 이렇게 써줘야 합니다.&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;b&gt;4. for문&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625191474750&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.list {
    list-style-type: none;
    padding: 0;
    @for $i from 1 through 4 {
        .item:nth-child(#{$i}):before {
            content: $i + ': ';
            margin-right: 5px;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for문을 사용하여 nth-child를 통해 앞에 카운트를 붙였습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 목록을 배열 변수에 담아 length값을 체크해서 사용해도 되지만, 이렇든 저렇든 이미 html에서 선언했던 부분을 한번 더 써줘야 하기 때문에 그냥 4로 박았습니다. 만약 더 여러 군데에서 사용해야 한다고 하면 배열을 선언해서 쓰는 것이 훨씬 편리할 것 같습니다.&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;b&gt;5. 파일 분리하기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 css내에서 외부 css를 불러올 때랑 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@include를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625192100149&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/mixin.scss

@mixin template($theme_name, $theme_bg, $theme_color) {
    ...
}

@mixin btn($theme_name) {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625192113624&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@import './mixins.scss';

{...}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변수나 mixin을 한파일에 관리하면 더 좋을 것 같습니다.&amp;nbsp;&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;/h3&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/35vnero6/1/embedded/&quot; width=&quot;100%&quot; height=&quot;450&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>sass</category>
      <category>sass 설정</category>
      <category>scss</category>
      <category>scss for</category>
      <category>scss if</category>
      <category>scss node</category>
      <category>scss package</category>
      <category>scss 설정</category>
      <category>scss 입문</category>
      <category>scss 자동 변환</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/42</guid>
      <comments>https://wazacs.tistory.com/42#entry42comment</comments>
      <pubDate>Thu, 1 Jul 2021 16:18:56 +0900</pubDate>
    </item>
    <item>
      <title>javascript 스크롤 상태바 만들기 (window / div)</title>
      <link>https://wazacs.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤을 함에 따라 상태바가 스크롤 위치에 따라 변경되며 상태를 보여주는 스크롤 프로그레스 바를 만들어보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;417&quot; data-filename=&quot;scroll_progressbar.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLmNi3/btq8xKbnAGh/onr8Ju2ZeIoPAfdzjoKkw0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLmNi3/btq8xKbnAGh/onr8Ju2ZeIoPAfdzjoKkw0/img.gif&quot; data-alt=&quot;완성된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLmNi3/btq8xKbnAGh/onr8Ju2ZeIoPAfdzjoKkw0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bLmNi3/btq8xKbnAGh/onr8Ju2ZeIoPAfdzjoKkw0/img.gif&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;417&quot; data-filename=&quot;scroll_progressbar.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;생각보다 간단해서 공유해보고자 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 마크업을 합니다. 보통은 window 스크롤을 많이 사용하지만 저는 div에 스크롤을 주고 적용해 보려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625120057384&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.html
&amp;lt;div class=&quot;wrap&quot;&amp;gt;
    &amp;lt;div class=&quot;barWrap&quot;&amp;gt;&amp;lt;div class=&quot;bar&quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;perWrap&quot;&amp;gt;&amp;lt;span class=&quot;per&quot;&amp;gt;0%&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;inner&quot;&amp;gt;
        &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;{ ...contents }&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625120141393&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// style.css
html, body, .wrap {
    width: 100%; 
    height: 100%; 
    overflow: hidden;
}

.wrap {
    position: relative; 
    overflow-y: auto;
    padding-top: 13px;
}

.barWrap {
    position: fixed; 
    top: 0; 
    left: 0; 
    width: 100%; 
    height: 13px; 
    background: lightslategrey;
}

.bar {
    position: fixed; 
    top: 0; 
    left: 0;
    height: inherit;
    background: black; 
    color: white;
}

.inner {
    padding: 20px;
    line-height: 3;
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;div에만 스크롤을 주기 위해 html, body 모두 overflow hidden 처리시킵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서는 스크롤을 가진 요소는 wrap이 되고 상태를 표시해줄 객체는 bar가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 div에서 스크롤을 할때마다 값을 가져와야 하므로 addEventListener에 타입을 scroll로 지정합니다.&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;/p&gt;
&lt;pre id=&quot;code_1625121753175&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.js
let wrap = document.querySelector(&quot;.wrap&quot;); 

wrap.addEventListener('scroll', function(){
   console.log(&quot;scrollTop: &quot;, wrap.scrollTop);
   console.log(&quot;scrollHeight: &quot;, wrap.scrollHeight);
   console.log(&quot;clientHeight: &quot;, wrap.clientHeight);
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 스크롤의 대상이 div가 아닌 window라면 wrap을 document.querySlector('. wrap')이 아닌 document.documentElement으로 선언해주세요. 어차피 이외의 로직은 동일합니다.&lt;/b&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot; data-filename=&quot;11.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLPJTq/btq8BmUYXew/VzKejSG1Q05Sq174lfpRnk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLPJTq/btq8BmUYXew/VzKejSG1Q05Sq174lfpRnk/img.jpg&quot; data-alt=&quot;죄송해요 발로 그렸음..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLPJTq/btq8BmUYXew/VzKejSG1Q05Sq174lfpRnk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLPJTq%2Fbtq8BmUYXew%2FVzKejSG1Q05Sq174lfpRnk%2Fimg.jpg&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot; data-filename=&quot;11.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;먼저 전체 높이(scrollHeight)에서 화면에 표시되는 높이(clientHeight)를 제외하고 나머지에서 스크롤의 위치를 나눈다면 나머지 값이 나옵니다. 100을 곱하여 퍼센트로 표시할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625121990034&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.js
let wrap = document.querySelector(&quot;.wrap&quot;);
let bar = document.querySelector('.bar');

wrap.addEventListener('scroll', function(){
    let scrollTop = wrap.scrollTop;
    let scrollHeight = wrap.scrollHeight - wrap.clientHeight;
    let percentage = (scrollTop/scrollHeight) * 100;
    bar.style.width = percentage + '%';
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 스크롤에 따라 progress bar가 위치에 따라 변화하는 것을 알 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 부드러운 움직임을 주고 싶다면 bar에 transition을 추가해주면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625122180617&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// style.css

.bar {
    ...
    transition: .1s ease;
}&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;어차피 bar의 width값이 넘어오니 이를 이용해서 퍼센트를 나타내는 숫자도 추가해 보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 계산할때 소수점까지 나타나므로 Math.floor로 소수점을 제거해 줄 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625122279728&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.html
&amp;lt;div class=&quot;wrap&quot;&amp;gt;
    &amp;lt;div class=&quot;barWrap&quot;&amp;gt;&amp;lt;div class=&quot;bar&quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;perWrap&quot;&amp;gt;&amp;lt;span class=&quot;per&quot;&amp;gt;0%&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- add --&amp;gt;
    &amp;lt;div class=&quot;inner&quot;&amp;gt;
        { ... }
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625122305630&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// style.css

{...}

.perWrap {
    position: fixed; 
    top: 20px; 
    left: 10px;
    width: 50px; 
    height: 50px; 
    text-align: center;
    background: black; 
    color: #fff; 
    border-radius: 100%;
    line-height:3.5;
    font-size: 13px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625122340683&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.js
let wrap = document.querySelector(&quot;.wrap&quot;);
let bar = document.querySelector('.bar');
let per = document.querySelector('.per'); // add

wrap.addEventListener('scroll', function(){
    {...}
    per.innerText = Math.floor(percentage) + '%'; // add
})&lt;/code&gt;&lt;/pre&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;하지만 한 가지 문제점이 있습니다. 만약 스크롤 하던 중 viewport의 사이즈가 달라져 버린다면, 계산을 하지 못하고 NaN을 뱉어버립니다. (Not-A-Number:숫자가 아님)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NaN인 경우 0을 리턴하도록 스크립트 한 줄을 추가해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625122596307&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let wrap = document.querySelector(&quot;.wrap&quot;);
let bar = document.querySelector('.bar');
let per = document.querySelector('.per');

wrap.addEventListener('scroll', function(){
    {...}
    if(isNaN(percentage)) percentage = 0;  // add
    bar.style.width = percentage + '%';
    per.innerText = Math.floor(percentage) + '%';
})&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/6jz98s53/21/embedded/js,html,css,result/dark/&quot; width=&quot;100%&quot; height=&quot;450&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /Playground</category>
      <category>scroll div</category>
      <category>scroll indicator</category>
      <category>scroll percentage</category>
      <category>scroll progressive</category>
      <category>scroll status bar</category>
      <category>scrolltop</category>
      <category>스크롤 바</category>
      <category>스크롤 상태 바</category>
      <category>스크롤 퍼센트</category>
      <category>스크롤 프로그레시브 바</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/41</guid>
      <comments>https://wazacs.tistory.com/41#entry41comment</comments>
      <pubDate>Thu, 1 Jul 2021 16:01:17 +0900</pubDate>
    </item>
    <item>
      <title>React 에서 useRef 사용하기</title>
      <link>https://wazacs.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참조: &lt;a href=&quot;https://ko.reactjs.org/docs/hooks-reference.html#useref&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.reactjs.org/docs/hooks-reference.html#useref&lt;/a&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;useRef는 생각보다 유용합니다. SPA에서는 변화에 따른 렌더링으로 객체에 직접 접근하는 것은 위험합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 React에서는 useRef hook을 지원합니다. useRef가 직접 객체에 접근하는 것을 가능하게 해주는 이유는 매번 렌더링을 할 때마다 동일한 객체를 제공한다고 공식문서에 나와있습니다.&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;반면, state와는 달리 실시간 변화를 감지하여 변화된 값을 리턴하지는 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 가능하게 하려면 ref를 따로 setState해줘야 합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. useRef를 통한 timer만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 :&amp;nbsp;&lt;a href=&quot;https://dmitripavlutin.com/react-useref-guide/&quot;&gt;https://dmitripavlutin.com/react-useref-guide/&lt;/a&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;타이머를 만들기 위한 jsx를 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625020074666&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useRef } from &quot;react&quot;;

export default function UseRefSample1() {
  const countRef = useRef(null);
  const [Count, setCount] = useState(0);

  return (
    &amp;lt;div className=&quot;wrap&quot;&amp;gt;
        &amp;lt;h2&amp;gt;StopWatch 만들기&amp;lt;/h2&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;div className=&quot;timer&quot;&amp;gt;Timer: {Count}ms&amp;lt;/div&amp;gt;
            &amp;lt;button&amp;gt;Start&amp;lt;/button&amp;gt;
            &amp;lt;button&amp;gt;Stop&amp;lt;/button&amp;gt;
            &amp;lt;button&amp;gt;Reset&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;countRef는 setInterval을 담기 위함이고 Count state는 초 단위를 업데이트하는 데 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 타이머 시작, 중지, 리셋 기능을 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625020523625&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const startHandler = () =&amp;gt; {
    countRef.current = setInterval(() =&amp;gt; setCount((c) =&amp;gt; c + 1), 100);
  };

  const stopHandler = () =&amp;gt; {
    clearInterval(countRef.current);
    countRef.current = null;
  };

  const resetHandler = () =&amp;gt; {
    stopHandler();
    setCount(0);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;startHandler에서 setInterval로 초단위 Count에 setState 시켜주고 해당 setInterval은 ref.current에 담겨있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 stopHandler에서 쉽게 clearInterval을 해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 startHandler를 클릭하면 Count의 값은 남아있으므로 기존 카운트가 쌓인 값에서 다시 1초마다 플러스되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resetHandler에서 온전한 리셋을 위해 Count의 값도 0 처리해줍니다.&amp;nbsp;&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 alignLeft&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;235&quot; data-filename=&quot;useRef1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdb933/btq8vheU6Yx/GzLSzo1MwGIEki692dk1pK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdb933/btq8vheU6Yx/GzLSzo1MwGIEki692dk1pK/img.gif&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdb933/btq8vheU6Yx/GzLSzo1MwGIEki692dk1pK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bdb933/btq8vheU6Yx/GzLSzo1MwGIEki692dk1pK/img.gif&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;235&quot; data-filename=&quot;useRef1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. useRef를 통해 input Focus 주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 입력하는 form 양식에 빈 값 체크를 해야 하는 경우가 있다면 체크 후 해당 input으로 focus를 주는 기능에서 유용하게 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 마크업을 해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625028036292&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useRef, useState } from &quot;react&quot;;

export default function UseRefSample2() {
  const [Msg, setMsg] = useState(null);
  return (
    &amp;lt;div className=&quot;wrap&quot;&amp;gt;
      &amp;lt;h2&amp;gt;Input Focus&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;
        &amp;lt;input placeholder=&quot;Name&quot; /&amp;gt;
      &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;
        &amp;lt;input placeholder=&quot;Age&quot; /&amp;gt;
      &amp;lt;/p&amp;gt;
      &amp;lt;button&amp;gt;Submit&amp;lt;/button&amp;gt;
      {
          Msg === null ? &quot;&quot; 
          : Msg ?  &amp;lt;div className=&quot;msg&quot;&amp;gt;모두 입력되었습니다!&amp;lt;/div&amp;gt; 
          : &amp;lt;div className=&quot;msg warning&quot;&amp;gt;모두 입력하세요!&amp;lt;/div&amp;gt;
      }
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 input value와 state를 연결합니다. 입력 양식이 여러 개라면 객체 타입으로 한 state에 묶고 name값을 키값으로 지정하여 onChange시 상태 변경해주는 것을 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625028265275&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{...}
  const [Inputs, setInputs] = useState({
    name: &quot;&quot;,
    age: &quot;&quot;
  });

  const handleChange = (e) =&amp;gt; {
    setInputs({
      ...Inputs,
      [e.target.name]: e.target.value
    });
  };
{...}
      &amp;lt;p&amp;gt;
        &amp;lt;input
          placeholder=&quot;Name&quot;
          name=&quot;name&quot;
          value={Inputs.name}
          onChange={handleChange}
        /&amp;gt;
      &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;
        &amp;lt;input
          placeholder=&quot;Age&quot;
          name=&quot;age&quot;
          value={Inputs.age}
          onChange={handleChange}
        /&amp;gt;
      &amp;lt;/p&amp;gt;&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;그리고 모두 입력한 뒤 처리되는 handleSubmit을 작성합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 간단하게 Msg 값 하나를 두어 null일 때는 메시지가 보이지 않고, false일 때는 &quot;모두 입력하세요!&quot;, true일 때는 &quot;모두 입력되었습니다.&quot;라는 메시지를 띄웁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 빈 값 체크를 하게 되는데 handleCheckForm 함수를 따로 두어 사용하도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625028585567&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleSubmit = (e) =&amp;gt; {
  if (handleCheckForm()) {
  	setMsg(true);
  } else {
  	setMsg(false);
  }
};
  
 const handleCheckForm = () =&amp;gt; {
  if (Inputs.name === &quot;&quot;) {
  	alert(&quot;이름을 입력하세요!&quot;);
  	return false;
  }
  if (Inputs.age === &quot;&quot;) {
  	alert(&quot;나이를 입력하세요!&quot;);
 	 return false;
  }
  return true;
};

  return (
    &amp;lt;div className=&quot;wrap&quot;&amp;gt;
      &amp;lt;h2&amp;gt;Input Focus&amp;lt;/h2&amp;gt;
      { ... }
      &amp;lt;button onClick={handleSubmit}&amp;gt;Submit&amp;lt;/button&amp;gt;
      {Msg === null ? (
        &quot;&quot;
      ) : Msg ? (
        &amp;lt;div className=&quot;msg&quot;&amp;gt;모두 입력되었습니다.&amp;lt;/div&amp;gt;
      ) : (
        &amp;lt;div className=&quot;msg warning&quot;&amp;gt;모두 입력하세요!&amp;lt;/div&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name, age 둘 중 하나라도 빈 값이면 false를 반환하고 둘 다 빈값이 아니면 true를 반환하면서 setMsg가 true 혹은 false로 변경됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 나머지 부가 기능들을 useRef를 통해 처리해보도록 하겠습니다.&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;b&gt;1. submit클릭 시 값이 비어있는 input에 자동 focus를 한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. name input에서 Enter시 다음 input으로 이동, age input에서 Enter시 handleSubmit 호출&amp;nbsp;&lt;/b&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;먼저 useRef를 통해 nameRef, ageRef를 선언하고 input에 연결시켜줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 접근은 current를 통해 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 ref를 console로 조회해보면 내부적으로 많은 정보를 담고 있음을 알 수 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;794&quot; data-filename=&quot;useRef11.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv0eP8/btq8v7KdQCI/6ItEjRjQoK0KOeBlD16Xmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv0eP8/btq8v7KdQCI/6ItEjRjQoK0KOeBlD16Xmk/img.png&quot; data-alt=&quot;ref 조회 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv0eP8/btq8v7KdQCI/6ItEjRjQoK0KOeBlD16Xmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv0eP8%2Fbtq8v7KdQCI%2F6ItEjRjQoK0KOeBlD16Xmk%2Fimg.png&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;794&quot; data-filename=&quot;useRef11.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;ref 조회 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1625029858665&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useRef, useState } from &quot;react&quot;;

const nameRef = useRef(null);
const ageRef = useRef(null);

// Enter시 age input으로 focus
const handleNextInput = (e) =&amp;gt; {
    if (e.key === &quot;Enter&quot;) {
        ageRef.current.focus();
    }
};

// Enter시 handleSubmit 호출
const handleChangeSubmit = (e) =&amp;gt; {
    if (e.key === &quot;Enter&quot;) handleSubmit();
};

// 최초 페이지 진입시 name input에 focus
useEffect(() =&amp;gt; {
    nameRef.current.focus();
}, []);

{...}

&amp;lt;p&amp;gt;
    &amp;lt;input
        placeholder=&quot;Name&quot;
        name=&quot;name&quot;
        value={Inputs.name}
        ref={nameRef}
        onChange={handleChange}
        onKeyPress={handleNextInput} 
    /&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
    &amp;lt;input
        placeholder=&quot;Age&quot;
        name=&quot;age&quot;
        value={Inputs.age}
        ref={ageRef}
        onChange={handleChange}
        onKeyPress={handleChangeSubmit}
    /&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/react-useref-sample-0xzc4?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;450px&quot;&gt;&lt;/iframe&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;이 외에 useRef를 통해 다른 기능을 구현한 것들을 살펴보고 싶다면 해당 글도 확인해보세요.&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;a href=&quot;https://wazacs.tistory.com/30&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React div 스크롤시 Fix 하기 (Element scroll)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wazacs.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react draggable 간단 샘플&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wazacs.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React&amp;nbsp;에서&amp;nbsp;애니메이션,&amp;nbsp;인터렉션&amp;nbsp;구축하기&amp;nbsp;(emotion&amp;nbsp;/&amp;nbsp;tweenmax)&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react hook useRef</category>
      <category>react input enter</category>
      <category>react input focus</category>
      <category>React useRef</category>
      <category>react 타이머</category>
      <category>useRef</category>
      <category>useRef sample</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/40</guid>
      <comments>https://wazacs.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 30 Jun 2021 14:25:23 +0900</pubDate>
    </item>
    <item>
      <title>CSS에서 변수 사용하기</title>
      <link>https://wazacs.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참조 : &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/Using_CSS_custom_properties&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/Using_CSS_custom_properties&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IE에서는 지원하지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;447&quot; data-filename=&quot;css_var1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY7het/btq8qIZOhV5/dLFxRvXRFFHfbdUkIGHE60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY7het/btq8qIZOhV5/dLFxRvXRFFHfbdUkIGHE60/img.png&quot; data-alt=&quot;Can I use 검색결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY7het/btq8qIZOhV5/dLFxRvXRFFHfbdUkIGHE60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY7het%2Fbtq8qIZOhV5%2FdLFxRvXRFFHfbdUkIGHE60%2Fimg.png&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;447&quot; data-filename=&quot;css_var1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;Can I use 검색결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원이 가능하도록 도와주는&lt;a href=&quot;https://github.com/jhildenbiddle/css-vars-ponyfill&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;css-var-ponyfill이라는 라이브러리&lt;/a&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;element와 root에서 선언함으로써 스코프(scope)를 지정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;element에서 지정된 변수는 해당 element가 부모가 아니면 적용이 되지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625018694819&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:root {
    --box-size: 50px;
}

.box1 {
    --main-color: orange;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:root는 전역을 뜻하고. box1 은. box1을 포함한 하위 element 에게만 적용됩니다.&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; 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625018747620&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;wrap&quot;&amp;gt;
    &amp;lt;div class=&quot;box box1&quot;&amp;gt;&amp;lt;div class=&quot;inner&quot;&amp;gt;box1&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;box&quot;&amp;gt;&amp;lt;div class=&quot;inner&quot;&amp;gt;box2&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;box&quot;&amp;gt;&amp;lt;div class=&quot;inner&quot;&amp;gt;box3&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt; &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wrap안에 box들을 float 하고 wrap의 width값을 box 사이즈 * 3 만큼 주려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 사용하지 않는 CSS라면 다음과 같이 줬을 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625018880764&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.wrap {
    overflow :hidden;
    width: calc(50px * 3);
    background: red;
}

.box {
    float: left;
    background: green;
    width: 50px;
    height: 50px; 
    color: yellow; 
}&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;만약 box의 사이즈가 45px로 줄어들기라도 한다면 50px이라고 선언된 부분을 모두 찾아 수정해야 합니다.&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;&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;선언된 변수를 사용하려면 var(변수명) 이렇게 사용하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625018953280&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:root {
    --box-size: 50px;
}

.wrap {
    overflow :hidden;
    width: calc(var(--box-size) * 3);
    background: red;
}

.box {
    float: left;
    background: green;
    width: var(--box-size);
    height: var(--box-size); 
    color: yellow; 
}
&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;요즘은 Sass와 같은 전처리기를 사용한다거나, SPA개발로는 css도 스크립트로 작성하니 그다지 필요성은 없어 보이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css에서도 변수를 활용할 수 있다는 것이 흥미로웠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날에 하던 프로젝트 중&amp;nbsp; slider를 css만으로 직접 구현할 때 사용했었던 기억이 나기도 합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/c4sayedg/2/embedded/html,css,result/&quot; width=&quot;100%&quot; height=&quot;450&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>:root</category>
      <category>css var</category>
      <category>css variable</category>
      <category>css 변수</category>
      <category>stlye 변수</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/39</guid>
      <comments>https://wazacs.tistory.com/39#entry39comment</comments>
      <pubDate>Wed, 30 Jun 2021 11:16:47 +0900</pubDate>
    </item>
    <item>
      <title>React 에서 애니메이션, 인터렉션 구축하기 (emotion / tweenmax)</title>
      <link>https://wazacs.tistory.com/38</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. emotion keyframes 사용하기&lt;/h2&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;&lt;span style=&quot;color: #6272a4;&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/@emotion/react&quot;&gt;https://www.npmjs.com/package/@emotion/react&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #6272a4;&quot;&gt;&lt;a href=&quot;https://emotion.sh/docs/keyframes&quot;&gt;https://emotion.sh/docs/keyframes&lt;/a&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;emotion/react 에서는 keyframes를 통하여 기존 CSS에서 사용하였던 애니메이션 keyframe을 그대로 사용할 수 있습니다. 먼저 설치를 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624424676091&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @emotion/react&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 사용법 및 런타임에러가 난다면 &lt;a href=&quot;https://wazacs.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&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;아주 심플한 floating 애니메이션을 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 keyframes를 불러옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;floating 애니메이션을 만들고, 스타일에 넣어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 세팅만으로 기존에 쓰던 CSS대로 사용할 수 있으니 아주 편합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 따로 분리해서 여기저기 import 해서 쓸 수 있으니 아주 유용하게 사용 될꺼라 생각됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624424934226&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { css, keyframes } from '@emotion/react'

function Animation1() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;emotion Animation&amp;lt;/h2&amp;gt;
            &amp;lt;div className=&quot;wrap&quot;&amp;gt;
                &amp;lt;div className=&quot;box&quot; css={boxStyle}&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

const floating = keyframes`
    0 {
        transform: translateY(0);    
    }
    50% {
        transform: translateY(-15px);
    }
    100% {
        transform: translateY(0);
    }
`

const boxStyle = css`
    width: 50px; 
    height: 50px; 
    border-radius: 100%;
    background: #a951bf;
    animation: ${floating} 2s ease infinite;
`


export default Animation1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;animation1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwkCKw/btq7ZyIbd5e/g5QKKtYWEEbkTvE7Y3y8p0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwkCKw/btq7ZyIbd5e/g5QKKtYWEEbkTvE7Y3y8p0/img.gif&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwkCKw/btq7ZyIbd5e/g5QKKtYWEEbkTvE7Y3y8p0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bwkCKw/btq7ZyIbd5e/g5QKKtYWEEbkTvE7Y3y8p0/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;animation1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. gsap 사용하기&lt;/h3&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;&lt;span style=&quot;color: #6272a4;&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-gsap&quot;&gt;https://www.npmjs.com/package/react-gsap&lt;/a&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;gsap 공식 문서를 보니 gsap 와 react-gsap 두 가지를 설치하도록 안내되어있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gsap는 기존 javascript에서 쓰던 방법과 크게 다른게 없었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624426564757&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; 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-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add gsap&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gsap는 from, to 키워드로 시작점과 끝점을 연결 시킬 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 단독으로 to만 사용하여 기존 css에서 변형을 줄 수 있습니다.&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;기존 gsap 동작방식은 객체에 직접 접근하여 움직임을 주기 때문에 리액트에서는 useRef를 사용하여,&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;기본적으로 인터렉션은 한 번만 동작 하며, 반복을 주고 싶다면 repeat: -1 속성을 추가해 주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624426584803&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; 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-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import gsap from 'gsap'

function Animation2() {
    const boxRef = useRef(null);
    useEffect(() =&amp;gt; {
        gsap.to(boxRef.current, 1, {transform: 'translateX(200px)', delay: .5, ease: 'ease'})
    }, [])

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;gsap Animation&amp;lt;/h2&amp;gt;
            &amp;lt;div className=&quot;wrap&quot;&amp;gt;
                &amp;lt;div className=&quot;box&quot; css={boxStyle} ref={boxRef}&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

const boxStyle = css`
    width: 50px; 
    height: 50px; 
    border-radius: 100%;
    background: #bf5160;
`
export default Animation2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;animation2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VeoIZ/btq7ZgAOqTT/ena89l5CjYypI6Uf881Kik/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VeoIZ/btq7ZgAOqTT/ena89l5CjYypI6Uf881Kik/img.gif&quot; data-alt=&quot;결과 ( 한 번만 동작 )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VeoIZ/btq7ZgAOqTT/ena89l5CjYypI6Uf881Kik/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/VeoIZ/btq7ZgAOqTT/ena89l5CjYypI6Uf881Kik/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;animation2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; background: url('../image/divider-line.svg') center -208px / 200px 420px repeat-x; height: 2px; padding: 21px 0px; color: #333333; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; 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-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. react-gsap 사용하기&lt;/h3&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;&lt;span style=&quot;color: #6272a4;&quot;&gt;&lt;a href=&quot;https://bitworking.github.io/react-gsap/src-components-timeline&quot;&gt;https://bitworking.github.io/react-gsap/src-components-timeline&lt;/a&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;gsap와 react-gsap의 사용법이 꽤 많이 달라고 별개로 구분을 해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 timeline을 주어 두 가지 동작을 순차적으로 불러오도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. opacity 0 -&amp;gt; 1&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 오른쪽으로 움직임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-gsap는 gsap와는 달리 class 컴포넌트 형태로 움직임을 주려는 객체를 target으로 보내서 구현을 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 구현하려는 모션들을 Tween에 담고 TimeLine으로 감싸주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624426646562&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; 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-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add gsap react-gsap&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1624426648568&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; 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-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Timeline, Tween } from 'react-gsap';

function Animation3() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;react-gsap Animation&amp;lt;/h2&amp;gt;
            &amp;lt;div className=&quot;wrap&quot;&amp;gt;
                &amp;lt;Timeline
                    target={&amp;lt;div className=&quot;box&quot; css={boxStyle}&amp;gt;&amp;lt;/div&amp;gt;}
                &amp;gt;
                    &amp;lt;Tween from={{ opacity: 0 }} to={{opacity: 1}} duration={1} /&amp;gt;
                    &amp;lt;Tween to={{ x: '200px' }} /&amp;gt;
                &amp;lt;/Timeline&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

const boxStyle = css`
    width: 50px; 
    height: 50px; 
    border-radius: 100%;
    background: #bf5160;
    opacity: 0;
`

export default Animation3&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;animation3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brGXQ6/btq7Up7wKku/etDJExf9lUfGVYLbJPhnC1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brGXQ6/btq7Up7wKku/etDJExf9lUfGVYLbJPhnC1/img.gif&quot; data-alt=&quot;결과 ( 한 번만 동작 )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brGXQ6/btq7Up7wKku/etDJExf9lUfGVYLbJPhnC1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/brGXQ6/btq7Up7wKku/etDJExf9lUfGVYLbJPhnC1/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;animation3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;공식 사이트를 보시면 stop, pause 등등 다양한 옵션이 있습니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/react-animation-interaction-u8b83?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;500px&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>emotion animation</category>
      <category>react animation</category>
      <category>react emotion animation</category>
      <category>react emotion keyframe</category>
      <category>react floating animation</category>
      <category>react gsap</category>
      <category>react interaction</category>
      <category>react tweenmax</category>
      <category>리액트 애니메이션</category>
      <category>리액트 인터렉션</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/38</guid>
      <comments>https://wazacs.tistory.com/38#entry38comment</comments>
      <pubDate>Wed, 23 Jun 2021 14:35:04 +0900</pubDate>
    </item>
    <item>
      <title>react draggable 간단 샘플</title>
      <link>https://wazacs.tistory.com/37</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;react-draggable : &lt;a href=&quot;https://www.npmjs.com/package/react-draggable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-draggable&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1624345304770&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;react-draggable&quot; data-og-description=&quot;React draggable component&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/react-draggable&quot; data-og-url=&quot;https://www.npmjs.com/package/react-draggable&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9hVr4/hyKD8SVnBy/n9Znsz7JnguyasGc9Yd9zK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-draggable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/react-draggable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9hVr4/hyKD8SVnBy/n9Znsz7JnguyasGc9Yd9zK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;react-draggable&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;React draggable component&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.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;객체를 자유롭게 드래그하는 모션을 찾다가 직접 구현도 해보았지만 렌더하는 과정에서 buffer가 생기는것 같아 라이브러리를 찾아보았습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1624345408041&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install react-draggable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 불러오기 및 적용&lt;/h3&gt;
&lt;pre id=&quot;code_1624351856115&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Draggable from &quot;react-draggable&quot;; 

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 }); // box의 포지션 값
  // 업데이트 되는 값을 set 해줌
  const trackPos = (data) =&amp;gt; {
	setPosition({ x: data.x, y: data.y }); 
  };
  return (
  &amp;lt;div className=&quot;App&quot;&amp;gt;
    &amp;lt;Draggable onDrag={(e, data) =&amp;gt; trackPos(data)} &amp;gt;
      &amp;lt;div className=&quot;box&quot; &amp;gt;
        &amp;lt;div&amp;gt;BOX&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;x: {position.x.toFixed(0)}, y: {position.y.toFixed(0)}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/Draggable&amp;gt;
  &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1624410638539&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// css
.box {
  position: absolute;
  cursor: move;
  color: black;
  width: 100px;
  border-radius: 5px;
  padding: 1em;
  margin: auto;
  user-select: none;
  background: lightgrey;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Draggable로 감싼뒤 필요한 옵션과 함수를 props에 전달합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x, y 좌표를 toFixed(0) 으로 소수점을 제외한 값을 표시합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 반드시 css에서 absolute를 줘야 작동이 되는것은 아닙니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onDrag 이외에 다양한 옵션값이 있습니다. ( 상단 react-draggable 사이트에서 확인 가능 )&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;적용시 동작이 제대로 되더라도 콘솔에 에러가 날 수 있습니다.  &lt;br /&gt;useRef를 통해 해결할 수 있습니다. 자세한 사항은&amp;nbsp;&lt;a href=&quot;https://wazacs.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 참조하세요.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;draggable1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bodDCA/btq7SEQqQWs/OlAE2tvN1OV3UmcI1Q3wuk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bodDCA/btq7SEQqQWs/OlAE2tvN1OV3UmcI1Q3wuk/img.gif&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bodDCA/btq7SEQqQWs/OlAE2tvN1OV3UmcI1Q3wuk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bodDCA/btq7SEQqQWs/OlAE2tvN1OV3UmcI1Q3wuk/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;draggable1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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;h3 data-ke-size=&quot;size23&quot;&gt;3. 드래그 중 효과주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드래그중 onStart와 onStop 옵션을 통해 함수를 호출 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드래그 중 opacity를 주는 효과를 적용해 보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624410575192&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ ... }
const [Opacity, setOpacity] = useState(false);

const handleStart = () =&amp;gt; {
  setOpacity(true);
};
const handleEnd = () =&amp;gt; {
  setOpacity(false);
};

{...}

&amp;lt;Draggable
  nodeRef={nodeRef}
  onDrag={(e, data) =&amp;gt; trackPos(data)}
  onStart={handleStart}
  onStop={handleEnd}
&amp;gt;
  &amp;lt;div
    ref={nodeRef}
    className=&quot;box&quot;
    style={{ opacity: Opacity ? &quot;0.6&quot; : &quot;1&quot; }}
  &amp;gt;
    &amp;lt;div&amp;gt;BOX&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
      x: {position.x.toFixed(0)}, y: {position.y.toFixed(0)}
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/Draggable&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 onStart을 통해 드래그를 하는 시점에서 setState를 하여 style 또한 변경 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, onStop을 통해 드래그를 종료하는 시점에 다시 setState를 하여 stlye을 변경해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;draggable2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLnIg/btq7U2KtVR9/awmyvwwVxLYAdrZHOUCK2K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLnIg/btq7U2KtVR9/awmyvwwVxLYAdrZHOUCK2K/img.gif&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLnIg/btq7U2KtVR9/awmyvwwVxLYAdrZHOUCK2K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/BLnIg/btq7U2KtVR9/awmyvwwVxLYAdrZHOUCK2K/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;183&quot; data-filename=&quot;draggable2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/react-draggable-sample-0pv17?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;500px&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react drag</category>
      <category>react drag free</category>
      <category>react draggable</category>
      <category>react-draggable</category>
      <category>리액트 드래그</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/37</guid>
      <comments>https://wazacs.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 22 Jun 2021 16:05:35 +0900</pubDate>
    </item>
    <item>
      <title>Error] findDOMNode is deprecated in StrictMode</title>
      <link>https://wazacs.tistory.com/36</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 에러 발생 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-draggable을 쓸 일이 생겨서 사용하던 중,&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 alignLeft&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;382&quot; data-filename=&quot;draggable_error.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chPpaE/btq7MbnEH9S/e0ZmvXWAKonJEZoR11caok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chPpaE/btq7MbnEH9S/e0ZmvXWAKonJEZoR11caok/img.png&quot; data-alt=&quot;에러 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chPpaE/btq7MbnEH9S/e0ZmvXWAKonJEZoR11caok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchPpaE%2Fbtq7MbnEH9S%2Fe0ZmvXWAKonJEZoR11caok%2Fimg.png&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;382&quot; data-filename=&quot;draggable_error.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;보다시피 findDOMNode 는 StrictMode에서 제거되었다는 내용이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StricMode는 React16에서 추가된 모드로 생명주기에 예상치 못한 오류를 발생시키는 것을 방지하고자 한다고 한다.&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;a href=&quot;https://ko.reactjs.org/docs/strict-mode.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.reactjs.org/docs/strict-mode.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624326894430&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;Strict 모드 &amp;ndash; React&quot; data-og-description=&quot;A JavaScript library for building user interfaces&quot; data-og-host=&quot;ko.reactjs.org&quot; data-og-source-url=&quot;https://ko.reactjs.org/docs/strict-mode.html&quot; data-og-url=&quot;https://ko.reactjs.org/docs/strict-mode.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/t86oL/hyKEbIQU2x/HUqKNYjzY8oAApo9NaVLdK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/strict-mode.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.reactjs.org/docs/strict-mode.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/t86oL/hyKEbIQU2x/HUqKNYjzY8oAApo9NaVLdK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Strict 모드 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A JavaScript library for building user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.reactjs.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;react-draggable 같은 경우 직접 개체를 컨트롤하다 보니 발생하는 문제인 것 같다.&amp;nbsp;&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;2. 해결책&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-1. StrictMode 해제 하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 strictMode를 사용하지 않는 다면 해제시켜주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624327047452&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/index.js

ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById('root')
);

// 이 부분은 다음과 같이 수정한다

ReactDOM.render(
  &amp;lt;App /&amp;gt;,
  document.getElementById('root')
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2-2. nodeRef 주기  &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 어지간한 프로젝트는 StrictMode 사용을 권장할테니, 위의 방법이 최선이라고 할 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 최적화된 방법은 useRef를 사용하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이게 react-draggable 공식 사이트에서도 제시한 방안이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;363&quot; data-filename=&quot;draggable_error2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KLFxJ/btq7PLWBwmL/LaDl2RMksRkvgFdEKHgbN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KLFxJ/btq7PLWBwmL/LaDl2RMksRkvgFdEKHgbN0/img.png&quot; data-alt=&quot;해결책 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KLFxJ/btq7PLWBwmL/LaDl2RMksRkvgFdEKHgbN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKLFxJ%2Fbtq7PLWBwmL%2FLaDl2RMksRkvgFdEKHgbN0%2Fimg.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;363&quot; data-filename=&quot;draggable_error2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-draggable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-draggable&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624327266261&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;react-draggable&quot; data-og-description=&quot;React draggable component&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/react-draggable&quot; data-og-url=&quot;https://www.npmjs.com/package/react-draggable&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9hVr4/hyKD8SVnBy/n9Znsz7JnguyasGc9Yd9zK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-draggable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/react-draggable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9hVr4/hyKD8SVnBy/n9Znsz7JnguyasGc9Yd9zK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;react-draggable&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;React draggable component&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.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;pre id=&quot;code_1624327405231&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useRef } from 'react';

const nodeRef = useRef(null);

&amp;lt;Draggable nodeRef={nodeRef}&amp;gt;
  &amp;lt;div ref={nodeRef} className=&quot;box&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/Draggable&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 useRef를 사용하면 깔끔하게 해결된다!&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /Error</category>
      <category>findDOMNode is deprecated in StrictMode</category>
      <category>react-draggable error</category>
      <category>react-draggable strictmode</category>
      <category>strictmode error</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/36</guid>
      <comments>https://wazacs.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 22 Jun 2021 11:05:21 +0900</pubDate>
    </item>
    <item>
      <title>리액트에서 setInterval을 사용하여 타이핑 효과 주기</title>
      <link>https://wazacs.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;리액트에서 타이핑 효과를 주고 싶어 알아 보았더니 react-typing-effect과 같은 여러 라이브러리가 있었는데,&amp;nbsp;&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;저는 좀 더 인공지능 스피커가 말하는 느낌을 주고 싶기도 했고,&amp;nbsp;&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;구현하기 어렵지 않을 것 같아서 만들어 보았습니다.&amp;nbsp;&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;기능&lt;/b&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;1. 뿌릴 텍스트 준비&amp;nbsp;&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;2. 뿌릴 텍스트 (state)&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;3. 텍스트 길이 체크 (state)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623998426400&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'

const TypingText = () =&amp;gt; {
  const txt = &quot;오늘은 뭐 먹지?&quot;;
  const [Text, setText] = useState('');
  const [Count, setCount] = useState(0);
  
  return (
  	&amp;lt;p className=&quot;text&quot; &amp;gt;{ Text }&amp;lt;/p&amp;gt;
  )
}

export default TypingText
&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;render 되자 마자 Text state 안에 text 의 문자열을 하나씩 setState 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 길이는 Text.length로 비교해도 상관 없지만, 저는 Count state를 따로 두었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1624000349838&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    const interval = setInterval(() =&amp;gt; {
        setText(Text + txt[Count]); // 이전 set한 문자 + 다음 문자
        setCount(Count + 1); // 개수 만큼 체크 
    }, 100);
    if(Count === txt.length)  {  // Count를 따로 두지 않고 Text.length 체크도 가능
        clearInterval(interval); // 문자열 체크를 통해 setInterval을 해제합니다
    }
    return () =&amp;gt; clearInterval(interval); // 언마운트시 setInterval을 해제합니다
})&lt;/code&gt;&lt;/pre&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;195&quot; data-filename=&quot;typing.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tuCV1/btq7DS18lai/YlP4t4nJc1ODwkwKCp3qq1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tuCV1/btq7DS18lai/YlP4t4nJc1ODwkwKCp3qq1/img.gif&quot; data-alt=&quot;적용된 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tuCV1/btq7DS18lai/YlP4t4nJc1ODwkwKCp3qq1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/tuCV1/btq7DS18lai/YlP4t4nJc1ODwkwKCp3qq1/img.gif&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;195&quot; data-filename=&quot;typing.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;clear 하는 시점이 글자를 모두 출력한 시점이므로,&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;pre id=&quot;code_1624078968107&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ ... }
const [Color, setColor] = useState('black');

const handleAfterAction = () =&amp;gt; {
  setColor('orange');
}


useEffect(() =&amp;gt; {
  { ... }
  if (Text.length === txt.length) {
  	clearInterval(interval);
  	handleAfterAction(); // 글자가 모두 출력된 후 handleAfterAction 함수를 호출합니다.
  }
  return () =&amp;gt; clearInterval(interval);
});

return (
  &amp;lt;div className=&quot;App&quot;&amp;gt;
    &amp;lt;p className=&quot;text&quot; style={{color: Color}}&amp;gt;{Text}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;195&quot; data-filename=&quot;typing2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfKjXM/btq7GA0KGgY/7yhYdSh7h7puZiECnKIKqk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfKjXM/btq7GA0KGgY/7yhYdSh7h7puZiECnKIKqk/img.gif&quot; data-alt=&quot;적용된 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfKjXM/btq7GA0KGgY/7yhYdSh7h7puZiECnKIKqk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cfKjXM/btq7GA0KGgY/7yhYdSh7h7puZiECnKIKqk/img.gif&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;195&quot; data-filename=&quot;typing2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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>개발  /ReactJS</category>
      <category>react setInterval</category>
      <category>react typing effect</category>
      <category>react 간단 타이핑</category>
      <category>react 간단한 효과</category>
      <category>react 타이핑</category>
      <category>react 타이핑 효과</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/35</guid>
      <comments>https://wazacs.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 18 Jun 2021 16:12:37 +0900</pubDate>
    </item>
    <item>
      <title>select 태그 커스텀 하기, 혹은 직접 만들기 (feat. javascript)</title>
      <link>https://wazacs.tistory.com/34</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Select CSS Customizing&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Select 태그의 외형은 주로 각 브라우저에서 정의 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-origin-width=&quot;171&quot; data-origin-height=&quot;53&quot; data-filename=&quot;제목 없음.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cElk76/btq7AbAkZfc/izRRY33BHKACU5KsPxfzxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cElk76/btq7AbAkZfc/izRRY33BHKACU5KsPxfzxK/img.png&quot; data-alt=&quot;Chrome 과 IE&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cElk76/btq7AbAkZfc/izRRY33BHKACU5KsPxfzxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcElk76%2Fbtq7AbAkZfc%2FizRRY33BHKACU5KsPxfzxK%2Fimg.png&quot; data-origin-width=&quot;171&quot; data-origin-height=&quot;53&quot; data-filename=&quot;제목 없음.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;Chrome 과 IE&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다 시피 크롬(왼쪽), IE(오른쪽) 이 똑같이 보이면서도 미세 하게 다름니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로젝트 별로 스타일에 맞게 css를 통해 외형을 바꿀 수 있습니다.&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;약간의 편법(?)을 통하여 외형을 변경 할 수 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. Select 기본 화살표 없애기&lt;/h3&gt;
&lt;pre id=&quot;code_1623990300011&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;select name=&quot;fruits&quot; class=&quot;select&quot;&amp;gt;
  &amp;lt;option disabled selected&amp;gt;fruits  &amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;apple&quot;&amp;gt;apple&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;orange&quot;&amp;gt;orange&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;grape&quot;&amp;gt;grape&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;melon&quot;&amp;gt;melon&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623990324708&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* IE */
select::-ms-expand { 
	display: none;
}
.select {
  -o-appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}&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;appearance none을 통해 화살표를 없앨 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, IE는 select::expand 를 없애야 사라집니다.&amp;nbsp;&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;1-2. select에 background 이미지 넣기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 select 자체에 백그라운드 이미지를 넣어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623990657542&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* IE */
{ ... }
.select {
  { ... }
  width: 150px;
  height: 35px;
  background: url('https://freepikpsd.com/media/2019/10/down-arrow-icon-png-7-Transparent-Images.png') calc(100% - 5px) center no-repeat;
  background-size: 20px;
  padding: 5px 30px 5px 10px;
  border-radius: 4px;
  outline: 0 none;
}
.select option {
  background: black;
  color: #fff;
  padding: 3px 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 소스를 보면 option 에도 background와 color도 커스텀이 가능한 것을 알 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;185&quot; data-filename=&quot;sel1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edVoca/btq7CPwKwma/RJBnikt0KFqBYJxKyk9NJ1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edVoca/btq7CPwKwma/RJBnikt0KFqBYJxKyk9NJ1/img.gif&quot; data-alt=&quot;select background 커스텀&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edVoca/btq7CPwKwma/RJBnikt0KFqBYJxKyk9NJ1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/edVoca/btq7CPwKwma/RJBnikt0KFqBYJxKyk9NJ1/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;185&quot; data-filename=&quot;sel1.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;select background 커스텀&lt;/figcaption&gt;
&lt;/figure&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;1-3. select 와 이미지를 분리하기&amp;nbsp;&lt;/h3&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;예를 들어, select에 focus시 아이콘을 회전 시킬 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 select를 한번 감싸고 이미지를 추가해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623992472526&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;selectBox&quot;&amp;gt;
  &amp;lt;select name=&quot;fruits&quot; class=&quot;select&quot;&amp;gt;
    &amp;lt;option disabled selected&amp;gt;fruits  &amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;apple&quot;&amp;gt;apple&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;orange&quot;&amp;gt;orange&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;grape&quot;&amp;gt;grape&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;melon&quot;&amp;gt;melon&amp;lt;/option&amp;gt;
  &amp;lt;/select&amp;gt;
  &amp;lt;span class=&quot;icoArrow&quot;&amp;gt;&amp;lt;img src=&quot;https://freepikpsd.com/media/2019/10/down-arrow-icon-png-7-Transparent-Images.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623992610524&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.selectBox {
  position: relative;
  width: 150px;
  height: 35px;
  border-radius: 4px;
  border: 2px solid lightcoral;
}
.selectBox .select {
  width: inherit;
  height: inherit;
  background: transparent;
  border: 0 none;
  outline: 0 none;
  padding: 0 5px;
  position: relative;
  z-index: 3; // select가 위로 올라와야 함
}
.selectBox .select option {
  background: lightcoral;
  color: #fff;
  padding: 3px 0;
  font-size: 16px;
}
.selectBox .icoArrow {
  position: absolute; 
  top: 0; 
  right: 0; 
  z-index: 1; 
  width: 35px; 
  height: inherit;
  border-left: 2px solid lightcoral;
  display: flex;
  justify-content: center;
  align-items: center;
}

.selectBox .icoArrow img {
  width: 50%;
  transition: .3s; // 부드럽게 회전
}

.selectBox .select:focus + .icoArrow img {
  transform: rotate(180deg);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;185&quot; data-filename=&quot;sel2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MV7MU/btq7COdBKF5/IvfFHNkK3d1jmshkzjf0tk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MV7MU/btq7COdBKF5/IvfFHNkK3d1jmshkzjf0tk/img.gif&quot; data-alt=&quot;화살표 아이콘 분리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MV7MU/btq7COdBKF5/IvfFHNkK3d1jmshkzjf0tk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/MV7MU/btq7COdBKF5/IvfFHNkK3d1jmshkzjf0tk/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;185&quot; data-filename=&quot;sel2.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;이 외에도 다양한 방법이 있겠지만 더 많은 조작과 제한이 있는 option의 커스터마이징을 더 자유롭게 하고 싶다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;select 태그를 사용하지 않고 직접 만드는 것도 방법입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css부터 script 까지 직접 만들어야 하는 번거로움도 있습니다.&amp;nbsp;&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;2. Select 직접 구축&lt;/h2&gt;
&lt;pre id=&quot;code_1623993264659&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;selectBox2 &quot;&amp;gt;
  &amp;lt;button class=&quot;label&quot;&amp;gt;fruits  &amp;lt;/button&amp;gt;
  &amp;lt;ul class=&quot;optionList&quot;&amp;gt;
    &amp;lt;li class=&quot;optionItem&quot;&amp;gt;apple&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;optionItem&quot;&amp;gt;orange&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;optionItem&quot;&amp;gt;grape&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;optionItem&quot;&amp;gt;melon&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623993356820&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.selectBox2 * { box-sizing: border-box; }
.selectBox2 {
  position: relative;
  width: 150px;
  height: 35px;
  border-radius: 4px;
  border: 2px solid lightcoral;
  background: url('https://freepikpsd.com/media/2019/10/down-arrow-icon-png-7-Transparent-Images.png') calc(100% - 7px) center no-repeat;
  background-size: 20px;
  cursor: pointer;
}

.selectBox2:after {
  content: '';
  display: block; 
  width: 2px;
  height: 100%; 
  position: absolute; 
  top: 0; 
  right: 35px;
  background: lightcoral;
}

.selectBox2 .label {
  display: flex;
  align-items: center;
  width: inherit;
  height: inherit;
  border: 0 none;
  outline: 0 none;
  padding-left: 15px;
  background: transparent;
  cursor: pointer;
}

.selectBox2 .optionList {
  position: absolute; 
  top: 28px;
  left: 0;
  width: 100%;
  background: lightcoral;
  color: #fff;
  list-style-type: none;
  padding: 0;
  border-radius: 6px;
  overflow: hidden;
  max-height: 0;
  transition: .3s ease-in;
}

.selectBox2.active .optionList {
  max-height: 500px;
}

.selectBox2 .optionItem {
  border-bottom: 1px dashed rgb(170, 72, 72);
  padding: 5px 15px 5px;
  transition: .1s;
}

.selectBox2 .optionItem:hover {
  background: rgb(175, 93, 93);
}

.selectBox2 .optionItem:last-child {
  border-bottom: 0 none;
}

// 스크롤 커스텀
.selectBox2 .optionList::-webkit-scrollbar {width: 6px;}
.selectBox2 .optionList::-webkit-scrollbar-track {background: transparent; }
.selectBox2 .optionList::-webkit-scrollbar-thumb {background: #303030; border-radius: 45px;}
.selectBox2 .optionList::-webkit-scrollbar-thumb:hover {background: #303030;}
&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;이렇게 하면 앞서 커스텀한 select 태그와 외형이 거의 같아집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 select 처럼 사용하기 위해 기능을 추가하는 일만 남았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 기능&amp;nbsp;&lt;/b&gt;&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;2. 한번 더 클릭시 옵션 목록이 닫힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 옵션 클릭시 옵션의 텍스트가 select 안으로 들어가면서 옵션 목록이 닫힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623993616741&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 화살표 함수 */
const label = document.querySelector('.label');
const options = document.querySelectorAll('.optionItem');

// 클릭한 옵션의 텍스트를 라벨 안에 넣음
const handleSelect = (item) =&amp;gt; {
  label.parentNode.classList.remove('active');
  label.innerHTML = item.textContent;
}
// 옵션 클릭시 클릭한 옵션을 넘김
options.forEach(option =&amp;gt; {
	option.addEventListener('click', () =&amp;gt; handleSelect(option))
})

// 라벨을 클릭시 옵션 목록이 열림/닫힘
label.addEventListener('click', () =&amp;gt; {
  if(label.parentNode.classList.contains('active')) {
  	label.parentNode.classList.remove('active');
  } else {
  	label.parentNode.classList.add('active');
  }
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623993675765&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 일반함수 */
const label = document.querySelector('.label');
const options = document.querySelectorAll('.optionItem');
// 클릭한 옵션의 텍스트를 라벨 안에 넣음
const handleSelect = function(item) {
  label.innerHTML = item.textContent;
  label.parentNode.classList.remove('active');
}
// 옵션 클릭시 클릭한 옵션을 넘김
options.forEach(function(option){
  option.addEventListener('click', function(){handleSelect(option)})
})
// 라벨을 클릭시 옵션 목록이 열림/닫힘
label.addEventListener('click', function(){
  if(label.parentNode.classList.contains('active')) {
    label.parentNode.classList.remove('active');
  } else {
    label.parentNode.classList.add('active');
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;185&quot; data-filename=&quot;sel3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YmHR1/btq7yWYk9id/KQBKkO6Kq0Rc0xnm59cDT1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YmHR1/btq7yWYk9id/KQBKkO6Kq0Rc0xnm59cDT1/img.gif&quot; data-alt=&quot;div로 만든 select&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YmHR1/btq7yWYk9id/KQBKkO6Kq0Rc0xnm59cDT1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/YmHR1/btq7yWYk9id/KQBKkO6Kq0Rc0xnm59cDT1/img.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;185&quot; data-filename=&quot;sel3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;div로 만든 select&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀된 셀렉트 박스가 개수를 알 수 없는 여러개인 경우 for문으로 각각 하위의 값을 찾아서 따로 기능을&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 단일인 경우&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/2mn4dso3/41/embedded/js,html,css,result/dark/&quot; width=&quot;100%&quot; height=&quot;300&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&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;* 멀티인 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;//jsfiddle.net/cling88/q8f7yp9h/4/embedded/js,html,css,result/dark/&quot; width=&quot;100%&quot; height=&quot;300&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&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;</description>
      <category>개발  /HTML,CSS</category>
      <category>select css</category>
      <category>select custom</category>
      <category>select option css</category>
      <category>select remove arrow</category>
      <category>select 기능</category>
      <category>select 디자인</category>
      <category>select 만들기</category>
      <category>select 스타일</category>
      <category>select 화살표</category>
      <category>셀렉트 css</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/34</guid>
      <comments>https://wazacs.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 18 Jun 2021 14:27:12 +0900</pubDate>
    </item>
    <item>
      <title>input  search x 버튼 삭제 및 클리어 버튼 직접 만들어 넣기</title>
      <link>https://wazacs.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;input의 타입을 search로 지정하면 브라우저 내에서 input 초기화 버튼을 넣어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;56&quot; data-filename=&quot;111111.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy8tWA/btq7myW43nW/8N7UZei1LUeSmKl0cEYfck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy8tWA/btq7myW43nW/8N7UZei1LUeSmKl0cEYfck/img.png&quot; data-alt=&quot;예시 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy8tWA/btq7myW43nW/8N7UZei1LUeSmKl0cEYfck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy8tWA%2Fbtq7myW43nW%2F8N7UZei1LUeSmKl0cEYfck%2Fimg.png&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;56&quot; data-filename=&quot;111111.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 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;약간은 번거로운 작업일 수 있지만 생각보다 간단합니다.&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;우선 브라우저 내에서 생성한 초기화 버튼을 없애려면 단순히 input의 타입을 text로 지정하거나 css에서 해결해 줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623742248993&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* IE의 경우 */
input::-ms-clear,
input::-ms-reveal{
    display:none;
}
/* 크롬의 경우 */
input::-webkit-search-decoration,
input::-webkit-search-cancel-button,
input::-webkit-search-results-button,
input::-webkit-search-results-decoration{
    display:none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 테스트 해보니 IE10까지만 먹습니다. )&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;그리고 div로 한번 감싸고 input옆에 버튼을 추가해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 absolute로 붙인 뒤 버튼의 크기만큼 input에 padding을 줘야 input에 글씨를 많이 넣어도 가려지지 않아요.&lt;/p&gt;
&lt;pre id=&quot;code_1623742396585&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// html
&amp;lt;div class=&quot;inputWrap&quot;&amp;gt;
    &amp;lt;input type=&quot;search&quot; placeholder=&quot;검색어를 입력하세요&quot; /&amp;gt;
    &amp;lt;button class=&quot;btnClear&quot;&amp;gt;&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623742439573&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// css
.inputWrap {
    position: relative;
    height: 30px;
    display: inline-block;
 }
  
.inputWrap input {
  padding-right: 30px;
  height: inherit;
}

.inputWrap .btnClear {
  position: absolute;
  top: 0;
  right: 0;
  width: 30px;
  height: inherit;
  background: url(https://img.icons8.com/pastel-glyph/2x/cancel.png) center center no-repeat;
  background-size: 50%;
  border: none;
  outline: none;
  cursor: pointer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 input의 value를 초기화시켜줄 스크립트를 짜줍니다.&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;이렇게 하면 형제를 찾는 것보다 이점은 구조의 순서(input과 button)가 바뀌어도 상관이 없고, 복수개를 넣어도 에러가 발생할 일이 없기 때문입니다.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. addEventListener&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1623742591684&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// javascript
// 한개일때
&amp;lt;script&amp;gt;
    var btnClear = document.querySelector('.btnClear');
    btnClear.addEventListener('click', function(){
        btnClear.parentNode.querySelector('input').value = &quot;&quot;;
    })
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623742945413&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// javascript
// 여러개일때 
&amp;lt;script&amp;gt;
    var btnClear = document.querySelectorAll('.btnClear');
    btnClear.forEach(function(btn){
        btn.addEventListener('click', function(){
            btn.parentNode.querySelector('input').value = &quot;&quot;;
        })
    })
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.&lt;span&gt; onClick&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1623743184632&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;inputWrap&quot;&amp;gt;
    &amp;lt;input type=&quot;search&quot; placeholder=&quot;나이를 입력하세요&quot; /&amp;gt;
    &amp;lt;button class=&quot;btnClear&quot; onClick=&quot;clearInput(this)&quot;&amp;gt;&amp;lt;/button&amp;gt; &amp;lt;!-- onClick 추가 --&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623743210693&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    var clearInput = function(obj) {
        obj.parentNode.querySelector('input').value = &quot;&quot;
    }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>input cancel button</category>
      <category>input cancel custom</category>
      <category>input clear</category>
      <category>input search clear</category>
      <category>input search x</category>
      <category>input x button</category>
      <category>input x 만들기</category>
      <category>input 삭제 버튼</category>
      <category>input 초기화</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/33</guid>
      <comments>https://wazacs.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 15 Jun 2021 16:50:16 +0900</pubDate>
    </item>
    <item>
      <title>React 에서 select 사용하기</title>
      <link>https://wazacs.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;셀렉트를 사용하기 위해 다음과 같이 jsx 구문을 만들었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623582437833&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;select className=&quot;w150&quot; onChange={handleChangeSelect}&amp;gt;
  &amp;lt;option value=&quot;1&quot; &amp;gt;0.1톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;2&quot;&amp;gt;0.2톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;3&quot;&amp;gt;0.3톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;4&quot; selected={true}&amp;gt;0.4톤&amp;lt;/option&amp;gt; {/* 선택하려던 부분! */}
  &amp;lt;option value=&quot;5&quot;&amp;gt;0.5톤&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;&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;기존 html 에서 디폴트 옵션을 선택하려면 option 태그안에 selected 속성을 주어 동작 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 리액트에서 이와 같이 사용하려고 한다면, 다음과 같은 에러를 뱉어냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;853&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;207&quot; height=&quot;203&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pf3kM/btq7fbUutK5/3YlGBFJm7Qb4XI6lxKl4f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pf3kM/btq7fbUutK5/3YlGBFJm7Qb4XI6lxKl4f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pf3kM/btq7fbUutK5/3YlGBFJm7Qb4XI6lxKl4f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpf3kM%2Fbtq7fbUutK5%2F3YlGBFJm7Qb4XI6lxKl4f0%2Fimg.png&quot; width=&quot;853&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;207&quot; height=&quot;203&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/figure&gt;
&lt;/p&gt;
&lt;p id=&quot;SE-f0735de6-1216-4b0f-98c2-5dae00916b8a&quot; data-ke-size=&quot;size16&quot;&gt;React에서는 selected 대신 value를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 selected의 선택된 value 값을 가져오려면, onChange를 사용해야합니다.&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;pre id=&quot;code_1623582743387&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;select className=&quot;w150&quot; onChange={handleChangeSelect} value={'4'}&amp;gt;
  &amp;lt;option value=&quot;1&quot; &amp;gt;0.1톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;2&quot;&amp;gt;0.2톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;3&quot;&amp;gt;0.3톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;4&quot;&amp;gt;0.4톤&amp;lt;/option&amp;gt;
  &amp;lt;option value=&quot;5&quot;&amp;gt;0.5톤&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;&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;/p&gt;
&lt;pre id=&quot;code_1623583483742&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function App() {
  const selectList = [&quot;apple&quot;, &quot;banana&quot;, &quot;grape&quot;, &quot;orange&quot;];
  const [Selected, setSelected] = useState(&quot;&quot;);

  const handleSelect = (e) =&amp;gt; {
    setSelected(e.target.value);
  };

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;h1&amp;gt;Select in React&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;select onChange={handleSelect} value={Selected}&amp;gt;
          {selectList.map((item) =&amp;gt; (
            &amp;lt;option value={item} key={item}&amp;gt;
              {item}
            &amp;lt;/option&amp;gt;
          ))}
        &amp;lt;/select&amp;gt;
        &amp;lt;hr /&amp;gt;
        &amp;lt;p&amp;gt;
          Selected: &amp;lt;b&amp;gt;{Selected}&amp;lt;/b&amp;gt;
        &amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&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;Selected 는 선택된 option이 들어갑니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onChange는 선택된 option의 value 값을 e.target.value 로 받은 후, Selected 에 setState 시켜줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 처음부터 grape가 선택 된 채로 설정 하고 싶다면 Selected 부분에 default 값을 넣어주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623583627197&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [Selected, setSelected] = useState(&quot;grape&quot;);&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react defaultValue error</category>
      <category>react select</category>
      <category>react selected</category>
      <category>리액트 셀렉트</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/31</guid>
      <comments>https://wazacs.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 14 Jun 2021 10:30:29 +0900</pubDate>
    </item>
    <item>
      <title>가운데서 좌우로 펼쳐지게 하기</title>
      <link>https://wazacs.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴를 hover 했을 때 아래에서 underline이 가운데부터 열리는 모션을 구현해 보고 싶어서 만들어봤습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;195&quot; data-filename=&quot;hover_effect.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/di8mt4/btq7fbUvGCQ/FMsfnBCYMkJto1ZFfwkXyk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/di8mt4/btq7fbUvGCQ/FMsfnBCYMkJto1ZFfwkXyk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/di8mt4/btq7fbUvGCQ/FMsfnBCYMkJto1ZFfwkXyk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/di8mt4/btq7fbUvGCQ/FMsfnBCYMkJto1ZFfwkXyk/img.gif&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;195&quot; data-filename=&quot;hover_effect.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 모션입니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 모션은 border 나 text-decoration 으로는 구현할 수없고, a 태그의 :after를 사용하여 만들었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623584715690&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;wrap&quot;&amp;gt;
	&amp;lt;a href=&quot;#&quot;&amp;gt;HOME&amp;lt;/a&amp;gt;
	&amp;lt;a href=&quot;#&quot;&amp;gt;ABOUT&amp;lt;/a&amp;gt;
	&amp;lt;a href=&quot;#&quot;&amp;gt;Q&amp;amp;A&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 마크업은 간단합니다. 어차피 :after 를 사용할 꺼라 별다른 태그가 추가로 필요하진 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 a:after 에 absolute 속성을 주어 a 보다 아래에 위치시켜줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 transform의 scale을 사용 하는게 핵심입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scale 속성은 현재의 element를 확대 시켜주는 기능을 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623584963003&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.wrap {
	padding: 15px;
}
.wrap &amp;gt; a {
	position: relative;
	display:inline-block; 
	color: #000;
	text-decoration: none; 
	font-weight: bold;
	margin-right: 15px;
}

.wrap &amp;gt; a:after {
	content: &quot;&quot;;
	position: absolute;
	width: 100%;
	height: 8px;
	bottom: -6px;
	left: 0;
	background-color: #dc6b6b;
	visibility: hidden;
	transform: scaleX(0);
	transition: all 0.3s ease-in-out;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 보면 a:after 의 width가 100%지만, scaleX의 값이 0 이므로 보이지 않습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 a를 hover 했을때 좌우로 늘려주려면, 원래 사이즈인 100%로 돌려주면 되기 때문에 scale 값을 1로 해주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 원래 100%의 사이즈로 돌아옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623585058228&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.wrap &amp;gt; a:hover {
	color: #dc6b6b;
}

.wrap &amp;gt; a:hover:after {
	visibility: visible;
	transform: scaleX(1);
}&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;이렇게 하면 끝입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 모션은 underline 처럼 사용하는 것 외에도 다양하게 사용이 가능해 보입니다.&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;참고로 scale 은 확인해 보니 IE9 이하는 동작이 안되더라고요. 참고하세요!&lt;/p&gt;</description>
      <category>개발  /HTML,CSS</category>
      <category>a hover</category>
      <category>css scale</category>
      <category>css 가운데서</category>
      <category>css 가운데서 열리기</category>
      <category>css 가운데서 좌우로</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/32</guid>
      <comments>https://wazacs.tistory.com/32#entry32comment</comments>
      <pubDate>Sun, 13 Jun 2021 20:56:03 +0900</pubDate>
    </item>
    <item>
      <title>React div 스크롤시 Fix 하기 (Element scroll)</title>
      <link>https://wazacs.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 window에서 스크롤할 때 특정 엘레멘트가 고정이 되는 기능을 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 window가 아닌 특정 div 에서 스크롤했을 때 div안의 다른 객체가 고정이 되도록 구현해 보겠습니다.&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;사실 구현 방식은 똑같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단지 window 와는 달리 객체의 scroll 값을 구하기 위해서는 useRef를 사용해야 합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에서 고정을 시키려면 fixed 와는 달리 absolute를 사용해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위치의 기준이 viewport 가 되는 fixed와 다르게 absolute는 상위 객체들 중 relative가 되는 값을 참조하여 위치가 결정됩니다.&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;그래서 제가 생각한 방법은 2가지 입니다. 거의 같으면서도 활용성이 조금 다를 것 같아서 두 가지 방식을 모두 적용해 보도록 하겠습니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기존 처럼 클래스 하나를 붙여 위치를 컨트롤한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. scroll값을 가져와 인라인 스타일로 실시간 반영한다. (다른 css 부분을 컨트롤하기 위해 어차피 클래스를 붙이긴 할 것이다.)&lt;/p&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; background: url('../image/divider-line.svg') center -208px / 200px 420px repeat-x; height: 2px; padding: 21px 0px;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 클래스 하나로만 컨트롤 하기&lt;/b&gt;&lt;/h3&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;box는 위치의 기준이 될 부모이고, 실제 스크롤이 되는 녀석은 boxInner입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 boxInner의 스크롤 값을 불러와 30 이상이 된다면 ScrollActive의 상태 값이 바뀌고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScrollActive 상태값이 바뀌면 smallBox에게 추가 클래스가 붙습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623399644189&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.js
&amp;lt;div className=&quot;box&quot;  &amp;gt;
    &amp;lt;div className={ScrollActive ? &quot;smallBox fixed&quot; : &quot;smallBox&quot;}&amp;gt;
        {
        ScrollActive ? 
            'I am fixed! ✨' : 
            'I will be fixed!  '
        }
    &amp;lt;/div&amp;gt;
    &amp;lt;div className=&quot;boxInner&quot;&amp;gt;
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestiae impedit ducimus perferendis, fuga nobis nihil eius similique. Laboriosam fuga doloribus quibusdam cumque beatae! Quae omnis, explicabo possimus molestias nam tempore!
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestiae impedit ducimus perferendis, fuga nobis nihil eius similique. Laboriosam fuga doloribus quibusdam cumque beatae! Quae omnis, explicabo possimus molestias nam tempore!
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestiae impedit ducimus perferendis, fuga nobis nihil eius similique. Laboriosam fuga doloribus quibusdam cumque beatae! Quae omnis, explicabo possimus molestias nam tempore!
        { ... }
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623399644189&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.css
.box {
  position: relative; 
  top: 30px; 
  right: 30px; 
  width: 400px; 
  height: 400px;
  background: black;
  color: white; 
  overflow: hidden;
  margin-left: 100px;
}

.box .boxInner {
  width: 100%; 
  height: 100%; 
  overflow: hidden;
  overflow-y: auto;
  padding: 10px; 
}

.smallBox {
  position: absolute; 
  top: 50px; 
  right: 40px;
  z-index: 1;
  padding: 10px; 
  background: rgb(236, 97, 218); 
  border-radius: 6px;
  color: #fff;
  border: 1px solid rgb(183, 81, 187);
  text-align: left;
}

.smallBox.fixed {
  width: 100%; 
  top: 0;
  right: 0; 
  border-radius: 0;
  text-align: center;
}&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;SPA형태인 리액트 안에서 하나의 특정 엘리먼트를 선택하려면 useRef을 사용하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref가 걸린 엘리먼트의 속성을 확인할 수도 있고, 내장 함수를 바로 사용할 수 있습니다.&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;예를 들어 boxRef를 div에 연결한 뒤 콘솔로 찍어보면 해당 엘리먼트의 속성 값들을 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;461&quot; data-origin-height=&quot;351&quot; data-filename=&quot;이미지 22.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tuYTN/btq616m6Bkj/Y6o0GWEPOVJnBArc0rTtA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tuYTN/btq616m6Bkj/Y6o0GWEPOVJnBArc0rTtA1/img.png&quot; data-alt=&quot;ref 정보&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tuYTN/btq616m6Bkj/Y6o0GWEPOVJnBArc0rTtA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtuYTN%2Fbtq616m6Bkj%2FY6o0GWEPOVJnBArc0rTtA1%2Fimg.png&quot; data-origin-width=&quot;461&quot; data-origin-height=&quot;351&quot; data-filename=&quot;이미지 22.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;ref 정보&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1623400231019&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useEffect, useRef } from 'react'

const boxRef = useRef(null);

&amp;lt;div className=&quot;box&quot;&amp;gt;
    { ... }
    &amp;lt;div className=&quot;boxInner&quot; ref={boxRef}&amp;gt; {/* boxRef 설정하기 */}
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestiae impedit ducimus perferendis, fuga nobis nihil eius similique. Laboriosam fuga doloribus quibusdam cumque beatae! Quae omnis, explicabo possimus molestias nam tempore!
        { ... }
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&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;스크롤이 생성되는 div에 boxRef값을 적용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 boxRef의 스크롤 값을 실시간으로 감시( ) 하여 값에 따라 컨트롤을 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 ScrollActive를 하나 생성하여 컨트롤을 하겠습니다.&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;- scroll 값을 가져오기 : boxRef.current.scrollTop&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- scroll 행위 여부를 판단하기 : addEventListener&amp;nbsp;&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_1623400387176&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [ScrollY, setScrollY] = useState(0)
    const [ScrollActive, setScrollActive] = useState(false);
    function logit() {
        setScrollY(boxRef.current.scrollTop)
        if(boxRef.current.scrollTop &amp;gt; 30) {  
            setScrollActive(true);
        } else {
            setScrollActive(false);
        }
    }
    useEffect(() =&amp;gt; {
        function watchScroll() {  boxRef.current.addEventListener(&quot;scroll&quot;, logit); }
        watchScroll();
        return () =&amp;gt; { boxRef.current.removeEventListener(&quot;scroll&quot;, logit); }; 
    })
    &amp;lt;div className=&quot;box&quot;&amp;gt;
                &amp;lt;div className={ScrollActive ? &quot;smallBox fixed&quot; : &quot;smallBox&quot;}&amp;gt;
                    {
                    ScrollActive ? 
                        'I am fixed! ✨' : 
                        'I will be fixed!  '
                    }
                &amp;lt;/div&amp;gt;
                &amp;lt;div className=&quot;boxInner&quot; ref={boxRef}&amp;gt; {/* boxRef 설정하기 */}
                    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestiae impedit ducimus perferendis, fuga nobis nihil eius similique. Laboriosam fuga doloribus quibusdam cumque beatae! Quae omnis, explicabo possimus molestias nam tempore!
                    { ... }
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;전체 소스&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/react-element-scroll-9jjw0?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;500&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>div scroll scrolltop</category>
      <category>react div offsetY</category>
      <category>react div scroll</category>
      <category>react div scrollTop</category>
      <category>react scroll</category>
      <category>리액트 div 스크롤</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/30</guid>
      <comments>https://wazacs.tistory.com/30#entry30comment</comments>
      <pubDate>Fri, 11 Jun 2021 17:42:49 +0900</pubDate>
    </item>
    <item>
      <title>React 스크롤시 Fix 하기 (Window scroll)</title>
      <link>https://wazacs.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤바를 어느 값만큼 내리면 특정 엘레먼트의 css 속성이 변경되어야 하는 경우가 종종 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wazacs.tistory.com/30?category=455192&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Window가 아닌 div 스크롤인 경우&amp;nbsp;&lt;/a&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;- App.js&lt;/p&gt;
&lt;pre id=&quot;code_1623129902707&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;div className=&quot;fixedBox&quot;&amp;gt;'I will be fixed!  '&amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;content&quot; style={{fontSize: '22px'}}&amp;gt;
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi at nihil sit fugit adipisci aperiam alias enim sed voluptates quo! Quam inventore officia commodi laudantium amet in sunt est quasi. &amp;lt;br /&amp;gt;
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi at nihil sit fugit adipisci aperiam alias enim sed voluptates quo! Quam inventore officia commodi laudantium amet in sunt est quasi. &amp;lt;br /&amp;gt;
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi at nihil sit fugit adipisci aperiam alias enim sed voluptates quo! Quam inventore officia commodi laudantium amet in sunt est quasi. &amp;lt;br /&amp;gt;
        ... // 스크롤이 생길때까지 복붙
      &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- App.css&lt;/p&gt;
&lt;pre id=&quot;code_1623129952113&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.fixedBox {
  position: fixed; 
  top: 50px; 
  right: 20px;
  z-index: 1;
  padding: 10px; 
  background: lightseagreen; 
  border-radius: 6px;
  color: #fff;
  border: 1px solid rgb(23, 122, 117);
  text-align: left;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;515&quot; data-filename=&quot;이미지 2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMyDPn/btq6QPEnTRp/YbZP1vKKbAjnlnrad1VPZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMyDPn/btq6QPEnTRp/YbZP1vKKbAjnlnrad1VPZ0/img.png&quot; data-alt=&quot;마크업이 완료된 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMyDPn/btq6QPEnTRp/YbZP1vKKbAjnlnrad1VPZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMyDPn%2Fbtq6QPEnTRp%2FYbZP1vKKbAjnlnrad1VPZ0%2Fimg.png&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;515&quot; data-filename=&quot;이미지 2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-ke-size=&quot;size16&quot;&gt;이 상태에서 300정도 스크롤을 했을때 'I will be fixed!' 라는 문구를 'I am fixed!' 로 바꾸고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상단에 고정하기 위해 top: 0 를 주도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 css를 컨트롤 하지 않고 &lt;b&gt;fixed라는 클래스를 가지고&lt;/b&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;- App.css&lt;/p&gt;
&lt;pre id=&quot;code_1623130218529&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{  ...  }

.fixedBox.fixed {
  width: 100%; 
  top: 0; 
  right: 0;
  font-size: 22px;
  border-radius: 0;
  text-align: center;
}&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;- App.js&lt;/p&gt;
&lt;pre id=&quot;code_1623130306627&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    const [ScrollActive, setScrollActive] = useState(false);
    
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;div className={ScrollActive ? &quot;fixedBox fixed&quot; : &quot;fixedBox&quot;}&amp;gt;
        {
          ScrollActive ? 
            'I am fixed!  ' : 
            'I will be fixed!  '
        }
      &amp;lt;/div&amp;gt;
      {/* &amp;lt;ElementScroll/&amp;gt; */}
      &amp;lt;div className=&quot;content&quot; style={{fontSize: '22px'}}&amp;gt;
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi at nihil sit fugit adipisci aperiam alias enim sed voluptates quo! Quam inventore officia commodi laudantium amet in sunt est quasi. &amp;lt;br /&amp;gt;
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi at nihil sit fugit adipisci aperiam alias enim sed voluptates quo! Quam inventore officia commodi laudantium amet in sunt est quasi. &amp;lt;br /&amp;gt;
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi at nihil sit fugit adipisci aperiam alias enim sed voluptates quo! Quam inventore officia commodi laudantium amet in sunt est quasi. &amp;lt;br /&amp;gt;
        { ... }
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScrollActive 라는 state가 true 일때는 fixedBox 에 fixed 클래스를 추가하고 'I am fixed!  ' 라는 문구를 출력 하도록 하고, flase 일때는 fixedBox 클래스에 'I will be fixed!  ' 라는 문구를 출력 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top 에 붙고 width가 변경되는 부분은 모두 .fixedBox.fixed 클래스명을 참조한 css에서 변경할 수 있습니다.&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;그러면 핵심이자 남은 부분은 window 스크롤의 값을 확인하여 300 이상인 경우 ScrollActive 의 값을 true 로 이하인 경우는 다시 false로 변경해 주는 일만 남았습니다.&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;b&gt;- scroll 값을 가져오기 : window.pageYOffset&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- scroll 행위 여부를 판단하기 : addEventListener&amp;nbsp;&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_1623130829424&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [ScrollY, setScrollY] = useState(0); // window 의 pageYOffset값을 저장 
  const [ScrollActive, setScrollActive] = useState(false); 
  function handleScroll() { 
      if(ScrollY &amp;gt; 299) {
          setScrollY(window.pageYOffset);
          setScrollActive(true);
      } else {
          setScrollY(window.pageYOffset);
          setScrollActive(false);
      }
  }
  useEffect(() =&amp;gt; {
      function scrollListener() {  window.addEventListener(&quot;scroll&quot;, handleScroll); } //  window 에서 스크롤을 감시 시작
      scrollListener(); // window 에서 스크롤을 감시
      return () =&amp;gt; { window.removeEventListener(&quot;scroll&quot;, handleScroll); }; //  window 에서 스크롤을 감시를 종료
  });&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;useEffect 로 실시간 감시를 해줘야하는데, 주의할 점은 반드시 이벤트리스너를 삭제해 줘야 스크롤할때 2-3번씩 렌더 되지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;전체 소스&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/strange-mayer-mvbnh?autoresize=1&amp;amp;expanddevtools=1&amp;amp;fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&quot; width=&quot;100%&quot; height=&quot;500&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react addEventListener</category>
      <category>react pageYOffset</category>
      <category>react scroll</category>
      <category>react scroll fix</category>
      <category>react scrollY</category>
      <category>리액트 스크롤</category>
      <category>리액트 스크롤 값</category>
      <category>리액트 스크롤 고정</category>
      <category>리액트 실시간 스크롤</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/28</guid>
      <comments>https://wazacs.tistory.com/28#entry28comment</comments>
      <pubDate>Tue, 8 Jun 2021 14:55:14 +0900</pubDate>
    </item>
    <item>
      <title>체크박스 CSS 커스텀</title>
      <link>https://wazacs.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;체크박스는 대부분 그대로 사용하지 않고 디자인하는 경우가 많습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input 타입을 checkbox로 사용시 브라우저에서 제공되는 체크박스의 형태는 변형하는 것에 한계가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS에서 셀렉터와 label태그를 이용하면 쉽게 커스텀이 가능합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.&amp;nbsp; 마크업&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input의 id와 label의 for 속성 갑을 동일하게 해야 input의 체크 여부가 작동합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622905251399&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;checkboxwrap&quot;&amp;gt;
    &amp;lt;input type=&quot;checkbox&quot; name=&quot;opt&quot; id=&quot;chk&quot; class=&quot;checkbox&quot; &amp;gt;
    &amp;lt;label for=&quot;chk&quot;&amp;gt;&amp;amp;nbsp;&amp;lt;/label&amp;gt;
    &amp;lt;label for=&quot;chk&quot;&amp;gt;체크박스&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 스타일 커스텀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식을 이렇습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기존 checkbox를 숨김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;checkbox + label&lt;/b&gt;을 통해 체크박스 형태를 만듦&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;checkbox:checked + label&lt;/b&gt; 을 사용하여 체크했을 때에 형태를 만듦&lt;/p&gt;
&lt;pre id=&quot;code_1622905369320&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.checkboxwrap .checkbox{display: none!important;}
.checkboxwrap .checkbox + label {display: inline-block;background: url(&quot;체크 전 이미지&quot;) 0 0 no-repeat; background-size: cover; width: 22px; height: 22px; cursor:pointer; margin-right: 9px;}
.checkboxwrap .checkbox:checked + label {background: url(&quot;체크 후 이미지&quot;) 0 0 no-repeat; background-size: cover; }
&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;radio도 똑같은 방식으로 적용할 수 있습니다.&amp;nbsp;&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;1. 마크업&lt;/h3&gt;
&lt;pre id=&quot;code_1622905859850&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;radiowrap&quot; style=&quot;margin-bottom: 12px;&quot;&amp;gt;
    &amp;lt;input type=&quot;radio&quot; name=&quot;options&quot; id=&quot;opt1&quot; class=&quot;radio&quot; checked&amp;gt;
    &amp;lt;label for=&quot;opt1&quot;&amp;gt;&amp;amp;nbsp;&amp;lt;/label&amp;gt;
    &amp;lt;label for=&quot;opt1&quot;&amp;gt;라디오1 checked&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 스타일 커스텀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크시 라벨도 변경해 주고 싶다면 input + label + label 을 통해 선택할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622905904285&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.radiowrap .radio{display: none!important;}
.radiowrap .radio + label {display: inline-block;background: url(&quot;../images/ico/ico_chk2.png&quot;) 0 0 no-repeat; background-size: cover; width: 22px; height: 22px;width: 22px;cursor:pointer; margin-right: 9px;}
.radiowrap .radio:checked + label {background: url(&quot;../images/ico/ico_check2.png&quot;) 0 0 no-repeat; background-size: cover;}
.radiowrap .radio + label + label { color: #9fa1a2; }
.radiowrap .radio:checked + label + label { color: #fff; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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-origin-width=&quot;746&quot; data-origin-height=&quot;296&quot; data-filename=&quot;checkbox_radio_custom.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5NCVe/btq6zWFFFVG/eVJkt0FMkoC9dKEHL0IN4k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5NCVe/btq6zWFFFVG/eVJkt0FMkoC9dKEHL0IN4k/img.gif&quot; data-alt=&quot;적용된 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5NCVe/btq6zWFFFVG/eVJkt0FMkoC9dKEHL0IN4k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/5NCVe/btq6zWFFFVG/eVJkt0FMkoC9dKEHL0IN4k/img.gif&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;296&quot; data-filename=&quot;checkbox_radio_custom.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&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;</description>
      <category>개발  /HTML,CSS</category>
      <category>checkbox css custom</category>
      <category>checkbox 커스텀</category>
      <category>css 다음 태그</category>
      <category>css 형제 태그</category>
      <category>radio css custom</category>
      <category>radio 커스텀</category>
      <category>라디오 디자인 css</category>
      <category>라디오 커스텀</category>
      <category>체크박스 디자인 css</category>
      <category>체크박스 커스텀</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/27</guid>
      <comments>https://wazacs.tistory.com/27#entry27comment</comments>
      <pubDate>Sun, 6 Jun 2021 00:13:43 +0900</pubDate>
    </item>
    <item>
      <title>React에서 가로 스크롤 구현하기 (react-horizontal-scrolling-menu)</title>
      <link>https://wazacs.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 마우스 휠로 가로 스크롤을 사용하기 위해 직접 만들어도 되지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;width값 설정부터 wheel값도 가져와야 하고 많은 공수가 들어갑니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 간단하게 활용할 수 있는 라이브러리가 있어서 공식 사이트의 예제를 가지고 hook버전으로 구현해 보았습니다.&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;URL : &lt;a href=&quot;https://www.npmjs.com/package/react-horizontal-scrolling-menu&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-horizontal-scrolling-menu&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DEMO: &lt;a href=&quot;https://codesandbox.io/s/lpjol1opmq&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codesandbox.io/s/lpjol1opmq&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 설치&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1622704839116&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install --save react-horizontal-scrolling-menu&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 적용하기&lt;/b&gt;&lt;/h3&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;예제에 나와있는 그대로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;const list = [{name: 'item1'}, ..., {name: 'item25'}]&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용하였습니다. &lt;/p&gt;
&lt;pre id=&quot;code_1622706371710&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ScrollMenu from 'react-horizontal-scrolling-menu' // ScrollMenu 가져오기

let list // 임시데이터 선언

// Item 단위의 컴포넌트
const MenuItem = ({ text }) =&amp;gt; {
  return &amp;lt;div className=&quot;menu-item&quot;&amp;gt;{text}&amp;lt;/div&amp;gt;;
};

// MenuItem들을 list만큼 생성한 List단위의 컴포넌트
export const Menu = list =&amp;gt; (
  list.map(el =&amp;gt; {
    const { name } = el;
    return &amp;lt;MenuItem text={name} key={name} /&amp;gt;
  })
);

function App() {

  const [DataItem, setDataItem] = useState(null);
  useEffect(() =&amp;gt; {
    setDataItem(Menu(list)); // Menu컴포넌트를 state에 저장
  }, [])
 

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;div className=&quot;wrap&quot;&amp;gt;
      // ScrollMenu 컴포넌트로 감싸주면 됩니다.
      {
        DataItem &amp;amp;&amp;amp;
        &amp;lt;ScrollMenu
          data={DataItem}
          wheel={true} // wheel 이 false 면 작동하지 않습니다
        /&amp;gt;
      }
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;381&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcvwtT/btq6vwylaIR/6KdSEQ0VpOJ4XYi3rW6p10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcvwtT/btq6vwylaIR/6KdSEQ0VpOJ4XYi3rW6p10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcvwtT/btq6vwylaIR/6KdSEQ0VpOJ4XYi3rW6p10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcvwtT%2Fbtq6vwylaIR%2F6KdSEQ0VpOJ4XYi3rW6p10%2Fimg.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;381&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에 다양한 옵션들이 있으니 사이트 가서 확인해 보세요~  &lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react x scroll</category>
      <category>react 가로스크롤</category>
      <category>react-horizontal-scrolling-menu</category>
      <category>리액트 가로스크롤</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/26</guid>
      <comments>https://wazacs.tistory.com/26#entry26comment</comments>
      <pubDate>Thu, 3 Jun 2021 18:40:04 +0900</pubDate>
    </item>
    <item>
      <title>[React][환경세팅] react-router-dom 사용하기</title>
      <link>https://wazacs.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 개발 시 귀찮은 환경 세팅을 기억이 나지 않을 때 일일이 찾아 사용하기 번거롭기 때문에 미리 정리해 놓기로 했습니다.&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;참조 1: &lt;a href=&quot;https://reactrouter.com/web/guides/quick-start&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://reactrouter.com/web/guides/quick-start&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 2: 리액트를 다루는 기술 서적 - 벨로 퍼트&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA의 단점중 하나는 너무 많은 자바스크립트를 한 번에 불러온다는 것인데,&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;h3 data-ke-size=&quot;size23&quot;&gt;설치&lt;/h3&gt;
&lt;pre id=&quot;code_1621920295259&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add react-router-dom &lt;/code&gt;&lt;/pre&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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;BrowserRouter&lt;/b&gt;: 주소에 대한 정보를 props에 담아 조회하거나 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;: HTML5의 History API를 사용하여 페이지를 새로고침 하지 않고 주소를 변경한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Link&lt;/b&gt;: history API를 사용하여 애플리케이션은 그대로 두고 경로만 변경한다.&lt;/li&gt;
&lt;li&gt;: a 태그는 페이지는 전환하는 과정에서 새로운 페이지를 불러옴으로 Link를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;URL Paramter || Query Parameter&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL Parameter&lt;/li&gt;
&lt;li&gt;: /user/info&lt;/li&gt;
&lt;li&gt;Query Paramter&lt;/li&gt;
&lt;li&gt;: /user?detail=info&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빠르게 세팅하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 라우터가 필요한 영역 전체를 BrowserRouter로 감싸주기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 꼭 index.js 안에 감싸줄 필요는 없지만 대부분의 사이트는 전역을 상대로 화면 전환을 하므로 index.js나 App.js, 혹은 메인이 되는 페이지에서 감싸도록 합니다. 그래야 전달되는 props값을 받을 수 있어 제대로 작동이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621920398378&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.js
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  &amp;lt;BrowserRouter&amp;gt;
    &amp;lt;React.StrictMode&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/React.StrictMode&amp;gt;
  &amp;lt;/BrowserRouter&amp;gt;,
  document.getElementById('root')
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 링크 만들어주기 (내비게이션)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 페이지 이동을 위해 a 태그 대신 Link라는 컴포넌트를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a 태그를 사용하게 되면 페이지 새로고침이 일어나기 때문에 SPA 개발 특성상 전체 새로고침이 아닌 부분 렌더를 해야하기 때문에 a 태그 대신 Link 태그를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 이후에 개발자 모드에서는 a 태그로 확인이 가능하면 css 또한 a 태그로 생각하고 스타일을 입히면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621920515094&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Nav.js
import React from 'react'
import { Link } from 'react-router-dom'

function Nav() {
    return (
        &amp;lt;ul className=&quot;navList&quot;&amp;gt;
            &amp;lt;li className=&quot;navItem&quot;&amp;gt;&amp;lt;Link to=&quot;/&quot;&amp;gt;Home&amp;lt;/Link&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li className=&quot;navItem&quot;&amp;gt;&amp;lt;Link to=&quot;/info&quot;&amp;gt;Info&amp;lt;/Link&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li className=&quot;navItem&quot;&amp;gt;&amp;lt;Link to=&quot;/user&quot;&amp;gt;User&amp;lt;/Link&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    )
}

export default Nav&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 링크 별 페이지 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 url 이외에 다른 경로로 접속했을 경우 무조건 404 페이지를 띄우고 싶다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 NotFound 페이지를 Route에 걸어주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621920600260&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.js
import {
  Switch,
  Route
} from 'react-router-dom'

//components
import Nav from './components/Nav'
import { 
  Home,
  Info,
  User,
  NotFound
} from './pages'

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Nav/&amp;gt;
      &amp;lt;hr/&amp;gt;
      &amp;lt;Switch&amp;gt;
        &amp;lt;Route exact path=&quot;/&quot; component={Home}/&amp;gt;
        &amp;lt;Route exact path=&quot;/user&quot; component={User}/&amp;gt;
        &amp;lt;Route exact path=&quot;/info&quot; component={Info}/&amp;gt;
        &amp;lt;Route component={NotFound} /&amp;gt;  // url이 /, /user, /info아 아닌 모든 경우 NotFound가 렌더됩니다
      &amp;lt;/Switch&amp;gt; 
    &amp;lt;/div&amp;gt;
  );
}
export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. URL Paramter 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user라는 페이지에 userid라는 이름으로 값을 전달합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 url에 &lt;b&gt;'/user/hong'이라고&lt;/b&gt; 접속을 하면, 컴포넌트 페이지에서는 userid='hong'으로 담겨옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621920717585&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// - App.js
- App.js
&amp;lt;Switch&amp;gt;
	{...}
	&amp;lt;Route exact path={[&quot;/user/:userid&quot;, &quot;/user&quot;]} component={User}/&amp;gt;
&amp;lt;/Switch&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 값(넘어오는 userid의 value값)에 접근을 하기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;match.params. 키값&amp;nbsp;&lt;/b&gt; (여기서 키값은 userid가 됩니다.)으로 접근하여 값을 받아볼 수 있습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621920816662&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- User.js

// 목업데이터
const data = {
    user1: {
        name: '홍길동',
        userId: 'hong'
    },
    user2: {
        name: '김둘리',
        userId: 'kim'
    }
}
const User = ({ match }) =&amp;gt; {
    const userid = match.params.userid; // match.params로 파라미터값을 받을 수 있음
    const userId2 = data[userid];  // 위 data목업데이터 에서 키값을 찾아 존재여부 확인    
    return (
        &amp;lt;div&amp;gt;
            This is User Page! &amp;lt;br/&amp;gt;
            {
                userId2 ?
                &amp;lt;p&amp;gt;User id is {userid}&amp;lt;/p&amp;gt; : 
                &amp;lt;p&amp;gt;No User&amp;lt;/p&amp;gt;
            }
        &amp;lt;/div&amp;gt;
    )
}
export default User&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. Query Paramter 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;url parameter에서는 url/:key 이렇게 값을 전달했다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query parameter에서는 /url? key=value 이렇게 기존 get 방식을 사용합니다. (저는 지금도 명칭이 너무 했갈립니다 ^^;;)&lt;/p&gt;
&lt;pre id=&quot;code_1621920939960&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- App.js

&amp;lt;Route exact path={[&quot;/info?userid='user1'&quot;, &quot;/info&quot;]} component={Info}/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 값을 받아보기 위해 url뒤에 붙은? 값을 제거하기 위해 한 번 더 작업을 해주면 깔끔하게 값을 받아볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 '리액트를 다루는 기술'이라는 책을 보고 학습을 하였기 때문에 해당 서적에서 제시해주는 방법에 따라 qs를 사용하였습니다.&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;b&gt;* qs 설치&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: qs는 querystring 타입의 데이터를 이리저리(?) 다룰 수 있게 해주는 라이브러리입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621921200024&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add qs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query parameter의 값은 location에서 받아볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621921252103&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- Info.js

import qs from 'qs'
// 목업데이터
const data = {
    user1: {
        name: '홍길동',
        detail: 'hong gill dong detail'
    },
    user2: {
        name: '김둘리',
        detail: 'kim dool li detail'
    }
}
const Info = ({ location }) =&amp;gt; {
	// location 에서 받음
    const query = qs.parse(location.search, {
        ignoreQueryPrefix: true // url 상에 앞에 ? 를 ignore함 
    })
    const queryCheck = data[query.userid];
    return (
        &amp;lt;div&amp;gt;
            This is info Page! &amp;lt;br/&amp;gt;
            {
                queryCheck &amp;amp;&amp;amp;
                &amp;lt;p&amp;gt;{queryCheck.userid}&amp;lt;/p&amp;gt;
            }
        &amp;lt;/div&amp;gt;
    )
}
export default Info&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;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react</category>
      <category>react a태그</category>
      <category>react parameter</category>
      <category>react-router</category>
      <category>react-router-dom</category>
      <category>라우터</category>
      <category>리액트</category>
      <category>리액트 네비게이션</category>
      <category>리액트 파라미터</category>
      <category>리액트 페이지</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/23</guid>
      <comments>https://wazacs.tistory.com/23#entry23comment</comments>
      <pubDate>Tue, 25 May 2021 15:11:09 +0900</pubDate>
    </item>
    <item>
      <title>[에러노트] defaultState for reducer handling [object Object] should be defined</title>
      <link>https://wazacs.tistory.com/22</link>
      <description>&lt;p&gt;리덕스 스터디중 handleAction을 사용하던 중 해당 소스에서 다음과 같은 에러가 발생함&lt;/p&gt;
&lt;pre id=&quot;code_1615947967884&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 리덕스 모듈
import { createAction, handleAction } from 'redux-actions'

const INCREASE = 'counter/INCREASE'
const DECREASE = 'counter/DECREASE'

export const increase = createAction(INCREASE)
export const decrease = createAction(DECREASE)

const initialState = 0

const counter = handleAction({
   [INCREASE]: state =&amp;gt; state + 1,
   [DECREASE]: state =&amp;gt; state - 1
}, initialState)

export default counter;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;defaultState for reducer handling [object Object] should be &lt;b&gt;defined&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;5555555555555555555555555.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;260&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgVqX/btq0iWKGVZN/hqy943hdsSSRO8qDwGVkPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgVqX/btq0iWKGVZN/hqy943hdsSSRO8qDwGVkPk/img.png&quot; data-alt=&quot;에러화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgVqX/btq0iWKGVZN/hqy943hdsSSRO8qDwGVkPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgVqX%2Fbtq0iWKGVZN%2Fhqy943hdsSSRO8qDwGVkPk%2Fimg.png&quot; data-filename=&quot;5555555555555555555555555.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;260&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;이러한 에러가 나타나는 이유는 액션이 2개 이상이기 때문.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2개 이상의 액션을 처리하려면 &lt;b&gt;handleAction =&amp;gt; handleActions로 변경한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;그러면 에러가 해결된다 .&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;굳이 굳이 handleAction을 사용해야한다면, 하나의 액션만 설정해 주면된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615948125597&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 리덕스 모듈
import { createAction, handleAction } from 'redux-actions'

const INCREASE = 'counter/INCREASE'

export const increase = createAction(INCREASE)

const initialState = 0

const counter = handleAction(
    INCREASE,
    state =&amp;gt; state + 1,
    initialState
) 

export default counter;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보통의 프로젝트를 보면 handleAction은 거의 사용할 일이 없을것 같다.  &lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/22</guid>
      <comments>https://wazacs.tistory.com/22#entry22comment</comments>
      <pubDate>Wed, 17 Mar 2021 11:29:33 +0900</pubDate>
    </item>
    <item>
      <title>React Hook 에서 Scroll 컨트롤 하기!  top버튼 구현</title>
      <link>https://wazacs.tistory.com/21</link>
      <description>&lt;p style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;How can I make a Top Button, click to scroll to the top ?!&lt;/b&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&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;처음에는 무조건 ref 를 사용해서 컨트럴 해야 한다고 생각했었는데, 어쨌든 리액트도 자바스크립트다 보니 꼭 그렇지만은 않더라구요. useEffect에서 addEventListener를 걸어주고 &lt;span&gt;removeEventListener로 clean-up을 해주기만 하면 되더라구요. 생각보다 간단하게 구현 할 수 있었습니다.&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;다음과 같이 클릭하면 페이지 최상단으로 스크롤 되는 기능을 구현해 보고자 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;687&quot; data-filename=&quot;playground100.gif&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zwkmD/btqWKZ5cr4J/8m0A79WgCEwr0NCRPso1s1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zwkmD/btqWKZ5cr4J/8m0A79WgCEwr0NCRPso1s1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zwkmD/btqWKZ5cr4J/8m0A79WgCEwr0NCRPso1s1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zwkmD/btqWKZ5cr4J/8m0A79WgCEwr0NCRPso1s1/img.gif&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;687&quot; data-filename=&quot;playground100.gif&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;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 구조를 잡아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612877986166&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;wrap&quot;&amp;gt;
  &amp;lt;button className=&quot;topBtn&quot;&amp;gt;TOP&amp;lt;/button&amp;gt;
  &amp;lt;div className=&quot;inner&quot;&amp;gt;
    &amp;lt;h1&amp;gt;Fixed Scroll Top&amp;lt;/h1&amp;gt;
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Mollitia, voluptas ratione libero earum alias, officia temporibus magni possimus atque rem distinctio autem unde eaque. Minus quaerat odio mollitia asperiores neque?
      ...
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CSS도 미리 올려두겠습니다. 디자인을 조금이라도 이쁘게 하고자 노오력 했지만..  &lt;/p&gt;
&lt;p&gt;CSS는 따로 설명 드리지 않겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612878314173&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.wrap {
  position: relative;
  padding: 30px;
  font-size: 18px;
  line-height: 1.6;
  background: lightgray;
}

.topBtn {
  position: fixed; 
  opacity: 0; 
  bottom: 40px; 
  right: 40px;
  z-index: -10; 
  width: 50px; 
  height: 50px;
  border-radius: 100%;
  border: 0 none;
  background: lightpink;
  color: blueviolet;
  border: 2px solid blueviolet;
  font-size: 18px;
  font-weight: bold;
  letter-spacing: -0.06em;
  box-shadow: 1px 1px 6px 3px rgba(0,0,0,0.3);
  cursor: pointer;
  transition: opacity 0.3s ease-in;
}

.topBtn.active {
  z-index: 10; 
  opacity: 1; 
}

.topBtn:hover,
.topBtn:focus,
.topBtn:active { 
  outline: 0 none; 
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. 스크롤을 100px 이상 내리면 TOP 버튼이 나타난다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. TOP버튼을 클릭하면 스크롤리 최상단으로 올라간다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 스크롤의 값을 알기 위해 ScrollY 라는 이름으로 state를 만듦니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 함수를 하나 정의하여 window.pageYoffset 값을 ScrollY 에 저장 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 부분을 스크롤 할때마다 반복하면 window의 스크롤 값이 ScrollY에 저장되고, ScrollY의 값이 저장되면서 useEffect를 통해 실시간으로 바뀌는 ScrollY의 값을 알 수 있겠지요?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612878532780&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [ScrollY, setScrollY] = useState(0);  // 스크롤값을 저장하기 위한 상태
  const handleFollow = () =&amp;gt; {
    setScrollY(window.pageYOffset); // window 스크롤 값을 ScrollY에 저장
  }

  useEffect(() =&amp;gt; {
    console.log(&quot;ScrollY is &quot;, ScrollY); // ScrollY가 변화할때마다 값을 콘솔에 출력
  }, [ScrollY])

  useEffect(() =&amp;gt; {
    const watch = () =&amp;gt; {
      window.addEventListener('scroll', handleFollow);
    }
    watch(); // addEventListener 함수를 실행
    return () =&amp;gt; {
      window.removeEventListener('scroll', handleFollow); // addEventListener 함수를 삭제
    }
  })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;지금 같이 스크롤을 사용하는 경우 처럼 &lt;/span&gt;한 번 실행하는 것이 아닌 여러번 add&lt;span style=&quot;color: #333333;&quot;&gt;EventListener를 사용할 때에는 &lt;/span&gt;removeEventListener를 꼭 해주세요!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;그렇지 않으면 여러번 호출 되고, 메모리에 gabarge collect&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;625&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/58zEU/btqWKZqCHPK/Ydn0cOyKLfkrdKzhtGsUAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/58zEU/btqWKZqCHPK/Ydn0cOyKLfkrdKzhtGsUAK/img.png&quot; data-alt=&quot;결과 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/58zEU/btqWKZqCHPK/Ydn0cOyKLfkrdKzhtGsUAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F58zEU%2FbtqWKZqCHPK%2FYdn0cOyKLfkrdKzhtGsUAK%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;625&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;이렇게 스크롤의 위치값을 알 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그러면 나머지는 더 쉽지요.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저는 임의로 스크롤이 100정도 내려가면 버튼이 나타나게 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;상태에 따라 버튼을 보이고 안보이고를 결정하는 BtnStatus 값을 하나 두었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;물론 &lt;span style=&quot;color: #333333;&quot;&gt;아래의 예시처럼 &lt;/span&gt;버튼을 아예 렌더를 하느냐 안하느냐로 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1612879036197&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ BtnStatus &amp;amp;&amp;amp;&amp;lt;button className=&quot;topBtn&quot;&amp;gt;TOP&amp;lt;/button&amp;gt; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 저는 opcity와 transition을 통해 좀 더 부드러운 효과를 주고 싶어서 그냥 클래스로 제어하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612879216355&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useEffect } from 'react'
import './App.css';

function App() {

  const [ScrollY, setScrollY] = useState(0);
  const [BtnStatus, setBtnStatus] = useState(false); // 버튼 상태
  
  const handleFollow = () =&amp;gt; {
    setScrollY(window.pageYOffset);
    if(ScrollY &amp;gt; 100) {
      // 100 이상이면 버튼이 보이게
      setBtnStatus(true);
    } else {
      // 100 이하면 버튼이 사라지게
      setBtnStatus(false);
    }
  }

  const handleTop = () =&amp;gt; {  // 클릭하면 스크롤이 위로 올라가는 함수
    window.scrollTo({
      top: 0,
      behavior: &quot;smooth&quot;
    });
    setScrollY(0);  // ScrollY 의 값을 초기화
    setBtnStatus(false); // BtnStatus의 값을 false로 바꿈 =&amp;gt; 버튼 숨김
  }

  useEffect(() =&amp;gt; {
    const watch = () =&amp;gt; {
      window.addEventListener('scroll', handleFollow)
    }
    watch();
    return () =&amp;gt; {
      window.removeEventListener('scroll', handleFollow)
    }
  })

  return (
    &amp;lt;div className=&quot;wrap&quot;&amp;gt;
      &amp;lt;button 
        className={BtnStatus ? &quot;topBtn active&quot; : &quot;topBtn&quot;} // 버튼 노출 여부
        onClick={handleTop}  // 버튼 클릭시 함수 호출
      &amp;gt;TOP&amp;lt;/button&amp;gt;
      &amp;lt;div class=&quot;inner&quot;&amp;gt;
        &amp;lt;h1&amp;gt;Fixed Scroll Top&amp;lt;/h1&amp;gt;
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Mollitia, voluptas ratione libero earum alias, officia temporibus magni possimus atque rem distinctio autem unde eaque. Minus quaerat odio mollitia asperiores neque?
          ...
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&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;물론 Scroll 부분만 따로 컴포넌트로 관리를 하면 더욱 좋겠네요!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저는 샘플 소스라 그냥 한 곳에 작성하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;scroll 관련하여 많은 라이브러리도 있고 ref를 사용하는 방법도 있습니다.&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;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 다들 즐거운 코딩 하세요!&amp;nbsp;&lt;/p&gt;</description>
      <category>개발  /ReactJS</category>
      <category>react addEventListener</category>
      <category>react hook scroll</category>
      <category>react removeEventListener</category>
      <category>react scroll</category>
      <category>react top button</category>
      <category>react 탑 버튼</category>
      <category>리액트 스크롤</category>
      <category>리액트 실시간 스크롤</category>
      <author>JOTOKKI</author>
      <guid isPermaLink="true">https://wazacs.tistory.com/21</guid>
      <comments>https://wazacs.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 9 Feb 2021 23:10:57 +0900</pubDate>
    </item>
  </channel>
</rss>