본문 바로가기

Frontend/성능 최적화

[블로그 이미지 사이즈 최적화] 웹 성능 최적화까지 해보자-2

이미지 사이즈 최적화

비효율적인 이미지를 분석해보자.

Opportunities 섹션의 첫 번째 항목인 ‘Properly size images’ 부분을 보면 해당 이미지를 적절한 사이즈로 사용하도록 제안한다.

해당 방법을 따르게 되면 용량을 이미지당 대략 200KiB 정도 줄일 수 있고 이미지 로드에 소용되는 시간을 대략 1.65초 정도 단축할 수 있다고 한다.

 

문제가 있는 이미지에 대해서 자세히 살펴보자.

실제 이미지 사이즈는 1200 x 1200 px인데, 화면에 그려지는 이미지의 사이즈는 120 x 120 px이라고 한다.

그래서 이미지 사이즈를 변경해주어야 하는데, 너비 기준으로 두 배 정도 큰 이미지를 사용하면 적당하다.

따라서 240 x 240 px 사이즈로 사용하면 된다.

 

그런데, 해당 이미지는 API를 통해 전달된다.

자체적으로 가지고 있는 정적(static) 이미지라면 사진 편집 툴을 이용해 직접 이미지 사이즈를 조절하면 되지만 API를 통해 받아오는 경우에는 Cloudinary나 Imgix와 같은 이미지 CDN을 사용해야 한다.

 

이미지 CDN

CDN(Content Delivery Network)이란 물리적 거리의 한계를 극복하기 위해 사용자와 가까운 곳에 콘텐츠 서버를 두는 기술을 의미한다.

이미지 CDN은 이미지에 특화된 CDN이라고 볼 수 있다.

기본적인 CDN 기능에 이미지를 사용자에게 보내기 전에 특정 형태로 가공해서 전해주는 기능까지 있다.

이미지 사이즈를 줄이거나 특정 포맷으로 변경하는 등의 작업이 가능하다.

 

이 사이트의 경우 Unsplash 서비스가 일종의 이미지 CDN 역할을 하고 있다.

 
import React from 'react'

import './index.css'

function zeroPad(value, len) {
  const str = '0000000000' + value.toString()
  return str.substring(str.length - len)
}

/* 파라미터 참고: https://unsplash.com/documentation#supported-parameters */
function getParametersForUnsplash({width, height, quality, format}) {
  return `?w=${width}&h=${height}&q=${quality}&fm=${format}&fit=crop`
}

/*
 * 파라미터로 넘어온 문자열에서 일부 특수문자를 제거하는 함수
 * (Markdown으로 된 문자열의 특수문자를 제거하기 위함)
 * */
function removeSpecialCharacter(str) {
  const removeCharacters = ['#', '_', '*', '~', '&', ';', '!', '[', ']', '`', '>', '\n', '=', '-']
  let _str = str
  let i = 0,
    j = 0

  for (i = 0; i < removeCharacters.length; i++) {
    j = 0
    while (j < _str.length) {
      if (_str[j] === removeCharacters[i]) {
        _str = _str.substring(0, j).concat(_str.substring(j + 1))
        continue
      }
      j++
    }
  }

  return _str
}

function Article(props) {
  const createdTime = new Date(props.createdTime)
  return (
    <div className={'Article'}>
      <div className={'Article__summary'}>
        <div className={'Article__summary__title'}>{props.title}</div>
        <div className={'Article__summary__desc'}>{removeSpecialCharacter(props.content)}</div>
        <div className={'Article__summary__etc'}>
          {createdTime.getFullYear() +
            '.' +
            zeroPad(createdTime.getMonth() + 1, 2) +
            '.' +
            zeroPad(createdTime.getDate(), 2)}
        </div>
      </div>
      <div className={'Article__thumbnail'}>
        <img src={props.image + getParametersForUnsplash({width: 1200, height: 1200, quality: 80, format: 'jpg'})} alt="thumbnail" />
      </div>
    </div>
  )
}

export default Article

API를 통해 전달된 props.image 값은 Unsplash 서비스의 이미지를 사용하고 있고, getParametersForUnsplash 함수에서 반환하는 쿼리스트링을 붙여서 이미지를 가공해서 전달받을 수 있게 된다.

결국, getParametersForUnsplash 함수에 전달하는 width와 height 값을 각각 240으로 변경하면 된다는 것이다.

import React from 'react'

import './index.css'

function zeroPad(value, len) {
  const str = '0000000000' + value.toString()
  return str.substring(str.length - len)
}

/* 파라미터 참고: https://unsplash.com/documentation#supported-parameters */
function getParametersForUnsplash({width, height, quality, format}) {
  return `?w=${width}&h=${height}&q=${quality}&fm=${format}&fit=crop`
}

/*
 * 파라미터로 넘어온 문자열에서 일부 특수문자를 제거하는 함수
 * (Markdown으로 된 문자열의 특수문자를 제거하기 위함)
 * */
function removeSpecialCharacter(str) {
  const removeCharacters = ['#', '_', '*', '~', '&', ';', '!', '[', ']', '`', '>', '\n', '=', '-']
  let _str = str
  let i = 0,
    j = 0

  for (i = 0; i < removeCharacters.length; i++) {
    j = 0
    while (j < _str.length) {
      if (_str[j] === removeCharacters[i]) {
        _str = _str.substring(0, j).concat(_str.substring(j + 1))
        continue
      }
      j++
    }
  }

  return _str
}

function Article(props) {
  const createdTime = new Date(props.createdTime)
  return (
    <div className={'Article'}>
      <div className={'Article__summary'}>
        <div className={'Article__summary__title'}>{props.title}</div>
        <div className={'Article__summary__desc'}>{removeSpecialCharacter(props.content)}</div>
        <div className={'Article__summary__etc'}>
          {createdTime.getFullYear() +
            '.' +
            zeroPad(createdTime.getMonth() + 1, 2) +
            '.' +
            zeroPad(createdTime.getDate(), 2)}
        </div>
      </div>
      <div className={'Article__thumbnail'}>
        <img src={props.image + getParametersForUnsplash({width: 240, height: 240, quality: 80, format: 'jpg'})} alt="thumbnail" />
      </div>
    </div>
  )
}

export default Article

 

변경 후 Lighthouse 검사도 다시 해보자.

이미지 최적화 후 점수가 많이 올랐고, Opportunities 섹션에 ‘Properly size images’ 항목이 보이지 않는다.