최근에 회사 웹 사이트의 맨 첫번째 랜딩 페이지를 리디자인하는 작업을 했다. 아무래도 회사의 '얼굴'을 담당하는 곳이다 보니 1, 2px의 디테일과 다양한 디바이스에서 깨지지 않는 것이 1순위였다. 이 과정에서 "생각보다 html, css 에 대해 모르는 부분이 많구나"라는 걸 느꼈다. 작업하면서 AI와 함께 아이디어를 내고, 새롭게 알게 된 것들을 남겨보려고 한다.
그보다 먼저 리뉴얼한 우리 회사 랜딩페이지 구경하고 가세요 :)
👉 https://elice.io/ko
<picture>로 전환한 이유Art direction 이란?
레이아웃에 따라 서로 다른 이미지를 제공하는 문제로, 예로 디바이스 크기에 따라 데스크탑에서는 가로로 긴 이미지, 모바일에서는 세로로 긴 이미지를 제공하는 것을 의미한다.
설명: https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images#art_direction
<img>를 여러 개 두고 display: block/none으로 토글했어요. 보이기만 조절하면 될 줄 알았는데, 네트워크 레벨에선 여전히 불필요한 이미지가 로드될 수 있어 성능에 불리하고, 접근성/유지보수도 좋지 않았습니다.<Box sx={{ position: 'relative' }}>
{/* XS */}
<Box
component="img"
src={image.xs.src}
alt={`${title} mockup`}
sx={{ display: { xs: 'block', sm: 'none' }, width: '100%', height: '100%', objectFit: 'cover' }}
/>
{/* SM */}
<Box
component="img"
src={image.sm.src}
alt={`${title} mockup`}
sx={{ display: { xs: 'none', sm: 'block', md: 'none' }, width: '100%', height: '100%', objectFit: 'cover' }}
/>
{/* MD */}
<Box
component="img"
src={image.md.src}
alt={`${title} mockup`}
sx={{ display: { xs: 'none', md: 'block', lg: 'none' }, width: '100%', height: '100%', objectFit: 'cover' }}
/>
{/* LG */}
<Box
component="img"
src={image.lg.src}
alt={`${title} mockup`}
sx={{ display: { xs: 'none', lg: 'block', xl: 'none' }, width: '100%', height: '100%', objectFit: 'cover' }}
/>
{/* XL */}
<Box
component="img"
src={image.xl.src}
alt={`${title} mockup`}
sx={{ display: { xs: 'none', xl: 'block' }, width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
<picture> + <source> 아트 디렉션import { breakpoints } from '@elice/mui-system';
import { Box } from '@mui/material';
<Box
component="picture"
sx={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<Box component="source" media={`(min-width: ${breakpoints.xl}px)`} srcSet={image.xl.src} />
<Box component="source" media={`(min-width: ${breakpoints.lg}px)`} srcSet={image.lg.src} />
<Box component="source" media={`(min-width: ${breakpoints.md}px)`} srcSet={image.md.src} />
<Box component="source" media={`(min-width: ${breakpoints.sm}px)`} srcSet={(image.sm ?? image.md).src} />
<Box
component="img"
src={(image.xs ?? image.md).src}
alt={`${title} mockup`}
sx={{ width: '100%', height: '100%', objectFit: 'contain', objectPosition: 'bottom' }}
/>
</Box>
이렇게 시행착오를 거쳐, 반응형 이미지는 <picture> 기반으로 표준적인 아트 디렉션을 적용하는 것이 가장 효율적이라는 점을 체감했습니다.
네이버에서 개발한 egjs-infinitegrid와 같은 JS 구현체를 사용해야함
출처: https://naver.github.io/egjs-infinitegrid/ko/
| desktop | laptop | tablet |
|---|---|---|
![]() | ![]() | ![]() |
nth-of-type에 반응형 span을 지정.
<Box
sx={{
display: 'grid',
gap: '24px',
width: '100%',
gridTemplateColumns: 'repeat(6, 1fr)',
// 1번째 카드
'& > *:nth-of-type(1)': {
gridColumn: { xs: '1 / span 6', lg: '1 / 3' },
},
// 2번째 카드
'& > *:nth-of-type(2)': {
gridColumn: { xs: '1 / span 6', md: '1 / 4', lg: '3 / 5' },
},
// 3번째 카드
'& > *:nth-of-type(3)': {
gridColumn: { xs: '1 / span 6', md: '4 / 7', lg: '5 / 7' },
},
// 4번째 카드
'& > *:nth-of-type(4)': {
gridColumn: { xs: '1 / span 6', md: '1 / 4', lg: '1 / 4' },
},
// 5번째 카드
'& > *:nth-of-type(5)': {
gridColumn: { xs: '1 / span 6', md: '4 / 7', lg: '4 / 7' },
},
}}
>
{CARD_DATA.map(card => (
<Card key={card.key} />
))}
</Box>
gridColumn 스팬을 지정.<picture> + <source>로 뷰포트별 최적 이미지 로드, 성능/가독성 향상.nth-of-type 스팬으로 단순하고 강력한 비정형 레이아웃 구현.