압축 및 인코딩 자동화

개발 프로세스에서 고성능 이미지 소스를 원활하게 생성할 수 있습니다.

이미지 데이터의 인코딩에서부터 반응형 이미지를 구동하는 정보 집약 마크업에 이르기까지 이 과정의 모든 구문은 머신과 통신하는 방법입니다. 클라이언트 브라우저가 서버에 요구사항을 전달하고 서버가 종류에 따라 응답하는 여러 가지 방법을 알아봤습니다. 반응형 이미지 마크업 (특히 srcsetsizes)을 사용하면 비교적 적은 수의 문자로 충격적인 정보를 설명할 수 있습니다. 좋든 나쁘든 이는 간결하게 설계한 것입니다. 이러한 구문을 덜 간결하고 개발자가 파싱하기 쉬우면 브라우저에서 파싱하기 더 어려워질 수 있습니다. 문자열에 복잡성이 더 많이 추가될수록 파서 오류가 발생하거나 브라우저 간 동작의 의도하지 않은 차이가 발생할 가능성이 커집니다.

자동 이미지 인코딩 기간

그러나 이러한 주체들이 무섭게 느끼게 하는 동일한 특성이 해결책을 제공할 수도 있습니다. 기계가 쉽게 읽는 문법은 컴퓨터가 더 쉽게 작성하는 문법입니다. 웹 사용자는 거의 확실하게 자동화된 이미지 인코딩 및 압축의 예를 많이 접했을 것입니다. 소셜 미디어 플랫폼, 콘텐츠 관리 시스템 (CMS), 이메일 클라이언트를 통해 웹에 업로드되는 모든 이미지는 거의 변함없이 크기를 조절하고, 다시 인코딩하고, 압축하는 시스템을 통과합니다.

마찬가지로 플러그인, 외부 라이브러리, 독립형 빌드 프로세스 도구 또는 클라이언트 측 스크립팅의 책임 있는 사용을 통해 반응형 이미지 마크업은 그 자체로 자동화에 쉽게 이릅니다.

이미지 성능 자동화와 관련된 두 가지 주요 우려사항은 이미지 생성 관리(인코딩, 압축, srcset 속성을 채우는 데 사용할 대체 소스)와 사용자 대상 마크업 생성입니다. 이 모듈에서는 개발 프로세스의 자동화된 단계로, 사이트를 지원하는 프레임워크 또는 콘텐츠 관리 시스템을 통해, 또는 전용 콘텐츠 전송 네트워크를 통해 거의 완전히 추상화하여 최신 워크플로의 일부로 이미지를 관리하는 일반적인 접근 방식을 알아봅니다.

압축 및 인코딩 자동화

프로젝트에 사용할 각 개별 이미지의 이상적인 인코딩과 압축 수준을 수동으로 결정할 수 있는 상황은 거의 없으며, 그렇게 해서도 바람직하지 않습니다. 이미지 전송 크기를 최대한 작게 유지하는 것이 중요하고 압축 설정을 미세 조정하고 프로덕션 웹사이트를 대상으로 하는 모든 이미지 애셋의 대체 소스를 다시 저장하면 일상 업무에 큰 병목 현상이 발생할 수 있습니다.

다양한 이미지 형식과 압축 유형에 관해 알아볼 때 배운 것처럼, 이미지의 가장 효율적인 인코딩은 항상 콘텐츠에 따라 결정되며, 반응형 이미지에서 알아본 것처럼 이미지 소스에 필요한 대체 크기는 페이지 레이아웃에서 이미지가 차지하는 위치에 따라 결정됩니다. 최신 워크플로에서는 이러한 결정에 개별적으로가 아니라 종합적으로 접근합니다. 즉, 이미지를 사용할 맥락에 가장 적합하도록 이미지의 적절한 기본값 집합을 결정합니다.

사진 이미지 디렉터리의 인코딩을 선택할 때 품질과 전송 크기 측면에서는 AVIF가 가장 낫지만 지원이 제한되어 있으며 WebP가 최적화된 최신 대체를 제공하며 JPEG가 가장 안정적인 기본값입니다. 페이지 레이아웃에서 사이드바를 차지하기 위한 이미지에 필요한 대체 크기는 가장 높은 중단점에서 전체 브라우저 표시 영역을 차지하기 위한 이미지와 크게 다릅니다. 압축 설정에서는 여러 결과 파일에서 아티팩트를 흐리게 처리하고 압축하는 데 신경을 써야 합니다. 그 결과 더 유연하고 안정적인 워크플로를 위해 각 이미지에서 가능한 모든 바이트를 만들 공간이 줄어듭니다. 요약하면, 이 과정에서 배운 것과 동일한 의사결정 프로세스를 따르게 됩니다.

처리 자체의 경우, 이미지를 일괄적으로 변환, 수정, 편집하여 속도, 효율성, 신뢰성을 두고 경쟁하는 방법을 제공하는 오픈소스 이미지 처리 라이브러리가 많습니다. 이러한 처리 라이브러리를 사용하면 이미지 편집 소프트웨어를 열지 않고도 인코딩 및 압축 설정을 전체 이미지 디렉터리에 한 번에 적용할 수 있으며, 설정을 즉시 조정해야 하는 경우 원본 이미지 소스를 보존할 수 있습니다. 로컬 개발 환경에서 웹 서버 자체에 이르기까지 다양한 컨텍스트에서 실행되도록 설계되었습니다. 예를 들어 Node.js용 압축 중심 ImageMin은 일련의 플러그인을 통해 특정 애플리케이션에 맞게 확장할 수 있는 반면, 크로스 플랫폼 ImageMagick과 Node.js 기반 Sharp는 엄청난 수의 기능을 제공합니다.

이러한 이미지 처리 라이브러리를 사용하면 개발자가 표준 개발 프로세스의 일환으로 이미지를 원활하게 최적화하는 전용 도구를 빌드할 수 있으므로 프로젝트에서 항상 최소한의 오버헤드로 프로덕션에 즉시 사용 가능한 이미지 소스를 참조할 수 있습니다.

로컬 개발 도구 및 워크플로

작업 실행자 및 번들러(예: Grunt, Gulp 또는 Webpack)를 사용하면 CSS 및 자바스크립트 축소와 같은 다른 일반적인 성능 관련 작업과 함께 이미지 애셋을 최적화할 수 있습니다. 비교적 간단한 사용 사례를 살펴보겠습니다. 프로젝트의 디렉터리에 공개 웹사이트에 사용되는 12개의 사진 이미지가 포함되어 있습니다.

먼저 이러한 이미지를 일관되고 효율적으로 인코딩해야 합니다. 이전 모듈에서 배운 것처럼 WebP는 품질과 파일 크기 측면에서 사진 이미지의 효율적인 기본값입니다. WebP는 지원되지만 범용으로 지원되지는 않으므로 프로그레시브 JPEG 형식으로 대체도 포함하는 것이 좋습니다. 그런 다음 이러한 애셋을 효율적으로 전송하기 위해 srcset 속성을 사용하려면 각 인코딩에 여러 대체 크기를 생성해야 합니다.

이미지 편집 소프트웨어를 사용하면 이 작업이 반복되고 시간도 오래 걸릴 수 있지만, Gulp와 같은 작업 실행기는 정확히 이런 종류의 반복을 자동화하도록 설계되었습니다. Sharp를 사용하는 gulp-responsive 플러그인은 비슷한 패턴을 따르는 여러 옵션 중 하나입니다. 소스 디렉터리에서 모든 파일을 수집하고, 다시 인코딩하고, 이미지 형식과 압축에서 배운 것과 동일한 표준화된 '품질' 약칭에 따라 압축하는 것입니다. 그러면 결과 파일이 정의된 경로로 출력되고 원본 파일은 그대로 두고 사용자에게 표시되는 img 요소의 src 속성에서 참조할 수 있습니다.

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.webp = function() {
  return src('./src-img/*')
    .pipe(respimg({
      '*': [{
        quality: 70,
        format: ['webp', 'jpeg'],
        progressive: true
      }]
  }))
  .pipe(dest('./img/'));
}

이와 같은 프로세스가 적용된 상태에서는 프로젝트의 누군가가 실수로 대규모 트루컬러 PNG로 인코딩된 사진을 원본 이미지 소스가 포함된 디렉터리에 추가하더라도 프로덕션 환경에 미치는 영향이 없습니다. 원본 이미지의 인코딩과 관계없이 이 작업은 효율적이고 안정적인 프로그레시브 JPEG 대체를 생성하며 압축 수준에서 즉시 쉽게 조정할 수 있습니다. 물론 이 프로세스를 통해 원본 이미지 파일이 프로젝트의 개발 환경에 유지됩니다. 즉, 자동 출력만 덮어쓴 상태에서 언제든지 이러한 설정을 조정할 수 있습니다.

여러 파일을 출력하려면 width 키 및 픽셀 값 추가를 제외하고는 모두 동일한 여러 구성 객체를 전달합니다.

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.default = function() {
  return src('./src-img/*')
    .pipe(respimg({
    '*': [{
            width: 1000,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-1000' }
            },
            {
            width: 800,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-800' }
            },
            {
            width: 400,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-400' },
        }]
        })
    )
    .pipe(dest('./img/'));
}

위 예에서는 원본 이미지 (monarch.png)가 3.3MB를 넘었습니다. 이 작업으로 생성된 가장 큰 파일 (monarch-1000.jpeg)은 약 150KB입니다. 가장 작은 파일인 monarch-400.web은 32KB에 불과합니다.

[10:30:54] Starting 'default'...
[10:30:54] gulp-responsive: monarch.png -> monarch-400.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-800.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-400.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-800.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.webp
[10:30:54] gulp-responsive: Created 6 images (matched 1 of 1 image)
[10:30:54] Finished 'default' after 374 ms

물론 표시되는 압축 아티팩트의 결과를 신중하게 검토하거나 추가 비용 절감을 위해 압축을 늘리는 것이 좋습니다. 이 작업은 비파괴적이므로 이러한 설정을 쉽게 변경할 수 있습니다.

하지만 세심한 수동 마이크로 최적화를 통해 몇 킬로바이트를 줄일 수 있는 대가로, 효율적일 뿐만 아니라 복원력이 뛰어난 프로세스를 얻을 수 있습니다. 이 도구는 수동 개입 없이 고성능 이미지 애셋에 관한 지식을 프로젝트 전체에 원활하게 적용하는 도구입니다.

반응형 이미지 마크업의 실제 사용 사례

srcset 속성을 채우는 방법은 일반적으로 간단한 수동 프로세스입니다. 이 속성은 실제로 소스를 생성할 때 이미 완료한 구성에 관한 정보만 캡처하기 때문입니다. 위 작업에서는 속성이 따를 파일 이름과 너비 정보를 설정했습니다.

srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w"

srcset 속성의 콘텐츠는 처방적이 아닌 설명적이라는 점에 유의하세요. 모든 소스의 가로세로 비율이 동일하다면 srcset 속성을 오버로드해도 문제가 없습니다. srcset 속성에는 불필요한 요청을 유발하지 않고 서버에서 생성한 모든 대체 컷의 URI와 너비를 포함할 수 있으며 렌더링된 이미지에 대해 더 많은 후보 소스를 제공할수록 브라우저에서 요청을 더 효율적으로 맞춤설정할 수 있습니다.

반응형 이미지에서 알아본 것처럼 <picture> 요소를 사용하여 WebP 또는 JPEG 대체 패턴을 원활하게 처리하는 것이 좋습니다. 여기서는 srcset와 함께 type 속성을 사용합니다.

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

앞서 배운 것처럼 WebP를 지원하는 브라우저는 type 속성의 콘텐츠를 인식하고 <source> 요소의 srcset 속성을 이미지 후보 목록으로 선택합니다. image/webp를 유효한 미디어 유형으로 인식하지 않는 브라우저는 이 <source>를 무시하는 대신 내부 <img> 요소의 srcset 속성을 사용합니다.

브라우저 지원 측면에서 한 가지 더 고려해야 할 사항이 있습니다. 반응형 이미지 마크업을 지원하지 않는 브라우저도 대체가 필요합니다. 그러지 않으면 특히 오래된 탐색 컨텍스트에서 이미지가 손상될 위험이 있습니다. <picture>, <source>, srcset는 이러한 브라우저에서 모두 무시되므로 내부 <img>src 속성에 기본 소스를 지정하려고 합니다.

이미지 축소는 시각적으로 원활하고 JPEG 인코딩이 보편적으로 지원되므로 가장 큰 JPEG가 합리적인 선택입니다.

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img src="filename-1000.jpg" srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

sizes는 다루기 더 어려울 수 있습니다. 학습한 바와 같이 sizes는 맥락에 반드시 부합합니다. 렌더링된 레이아웃에서 이미지가 차지해야 할 공간을 모르고는 속성을 채울 수 없습니다. 가장 효율적인 요청을 위해서는 최종 사용자가 요청할 때 페이지 레이아웃에 적용되는 스타일이 요청되기 훨씬 전에 마크업에 정확한 sizes 속성이 있어야 합니다. sizes를 모두 생략하면 HTML 사양을 위반하는 것뿐만 아니라 sizes="100vw"와 같은 기본 동작이 발생합니다. 따라서 이 이미지는 표시 영역 자체에서만 제한되므로 가능한 가장 큰 후보 소스가 선택된다고 브라우저에 알립니다.

특히 부담스러운 웹 개발 작업의 경우와 마찬가지로 sizes 속성을 직접 작성하는 프로세스를 추상화하기 위해 많은 도구가 만들어졌습니다. respImageLintsizes 속성의 정확성을 검증하고 개선을 위한 추천을 제공하기 위한 필수적인 코드 스니펫입니다. 이는 이미지 요소가 포함된 완전히 렌더링된 페이지를 가리키는 동안 브라우저에서 실행하는 도구인 북마크로 실행됩니다. 브라우저가 페이지 레이아웃을 완전히 이해하는 컨텍스트에서는 가능한 모든 표시 영역 크기에서 이미지가 레이아웃에서 차지해야 하는 공간을 거의 픽셀 단위로 완벽히 인식합니다.

크기/너비 불일치를 보여주는 반응형 이미지 보고서

sizes 속성 린트를 위한 도구는 확실히 유용하지만 도매로 생성하는 도구로서 훨씬 더 많은 가치가 있습니다. 아시다시피 srcsetsizes 문법은 시각적으로 원활한 방식으로 이미지 확장 소재에 대한 요청을 최적화하기 위한 것입니다. 프로덕션에서 사용하면 안 되는 것은 아니지만, 로컬 개발 환경에서 페이지 레이아웃 작업을 하는 동안에는 기본 sizes 자리표시자 값 100vw이 매우 적합합니다. 레이아웃 스타일이 배치되고 나면 respImageLint를 실행하여 맞춤설정한 sizes 속성을 제공하며, 이 속성을 복사하여 마크업에 붙여넣을 수 있습니다. 직접 작성한 것보다 훨씬 더 세부적인 수준으로 제공됩니다.

추천 크기가 포함된 반응형 이미지 보고서

서버 렌더링 마크업으로 시작된 이미지 요청은 자바스크립트가 클라이언트 측 sizes 속성을 생성하기에는 너무 빠르게 발생하지만 이러한 요청이 클라이언트 측에서 시작되는 경우에는 동일한 추론이 적용되지 않습니다. 예를 들어 Lazysizes 프로젝트를 사용하면 레이아웃이 설정될 때까지 이미지 요청을 완전히 연기할 수 있으므로 JavaScript가 sizes 값을 자동으로 생성할 수 있습니다. 이는 매우 편리하고 사용자에게 가장 효율적인 요청을 보장합니다. 그러나 이 접근 방식은 서버 렌더링 마크업의 안정성과 브라우저에 빌드된 속도 최적화를 희생하게 되며, 페이지가 렌더링된 후에만 이러한 요청을 시작하면 LCP 점수에 막대한 부정적인 영향을 미칩니다.

물론, React 또는 Vue와 같은 클라이언트 측 렌더링 프레임워크를 이미 사용하고 있다면 이는 이미 발생하고 있는 부채입니다. 이 경우 Lazysizes를 사용하면 sizes 속성을 거의 완전히 추상화할 수 있습니다. 더 좋은 방법도 있습니다. 지연 로드 이미지의 sizes="auto"가 합의와 네이티브 구현을 얻으면서 Lazysizes는 새로 표준화된 브라우저 동작을 위한 폴리필이 됩니다.