반응형
메인 페이지 캐러셀 컴포넌트에서 게시글의 제목 / 내용 / 게시 날짜 및 시간을 보여준다.
만약 게시글 내용을 가공(?) 없이 그대로 출력되게 둔다면 다른 아이템의 자리까지 침범하게 된다. 그러므로 게시글의 내용을 미리 보기 할 수 있도록 한 줄 정도만 출력되도록 하고 추가적인 내용이 있다는 의미인 '...'을 붙여줘 보자.
먼저 미리 보기 텍스트를 한 줄 정도 길이로 출력하기 위해 줄 바꿈을 없애준다.
const mergedContent = content.replace(/\n/g, '');
게시글 내용의 html 태그를 없애준 후, 새롭게 만든 div 태그의 값으로 가공한 텍스트를 집어넣는다.
const stripHTMLTags = (html) => {
const tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
};
const sanitizedContent = stripHTMLTags(mergedContent);
그다음, 내용의 길이가 설정한 미리 보기 최대 글자 수를 넘으면 최대 글자수만큼 잘라주고,... 을 붙여준다.
const truncateContent = (text, maxLength) => {
if (text.length > maxLength) {
return `${text.slice(0, maxLength)}...`;
}
return text;
};
const truncatedContent = truncateContent(sanitizedContent, 70);
전체 코드는 다음과 같다.
const truncateContent = (text, maxLength) => {
if (text.length > maxLength) {
return `${text.slice(0, maxLength)}...`;
}
return text;
};
const stripHTMLTags = (html) => {
const tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
};
const mergedContent = content.replace(/\n/g, '');
const sanitizedContent = stripHTMLTags(mergedContent);
const truncatedContent = truncateContent(sanitizedContent, 70);
return 문 안에 다음과 같이 써주면 완성이다.
<Content
style={{
color: isHovered ? '#555555' : '#3f3f3f'
}}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(truncatedContent)
}}
></Content>
+ 이렇게 했더니 vw가 줄어들어도 최대 출력 글자 수가 70 자라 텍스트가 부모 컴포넌트를 삐져나와서 출력되는 현상을 발견했다.
결국 부모 컴포넌트에 width를 설정하는 등, css를 수정하여 해결하였다.
import React, { useState, useCallback, useRef, useEffect } from 'react';
import styled, { keyframes } from 'styled-components';
import DOMPurify from 'isomorphic-dompurify';
import { useNavigate } from 'react-router-dom';
const ItemWrapper = styled.article`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
margin: 10px auto 0px auto;
padding: 20px 20px 10px 20px;
border-radius: 20px;
background-color: #ffffff;
width: 92%;
height: calc(10%);
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: scale(1.01);
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
`;
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const AnimatedCarouselItem = styled.div`
animation: ${fadeIn} 0.5s ease;
width: 100%;
`;
const Title = styled.div`
font-size: 14px;
font-weight: bold;
transition: color 0.3s ease;
white-space: nowrap;
overflow: hidden;
width: 95%;
@media (min-width: 1024px) {
font-size: 16px;
}
@media (min-width: 1440px) {
font-size: 18px;
}
`;
const Content = styled.div`
font-size: 12px;
font-weight: 600;
transition: color 0.3s ease;
margin: 5px 0px 0px 0px;
width: calc(95%);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (min-width: 1024px) {
font-size: 14px;
}
@media (min-width: 1440px) {
font-size: 16px;
}
`;
const Date = styled.span`
font-size: 10px;
font-weight: 300;
align-self: flex-end;
@media (min-width: 1024px) {
font-size: 12px;
}
@media (min-width: 1440px) {
font-size: 14px;
}
`;
const CarouselItem = ({ title, content, createdAt, id }) => {
const [isHovered, setIsHovered] = useState(false);
const [maxCharacters, setMaxCharacters] = useState(62);
const itemWrapperRef = useRef(null);
const navigate = useNavigate();
const formattedDate = new window.Date(createdAt.seconds * 1000);
const handleMouse = useCallback(() => {
setIsHovered(!isHovered);
}, [isHovered]);
const handleNavigate = useCallback(() => {
navigate(`/posts/${id}`);
}, [id]);
const stripHTMLTags = (html) => {
const tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
};
const mergedContent = content.replace(/\n/g, '');
const sanitizedContent = stripHTMLTags(mergedContent);
const options = {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: true
};
return (
<ItemWrapper
onMouseEnter={handleMouse}
onMouseLeave={handleMouse}
style={{
backgroundColor: isHovered ? '#f8f8f8' : '#ffffff'
}}
onClick={handleNavigate}
>
<AnimatedCarouselItem>
<Title
style={{
color: isHovered ? '#9eb23b' : '#3f3f3f'
}}
>
{title}
</Title>
{sanitizedContent && (
<Content
style={{
color: isHovered ? '#555555' : '#3f3f3f'
}}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(sanitizedContent)
}}
></Content>
)}
{/* {truncatedContent && (
<Content
style={{
color: isHovered ? '#555555' : '#3f3f3f'
}}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(truncatedContent)
}}
></Content>
)} */}
</AnimatedCarouselItem>
<Date>{formattedDate.toLocaleString('ko-KR', options)}</Date>
</ItemWrapper>
);
};
export default CarouselItem;
참고한 블로그)
https://devbirdfeet.tistory.com/140
반응형