@@@결과 미리보기@@@
1. 구현
내가 이전에 만든 웹 화면은, 바로 위 gif와 같이 창을 전체화면으로 사용하기보다 한쪽으로 화면을 축소하여 사용하는 일이 많았다. 이에 화면이 반절로 되게 되면, 웹사이트에 불편함을 느껴 반응형을 도입하여 이문제를 해결하고자 하였다.
1. display: grid
기본적으로 display:flex로 구현되어 있는 컨테이너를 반응형을 위해 grid로 변경하였다.
왜 grid냐?
현재 나는 왼쪽 사진과 같이 1열로 나란히 있는 요소들을 화면의 width가 줄어들면, 오른쪽과 같이 세로로 배치되기를 원하기 때문이다.
display: flex;의 경우 일반 div의 성격인 세로로 쌓이는 것이 아닌, div가 가로로 쌓이게 된다. 이러한 경우 반응형으로 가로 세로 요소의 배치를 바꾸기에 매우 껄끄럽기 때문에, display:grid로 변경해야 한다.
현재의 요구사항을 정리하면 다음과 같다.
1. 화면이 넓을 때에는 가로로 배치되어야 한다.
2. 화면이 좁아지는 경우 세로로 배치되어야 한다.
display: grid;
grid-template-columns: 1fr 1fr 1fr;
(fr은 단위는 화면 크기에 맞춰 공간을 자동으로 조정한다. 고정된 크기(px)와 반대되는 성격을 가진다.)
display를 grid로 하고, grid-template-columns: 1fr 1fr 1fr;를 설정하게 되면, 3개의 열을 가진 grid로 생성된다.
결과는 아래와 같다.
위 결과 말고 이해를 하고자 덧붙이면,
display: grid;
grid-template-rows: 1fr 1fr 1fr;
이와 같이 지정하면 행이 3개가 생기고,
display: grid;
grid-template-columns: 1fr 2fr 1fr;
grid-template-rows: 3fr 1fr 1fr;
| Container1 | Container2 | Container3 |
| Container4 | Container5 | Container6 |
| Container7 | Container8 | Container9 |
행과 열을 모두 설정하게 되면, 3x3 칸이 생기며, 열끼리 비율은 1:2:1 , 행끼리 비율은 3:1:1로 설정되게 된다.
2. media query
@media (조건) {
/* 조건을 만족할 때 적용될 CSS 스타일 */
}
media query는 CSS에서 반응형 디자인을 구현할 때 사용하는 기능이다.
간단히 설명하면 조건에 맞으면 실행되는 CSS코드라고 생각하면 쉽다.
조건에는 보통 screen의 크기 등이 사용되는데,
@media (max-width: 1200px) {
/* 조건을 만족할 때 적용될 CSS 스타일 */
}
max-width, min-width 등 높이에 관련한 조건을 넣으면, 우리가 원하는 반응형에 맞게 사용할 수 있게 된다.
3. display: grid과 media query 활용
반응형 작업 이전 코드와 이후 코드 순서이다.
//Home 변경 전 코드
const BaseContainer = styled.div`
display: flex;
padding: 20px;
width: 100%;
height: 100%;
align-items: center;
justify-content: space-around;
`;
const ButtonWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 40px;
align-items: center;
`;
//Home 변경 후 코드
const BaseContainer = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
padding: 20px;
width: 100%;
height: 100%;
align-items: center;
justify-items: center;
@media (max-width: 1200px) {
grid-template-columns: 1fr;
}
`;
const ButtonWrapper = styled.div`
display: grid;
grid-template-rows: 150px auto;
flex-direction: column;
align-items: center;
justify-item: center;
@media (max-width: 1200px) {
grid-template-columns: 150px auto;
}
`;
BaseContainer를 보면, display:flex; -> display:grid; 와 @media가 추가된 것을 볼 수 있다.
const BaseContainer = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
...
@media (max-width: 1200px) {
grid-template-columns: 1fr;
}
`;
핵심적으로 기본적인 전체화면에서는 grid-template-columns: 1fr 1fr 1fr;를 통해 3개의 열이 1:1:1 비중으로 차지하는 것을 알 수 있고, 화면의 width가 1200px보다 작아지는 경우 열을 1개로 변경하는 것을 볼 수 있다.
위 로직이 반응형 웹에 핵심적인 로직이다.
웹화면 가운데에 버튼들도 보면, 넓을 때에는 세로로 배치, 좁아지는 경우 가로로 배치되는 것을 볼 수 있는데, 1열이 1행으로 변경되는 방식에서는 display: grid만 해도 크기에 맞게 알아서 행과 열로 지정방식이 달라진다.
const ButtonWrapper = styled.div`
display: grid;
grid-template-rows: 150px auto;
align-items: center;
justify-item: center;
@media (max-width: 1200px) {
grid-template-columns: 150px auto;
}
`;
그렇기에 기본적으로 가로방향으로 지정되어 있을 때와 세로로 지정되어있을 때 크기를 지정해 주고자, 150px만 각각 입력해 준 것을 볼 수 있다.
마지막으로 입력을 받을 수 있는 TextField부분도 반응형을 적용하였다.
아래 결과화면 또는 가장 상단에 있는 미리 보기를 확인하면,
화면 사이즈 줄어듬에 따라 widh:400px로 하는 경우 화면이 작아졌을 때 너무 크기가 작고, 400px이 아닌 비율로만 지정하는 경우, 원하는 탭 사이즈에 원하는 텍스트 상자의 크기가 나오지 않았다.
const TextareaStyled = styled(TextareaAutosize)`
width: 400px;
...
@media (min-width: 1500px) {
width: 65vh;
}
@media (max-width: 1200px) {
width: 65vh;
}
...
`;
위 문제를 해결하고자, 기본적인 CSS 설정은 400px로 설정하고, 웹 크기가 1500px이 넘어가는 경우 65vh로 하고,
1200px보다 작은 경우 65vh로 설정하였다.
즉 1200px보다 작은 경우와 1500px보다 큰 경우에는 65vh로 지정하고, 1200px < x <1500px인 경우에는 400px로 지정하였다.
2. 결과 및 소스코드
1. 결과 화면
2. 웹사이트
https://ppt-code-copypaste.netlify.app/
3. 전체 소스 코드
//Home.jsx
import styled from 'styled-components';
import { useState } from 'react';
import ConvertButton from '../components/ConvertButton';
import TextField from '../components/TextField';
import CopyButton from '../components/CopyButton';
import HelpButton from '../components/HelpButton';
import Modal from './Modal';
function Home() {
const [input, setInput] = useState('');
const [result, setResult] = useState('');
const [isOpen, setIsOpen] = useState(false);
const onChangeInput = (e) => {
setInput(e.target.value);
};
const onChangeOutput = (e) => {
setResult(e.target.value);
};
const onClickConvert = () => {
const formatCode = input
.replace(/[“”❛❜]/g, '"') // 코드 번호 제거
.replace(/^\d{2}/gm, '') // 코드 번호 제거
.replace(/^ /gm, ''); // 각 줄의 앞 공백 제거
let indentLevel = 0;
const indentSize = 4; // 탭 크기
let formattedCode = formatCode
.split('\n') // 코드를 줄 단위로 처리
.map((line) => {
line = line.trim(); // 각 줄의 앞뒤 공백을 제거
if (line.startsWith('}')) indentLevel--; // 닫는 중괄호가 있는 경우 들여쓰기 레벨을 먼저 감소
const indentedLine = ' '.repeat(indentLevel * indentSize) + line;
if (line.endsWith('{')) indentLevel++; // 여는 중괄호가 있는 경우 들여쓰기 레벨을 증가
return indentedLine;
})
.join('\n'); // 다시 줄을 합쳐서 반환
setResult(formattedCode);
};
const onClickCopy = () => {
handleCopyClipBoard(result);
};
const handleCopyClipBoard = async (text) => {
try {
await navigator.clipboard.writeText(text);
alert('클립보드에 링크가 복사되었습니다.');
} catch (e) {
alert('복사에 실패하였습니다');
}
};
const modalOpen = () => {
setIsOpen(true);
console.log(isOpen);
};
const modalClose = () => {
setIsOpen(false);
};
return (
<>
<AllowedContainer>
<TopContainer>
<HelpButton modalOpen={modalOpen} />
</TopContainer>
<BaseContainer>
<TextField
text="📝 코드를 입력 하세요"
value={input}
onChange={onChangeInput}
></TextField>
<ButtonWrapper>
<ConvertButton onClick={onClickConvert}></ConvertButton>
<CopyButton onClick={onClickCopy}></CopyButton>
</ButtonWrapper>
<TextField
text="🤖 변환 결과"
value={result}
onChange={onChangeOutput}
></TextField>
</BaseContainer>
</AllowedContainer>
<Modal isOpen={isOpen} modalClose={modalClose} />
</>
);
}
export default Home;
const AllowedContainer = styled.div`
height: 100vh;
padding: 20px;
`;
const TopContainer = styled.div`
width: 100%;
height: 20px;
display: flex;
justify-content: end;
`;
const BaseContainer = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
padding: 20px;
width: 100%;
height: 100%;
align-items: center;
justify-items: center;
@media (max-width: 1200px) {
grid-template-columns: 1fr;
}
`;
const ButtonWrapper = styled.div`
display: grid;
grid-template-rows: 150px auto;
align-items: center;
justify-item: center;
@media (max-width: 1200px) {
grid-template-columns: 150px auto;
}
`;
//TextField.jsx
import styled from 'styled-components';
import TextareaAutosize from 'react-textarea-autosize';
const TextField = ({ text, value, onChange }) => {
return (
<div>
<h2>{text}</h2>
<TextareaStyled
minRows={20}
value={value}
onChange={onChange}
></TextareaStyled>
</div>
);
};
export default TextField;
const TextareaStyled = styled(TextareaAutosize)`
width: 400px;
resize: none;
border-radius: 15px;
outline: none;
border: 3px solid #c4d7ff;
font-size: 14px;
padding: 10px 10px;
font-weight: 400px;
font-family: pretendard;
color: #33333;
overflow: hidden;
@media (min-width: 1500px) {
width: 65vh;
}
@media (max-width: 1200px) {
width: 65vh;
}
&:focus {
border: 5px solid #87a2ff;
transform: scale(1.05);
}
transition: all 0.7s ease;
`;
'React.js > React 프로젝트 및 구현' 카테고리의 다른 글
[React.js] 페이지 있는 게시판 리스트 만들기 (styled-components) (1) | 2024.12.15 |
---|---|
[React.js] 리액트 최적화를 위한 Intersection Observer 사용 (더보기 버튼/스크롤) (2) | 2024.11.02 |
[React.js] 리액트 글자 타이핑 효과 구현 (직접 구현/전체 코드) (0) | 2024.10.27 |
[React.js] 리액트 원페이지 스크롤 구현 (직접 구현/스크롤 유도) (4) | 2024.10.27 |
[React.js] 구글 페이지 번역 (전체 번역 및 언어 선택 버튼) (4) | 2024.10.18 |