ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 비율에 따라 줄어드는 SVG 이미지 구현하기 with CSS 고군분투
    Frontend 2020. 5. 24. 18:37

    이번 주에 회사에서 개발하다가 씨름했던 "비율에 따라 줄어드는 이미지 구현기"를 정리해보려고 한다.

     

    음, 그러니까 주어졌던 요구사항은 이러했다.

     

    1. 우리는 리액트로 개발을 하고 있다.
    2. 이미지는 SVG로 주어지며 되도록이면 .svg 파일이 아닌 SVG를 리턴하는 React 컴포넌트로 처리되었으면 한다.
    3. 예를 들어서 이미지의 사이즈가 300 * 200이고 이미지를 감싸는 컨테이너 사이즈가 400 * 200이라고 해보자.
      이 경우에 이미지의 높이가 200보다 작아진다면 이미지 사이즈는 400 * 200의 비율대로 줄어들어야 한다.
      반대로 이미지의 높이가 200보다 커진다면 이미지는 커지면 안되고 양쪽 여백만 늘어나야 한다.

     

    특정 비율대로 줄어들고 늘어나는 이미지를 구현하는 일은 약간의 CSS 트릭을 쓴다면 그렇게 어렵지는 않다.

    예를 들어서 300 * 200 사이즈의 이미지가 400 * 200의 비율대로 줄어들고 늘어나야 한다면??

     

    See the Pen Resizable image in certain ratio by juyeonH (@JY712) on CodePen.

     

     

     

     

    다음과 같은 trick으로 해결할 수 있다.

     

    .container {
      position: relative;
      overflow: hidden;
      height: 0;
      padding-top: calc(200 / 400 * 100%);
      background: rgba(0, 0, 0, 0.5);
    }

     

    이미지를 감싸는 컨테이너에 위와 같은 css를 줌으로써 전체 높이를 padding-top을 통해 조절하며,

    calc(200 / 400 * 100%)를 통해 400 * 200 비율로 계산하도록 조정한다.

     

     

    img {
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      margin: auto;
      height: 100%;
    }

     

    안의 이미지에는 absolute 포지션에 top, left, bottom, right에 모두 0을 주고 height를 container의 높이로 맞춘다.

    margin: auto로 가운데 정렬을 한다.

     

    위 trick은 W3Schools 사이트에도 나와있는 방법이다.

     

     

    https://www.w3schools.com/howto/howto_css_aspect_ratio.asp

     

    How To - Aspect Ratio / Height Equal to Width

    How TO - Aspect Ratio Learn how to maintain the aspect ratio of an element with CSS. Aspect Ratio Create flexible elements that keep their aspect ratio (4:3, 16:9, etc.) when resized: What is aspect ratio? The aspect ratio of an element describes the propo

    www.w3schools.com

     

     

    이제 위 gif처럼 300 * 200 사이즈의 이미지는 400 * 200의 컨테이너 안에서 

    컨테이너의 비율에 맞춰서 늘어났다 줄어들었다 한다.

     

    이 방법의 문제점은 특정 사이즈 이상 늘어났을 때, (즉, 이미지 높이가 200이 넘어갈 때)

    이미지 크기를 fix 시킬 수 없다는 점이다(!)

    왜냐면 전체 컨테이너의 높이를 height이 아닌 padding-top으로 조정하고 있기 때문이다.

     

    그리고 max-height은 있어도 max-padding-top이란 속성은 없다는 문제가 있다... 😭

     

    그러니까 이미지 높이가 200보다 작아지면 위 gif처럼 줄어들어야 하지만

    이미지 높이가 200보다 커지면 이미지 사이즈를 300 * 200으로 고정하고 양 옆의 여백만 늘어나게 만들어야 한다.

     

     

    여기서 진짜 별의 별 방법을 다 써보면서 해봤는데 결론적으로 다음과 같이 해결했다.

     

    See the Pen Resizable image2 by juyeonH (@JY712) on CodePen.

     

     

     

     

    .container {
      height: calc(200 / 400 * 100vw);
      max-height: 200px;
      background: rgba(0, 0, 0, 0.5);
    }
    
    .image-container {
      width: 100%;
      height: 100%;
      background: url("https://dummyimage.com/300x200/000/fff") no-repeat center / contain;
    }

     

    가장 핵심인 부분은 전체 컨테이너의 max-height을 200px로 고정시키고,

    height200 / 400 * 100vw로 처리했다는 점이다.

    즉, 400 * 200 비율로 컨테이너 높이를 가변적으로 처리했다.

     

    그리고 그 안에 들어가는 이미지를 background로 처리했다.

    이유는 background-size: contain을 통해 쉽게 이미지 영역을 자르지 않고 컨테이너 영역을 꽉 채우기 위해서이다.

     

    전체 컨테이너가 400 * 200 비율로 줄어들었다 늘어났다 하며,

    그 안의 이미지는 부모 컨테이너를 꽉 채우기 때문에 원하던 스펙대로 구현된다.

    게다가 전체 컨테이너에 max-height을 줌으로써 이미지 높이가 200이 넘어가면 더 이상 늘어나지 않도록 구현했다.

     

     

    짜잔~! 🎉

     

     

    이제 문제는 SVG 컴포넌트를 리턴하는 React 컴포넌트Background image로 어떻게 설정하냐는 것이었는데,

    요거는 React에 기본 내장되어 있는 ReactDOMServer의 renderToStaticMarkup 메소드로 해결했다.

     

    ReactDOMServer.renderToStaticMarkup(element)

     

    ReactDOMServer에는 renderToString()과 renderToStaticMarkup()이란 메소드가 있다.

     

    renderToString()

    renderToString은 React 엘리먼트를 최초의 HTML에 렌더하는 메소드로, HTML string을 리턴한다. server에서 HTML을 생성한 후, 페이지 첫 로드 시 빠르게 마크업을 내려줄 때 사용한다. SEO 목적으로 빠르게 서버사이드 렌더할 때 유용한 메소드이다. 

     

     

    renderToStaticMarkup()

    renderToStaticMarkup은 renderToString과 비슷한 컨셉이지만 React가 내부적으로 사용하는 DOM attribute들을 생성하지 않는다. 따라서 간단한 static 페이지를 React로 생성할 때 유용한 메소드이다. 

     

    import { renderToStaticMarkup } from 'react-dom/server';
    import React from 'react';
    
    const SvgComponent = () => (
      <svg xmlns='http://www.w3.org/2000/svg'>
        <rect fill='red' width={100} height={100} />
      </svg>
    );
    
    const ParentComponent = () => {
      const svgString = encodeURIComponent(renderToStaticMarkup(<SvgComponent />));
    
      return (
        <div
          style={{
            backgroundImage: `url('data:image/svg+xml;utf8, ${svgString}')`,
          }}
        />
      );
    };

     

    따라서 renderToStaticMarkup의 인자로 SVG 컴포넌트를 넣은 후 encodeURIComponent로 인코딩하면 background 이미지로 처리할 수 있게 된다.

     

     


     

     

    + 위와 같이 처리했다가 그 다음 날 회사 가서 생각해보니 부모 컨테이너에 height, max-height을 주었기 때문에 자식 컴포넌트에 이미지를 굳이 background로 처리하지 않아도 된다는 것을 깨달았다...

     

    그래서 background로 처리되는 로직을 지우고 부모 컨테이너 안에 svg 컴포넌트를 바로 넣되, svg의 width, height을 '100%'로 변경하여 최종 반영했다~!

     

     

    Reference

     

    반응형

    COMMENT