
시맨틱 태그와 seo 관련 글에서 seo 성능을 확인하기 위해 크롬의 도구인 lighthouse을 사용할 수 있다는 것을 알았다!
그래서 잠깐 짬을 내서 투두리스트도 시험해봤는데 오! 100 이 떴다 😮😮😀
'프로젝트 > 투두리스트' 카테고리의 다른 글
display: grid 사용 이유! (0) | 2024.04.18 |
---|---|
setState일때 strict모드로 인해 함수가 두번 호출됨 (0) | 2022.09.08 |
시맨틱 태그와 seo 관련 글에서 seo 성능을 확인하기 위해 크롬의 도구인 lighthouse을 사용할 수 있다는 것을 알았다!
그래서 잠깐 짬을 내서 투두리스트도 시험해봤는데 오! 100 이 떴다 😮😮😀
display: grid 사용 이유! (0) | 2024.04.18 |
---|---|
setState일때 strict모드로 인해 함수가 두번 호출됨 (0) | 2022.09.08 |
Window.scrollY - Web API | MDN
scrollY는 읽기 전용 속성으로, 문서가 수직으로 얼마나 스크롤됐는지 픽셀 단위로 반환한다.
updateScroll 함수를 만들어서 scrollY가 75 (=헤더의 높이) 이상이면 현재 스크롤되고 있는 것으로 판단해서 scrollFlag를 true로 한다. 여기서 scrollFlag는 useState를 활용한다.
HeaderContainer에 isScroll 변수명으로 props를 만들고 scrollFlag를 넣는다.
scrollFlag의 초기값은 false로 하고, 스크롤이 감지되면 true로 업데이트하고 리렌더링 한다.
scrollFlag가 false일 땐 배경색을 투명으로 하고, true일 땐 배경색을 흰색으로 하고 밑테두리 css를 추가했다.
또한 상태 전환 시 transition으로 끊김이 없어보이도록 변화를 부드럽게 만들었다.
const [scrollFlag, setScrollFlag] = useState(false);
const updateScroll = () => {
const { scrollY } = window;
const scrolled = scrollY > 75;
setScrollFlag(scrolled);
};
<HeaderContainer isScroll={scrollFlag}></HeaderContainer>
const HeaderContainer = styled.header`
position: fixed;
width: 100%;
height: 75px;
background-color: transparent;
color: #fff;
z-index: 9999;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
transition: all 0.8s ease-out;
${(props) =>
props.isScroll &&
css`
background-color: #ffffff;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
`};
`;
그래서 window.scrollY로 스크롤이 발생할 때마다 state를 업데이트하면 되겠다는 생각까진 도달했다.
하지만 scroll 이벤트는 유저가 스크롤을 조금만 움직여도 실행되기 때문에 scroll을 할 때마다 state를 변경하려는 생각은 잠시 접어두자… 해당 컴포넌트가 수십번 불필요하게 리렌더링되기 때문이다.
scroll 이벤트를 제어하기 위해서는 일정 시간 걸어두고 그 안에서 이벤트를 한 번만 실행되도록하는 throttle을 적용하는 게 좋겠다. 여러번 발생하는 이벤트를 일정 시간 동안 한 번만 실행시키는 게 적합해 보이기 때문이다.
const throttle = (callback, delay) => {
let timer = null;
return () => {
if (timer) return;
timer = setTimeout(() => {
callback();
timer = null;
}, delay);
};
};
src/components/Header.jsx
import { useEffect, useState } from "react";
import { Link as L, useLocation } from "react-router-dom";
import styled, { css } from "styled-components";
import { logo } from "assets/img";
function Header() {
let { pathname } = useLocation();
const [scrollFlag, setScrollFlag] = useState(false);
const throttle = (callback, delay) => {
let timer = null;
return () => {
if (!timer) {
timer = setTimeout(() => {
callback();
timer = null;
}, delay);
}
};
};
const updateScroll = () => {
const { scrollY } = window;
const scrolled = scrollY > 75;
setScrollFlag(scrolled);
};
const handleScroll = throttle(updateScroll, 100);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
return (
<HeaderContainer isScroll={scrollFlag}>
<Link to="/">
<img src={logo} alt="LOGO" />
</Link>
<nav>
<NavContainer>
<li>
<Link to="/intro" open={pathname === "/intro" && true}>
Intro
</Link>
</li>
<li>
<Link to="/crime" open={pathname === "/crime" && true}>
금융 사기
</Link>
</li>
<li>
<Link to="/prevent" open={pathname === "/prevent" && true}>
범죄 예방
</Link>
</li>
</NavContainer>
</nav>
</HeaderContainer>
);
}
export default Header;
https://velog.io/@dami/ReactThrottle%EB%A1%9C-Sticky-Header-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0
React에서 기본적으로 제공해주는 createPortal api를 사용 (0) | 2024.04.18 |
---|---|
보충 설명이 필요한 내용은 Modal을 사용해서 UX를 높임 (1) | 2024.04.18 |
가상 엘리먼트 ::before ::after (0) | 2024.04.18 |
리액트 프로젝트는 기본적으로 index.js에서 렌더링한다.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Router>
**<App />**
</Router>
</React.StrictMode>,
);
reactDOM 라이브러리가 제공하는 reactRoot 함수가 반환하는 render 함수를 이용하는데, createRoot 함수는 인자로 먼저 document.getElementById(”root”)
를 받는다.
여기서 id가 root인 HTML 요소는 public/index.html
에 있다.
아래 html 파일이 개발서버를 구동했을 때 화면에 보이게 되는 html 파일이라고 볼 수 있다.
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<div id="root"></div>
**<div id="modal"></div>**
</body>
</html>
root 페이지와 분리해서 만들기 위해 id=”modal”
를 index.html에 만들어 뒀다.
자식들을 DOM의 다른 부분으로 렌더링해주는 기능을 제공해준다.
https://xionwcfm.tistory.com/316#google_vignette
src/components/Modal/ModalContainer.jsx
import { createPortal } from "react-dom";
function ModalContainer({ children }) {
return createPortal(<>{children}</>, document.getElementById("modal"));
}
export default ModalContainer;
createPortal(children, domNode, key?)
<div id="modal"></div>
를 미리 만들어 뒀다.
src/components/Modal/index.jsx
import { useEffect } from "react";
import { useRef } from "react";
import styled from "styled-components";
import useOutsideClick from "hooks/useOutSideClick";
import ModalContainer from "./ModalContainer";
function Modal({ onClose, children }) {
const modalRef = useRef(null);
const handleClose = () => {
onClose?.();
};
return (
<ModalContainer>
<Overlay>
<ModalWrap ref={modalRef}>
<Contents>{children}</Contents>
</ModalWrap>
</Overlay>
</ModalContainer>
);
}
export default Modal;
state가 true일 땐 모달을 보여주고, false일 땐 모달을 보여주지 않는다.
HelpIcon과 눈에 띄는 색깔로 모달을 띄워 줄 글자(LRAT)를 강조하고, 사용자가 글자를 클릭하면 모달이 열린다.
→ LratModal.jsx가 랜딩된다
src/pages/Intro/MethodArea.jsx
import LratModal from "./LratModal";
function MethodArea() {
const [isOpen, setIsOpen] = useState(false);
const onOpen = () => {
setIsOpen(true);
};
const onClose = () => {
setIsOpen(false);
};
return (
<Container>
<SubTitle content="데이터 분석 방법" />
<SubDesc>
<span onClick={onOpen}>
<HelpIcon /> LRAT (Lifestyle and Routine Activity Theories)
</span>
의 세 가지 요소를 개념화(operationalization)했습니다.
</SubDesc>
...
{isOpen && <LratModal onClose={onClose} />}
</Container>
);
}
export default MethodArea;
components 폴더에 만들어 둔 Modal을 import해서 모달 내용을 채운다.
‘확인’ 버튼이나 ‘X’ 아이콘을 클릭하면 모달이 닫힌다.
src/pages/Intro/LratModal.jsx
import styled from "styled-components";
import { Modal } from "components";
import { modalTree } from "assets/img";
import { MdClose } from "react-icons/md";
function LratModal({ onClose }) {
return (
<Modal onClose={onClose}>
<CloseBtn onClick={onClose} />
<Content>
...
</Content>
<Button onClick={onClose}>
<p align="center">확인</p>
</Button>
</Modal>
);
}
export default LratModal;
Throttle로 스크롤 감지하고 스크롤하면 Header css 바꾸기 (1) | 2024.04.18 |
---|---|
보충 설명이 필요한 내용은 Modal을 사용해서 UX를 높임 (1) | 2024.04.18 |
가상 엘리먼트 ::before ::after (0) | 2024.04.18 |
Modal과 Popup의 차이를 먼저 알아봤다.
두 가지 모두 사용자에게 정보를 보여주거나 상호작용을 유도하는 데 사용된다.
정리하면,
Popup은 현재 창을 벗어난 새 창으로 나타나지만, Modal은 현재 창인 뷰포트 안에 나타난다.
참고: https://brunch.co.kr/@minakoro/156, https://www.coneboy.kr/54/?idx=18153167&bmode=view
검색한 결과를 토대로 친구와 다시 얘기해본 결과,
src/components/Modal
→ 모달은 한 번 만들어 두면 나중에 또 쓰일 것 같아서 공통 컴포넌트 폴더에 생성했다.
React에서 기본적으로 제공해주는 createPortal api를 사용
Throttle로 스크롤 감지하고 스크롤하면 Header css 바꾸기 (1) | 2024.04.18 |
---|---|
React에서 기본적으로 제공해주는 createPortal api를 사용 (0) | 2024.04.18 |
가상 엘리먼트 ::before ::after (0) | 2024.04.18 |
CSS 의 ::before및 ::after 가상 요소를 사용하면 HTML에 있을 필요 없이 페이지에 콘텐츠를 삽입할 수 있다. 최종 결과는 실제로 DOM에 없지만 페이지에는 마치 DOM에 있는 것처럼 나타난다.
콘텐츠는 여전히 적용된 요소 내에 있다.
https://css-tricks.com/almanac/selectors/a/after-and-before/
CSS ::before나 ::after에 z-index: -1
을 준다면, 가상요소의 컨텐츠가 요소의 컨텐츠보다 뒤에 위치하게 된다.
→ figure의 ::after에 z-index:-1
을 주지 않는다면 a태그가 뒤로 가려지기 때문에 범죄분석 페이지로 이동할 수 없게 된다.
그런데 ::before에서는 z-index:-1
없어도 가능하네?
어찌보면 당연한 .. !
-> before는 a 태그의 전에, after는 a 태그의 뒤에 위치하기 때문에 before는 이미 a태그보다 뒤에 위치해 있다.
figure에 position: relative
를 넣고 :before이나 :after에 position:absolute
를 하면 figure를 기준으로 자유롭게 위치를 조절할 수 있다.
&:before, &:after {
content: "";
position: absolute;
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
z-index: -1;
border-radius: 50%;
border: 3px solid #bbb;
}
위처럼 top, bottom, right, left를 모두 0px로 하면, top(위), bottom(아래), right(오른쪽), left(왼쪽)으로부터 0px 떨어져 있도록 하는 것이기 때문에 아래와 같이 요소의 테두리를 감싸고 있는 형태가 된다. (흥미 돋았던 내용이었다)
Throttle로 스크롤 감지하고 스크롤하면 Header css 바꾸기 (1) | 2024.04.18 |
---|---|
React에서 기본적으로 제공해주는 createPortal api를 사용 (0) | 2024.04.18 |
보충 설명이 필요한 내용은 Modal을 사용해서 UX를 높임 (1) | 2024.04.18 |
마이페이지, 네트워크, 로그아웃 세개의 메뉴를 우측 끝으로 이동시키고 싶었다..
react-bootstrap은 보통 container, Col, Row, Stack 4개의 컴포넌트로 레이아웃을 잡는 편이다.
그런데 Nav 같이 이미 자체적으로 layout이 잡혀져 있는 것도 있다!
padding-right
, padding-left
, Nav 전체적으로 내부 자식에게 간격을 주기 위함margin-left: auto
를 의미함. auto 는 부모의 여백공간만큼을 왼쪽으로 밀어내는 느낌이다, 자주 쓰이는 속성이니 숙지하기!Nav.item
태그 3개를 Nav 태그 하나로 묶어서 덩어리때 내려가게 한다.
결국 display: flex;
를 사용했는데 , CSS 레이아웃을 잡을 땐 flex를 많이 사용하므로 알아두는 게 좋다.
flex 를 공부하기 좋은 사이트 → 개구리 꽤나 귀여워 Flexbox Froggy
const navigationInformations = [
{ title: '마이페이지', link: '/' },
{ title: '네트워크', link: '/network' },
];
<div
style={{
backgroundColor: '#D9DDFF',
display: 'flex',
alignItems: 'center',
padding: '34px 56px',
}}
>
<div style={{ fontSize: '2em' }}>MY PORTFOLIO</div>
<ul
style={{
margin: '0 0 0 auto',
listStyleType: 'none',
padding: 0,
display: 'flex',
gap: 30,
padding: 0,
}}
>
{navigationInformations.map((navigationItem) => (
<li
onClick={() => {
navigate(navigationItem.link);
}}
style={{
borderRadius: '10px',
padding: '6px 14px',
backgroundColor: '#989CFD',
color: 'white',
}}
key={navigationItem.title}
>
{navigationItem.title}
</li>
))}
</ul>
</div>
e.preventDefault() ,e.stopPropagation() ? (0) | 2024.04.18 |
---|
몇몇 태그는 내장된 고유 기능이 있다.
form 태그는 submit될 시에 페이지를 새로 고침을 한다.
이런 것들을 방지하기 위해 e.preventDefault()
를 사용한다.
e.stopPropagation()
은 이벤트 버블링과 관련.
html 태그 안에서 자식에게 이벤트가 발생하면 특정 함수가 발생되도록 이벤트 핸들러를 심어놓는다.
ex) onClick = {() => console.log('hi')}
그러면 이벤트가 발생한 자식을 기점으로 부모에서부터 위로 존재하는 모든 태그의 끝까지 순차적으로 이벤트 핸들러가 동작한다.
이것을 막기 위해 e.stopPropagation()
을 사용한다.
하지만 이벤트 버블링을 막아야 할 상황은 거의 없다고 하기 때문에 e.stopPropagation()
의 사용은 권장하지 않는다.
event.currentTarget
) : 현재요소 <form> 요소에 있는 핸들러가 동작했으므로 <form> 요소를 가리킴event.target
- 폼 안쪽에 실제 클릭한 요소를 가리킴
nav들 위치 이동? (1) | 2024.04.18 |
---|
import LogoSrc from './assets/logo.png';
/* ... */
const LogoDiv = styled.div`
background-image: url(${LogoSrc});
/* width and height should be set otherwise container will have either have them as 0 or grow depending on its contents */
`;
/* ... */
<LogoDiv />
../assets/images/lala.jpg
로 했는데도 이상하게 안됐다!
이미지를 import 시켜와서 url에 넣어두고 width와 height을 주면 그제서야 잘 나오는 것 같다!
→ 왜? width와 height 값을 설정해주지 않으면 컨테이너가 0으로 설정되거나 컨테이너의 내용에 따라 변화한다! (auto가 되므로)
styled-components는 이미지를 import 해서 시키는 것을 권장한다! 정석이다!
nodemon stuck restarting… 해결 (0) | 2024.04.18 |
---|---|
aws s3 생성 및 업로드 코드 구현 (0) | 2024.04.18 |
백엔드 코드를 수정하고 저장하고 다시 시작하면 nodemon stuck restarting… 하면서 오류가 떴다.
C:\\Windows;
C:\\Windows\\system32;
C:\\Windows\\System32\\Wbem
를 시스템 환경변수에 넣어주면 ,,,!!! 드디어 restarting 에서 stuck되던 게 !!
다시 starting이 된다!!!
https://github.com/remy/nodemon/issues/1956
It was a Windows path thing. Windows was not able to get the path as it had unnecessary special characters like the & and the whitespaces in the path names. Looks like Node is not able to resolve the paths.
⇒ 윈도우 환경변수의 path에 경로가 존재하지 않아서 노드가 path를 찾을 수가 없어서 starting을 하지 못했던 것이다!
styled-components 사용해서 bgImage 넣기! (0) | 2024.04.18 |
---|---|
aws s3 생성 및 업로드 코드 구현 (0) | 2024.04.18 |
[Node.js] multer-s3를 이용한 AWS s3 파일 업로드
아마존 웹 서비스 IAM 사용자의 액세스 키 발급 및 관리
iam에서 액세스키와 비밀 액세스 키는 한번 생성되면 다시 확인할 수 없으니 .csv로 저장해두거나 잘 메모해놔야 한다.
혹시 multerS3에 다른 기능도 넣고 싶다면 공식문서를 참고하자.
multerS3 와 aws-sdk의 버전이 달라서 생긴 오류였다.
다르니까 @aws-sdk/client와 @aws-sdk/abort-controller 모듈이 필요하다는 에러가 떴고 설치해서 실행하니까 client.send() 함수가 없다는 오류가 뜬 것이다.
버전을 2.~~으로 맞춰서 설치해주니까 두 모듈이 필요하다는 에러메시지가 안뜨고 해결됐다!
(근데 이걸 팀원분이 찾아낸 해결책인데 구글에 어떻게 검색하신 걸까?)
검색어: aws-sdk multer-s3 @aws-sdk/abort-controller
Can the multer-s3 package be used with AWS v3 NodeJS SDK?
→ multer-s3 패키지는 오직 aws-sdk v2 api만 지원한다.
[AWS] AccessDenined: Access Denied
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1405592139000",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::dev-team8-bucket/*",
"arn:aws:s3:::dev-team8-bucket"
]
}
]
}
multer에서 로직처리를 하고 s3에 업로드를 하는 방식이 더 좋지 않을까?!
[AWS] Node.js Multer로 S3에 이미지 업로드하고 Mysql에 저장하기
→ 5기가를 넘어서는 과금이 생김
styled-components 사용해서 bgImage 넣기! (0) | 2024.04.18 |
---|---|
nodemon stuck restarting… 해결 (0) | 2024.04.18 |