회사에서 내가 담당하는 서비스의 이벤트 마케팅 페이지를 만드는 업무가 있었다. 별 일 아니라고 생각했는데, 내 생각과 달리 예상치 못한 이슈가 있어 해결하는 과정에서 재밌는 경험을 했었다.
이벤트 페이지의 메인 이미지의 파일 종류는 해상도에 따라 낮은 해상도(x1)와 높은 해상도(x2) 두 가지로 나뉜다. 화면의 너비를 기준으로 너비가 일정 기준(768px) 이하면 낮은 해상도의 이미지를, 이상이면 높은 해상도의 이미지를 화면에 표시하려 한다.
분명 내 컴퓨터에서는 잘 되었다고 생각해서 배포까지 진행했는데, 알고 보니 두 가지 문제가 있었다.
화면을 늘였다 줄였다 해도 같은 해상도의 이미지만을 받아 온다.
맥북의 경우 고해상도 2x 이미지를, 엘지 그램은 저해상도 1x 이미지만을 받아 온다.
이 문제가 나타난 것은 화면 크기에 따른 이미지 최적화가 제대로 이루어지지 않았다는 것을 의미한다.
지금 이미지 최적화 코드는 다음과 같이 img 태그의 srcset
속성을 사용하고 있다.
<!-- src 속성은 srcset 속성을 사용할 수 없는 환경에서 동작한다. -->
<img v-if="mq === 'sm'"
class="main_graphic img-100"
:src="resource('mobile/main_graphic@1x.png')"
:srcset="resourceJoin([
{path: resource('mobile/main_graphic@1x.png'), x: 1},
{path: resource('mobile/main_graphic@2x.png'), x: 2}
])"
>
<img v-else
class="main_graphic img-500"
:src="resource('web/main_graphic@1x.png')"
:srcset="resourceJoin([
{path: resource('web/main_graphic@1x.png'), x: 1},
{path: resource('web/main_graphic@2x.png'), x: 2}
])"
>
클래스명 img-100과 img-500은 각각 모바일과 웹 환경일 때 이미지 태그의 크기에 대한 스타일을 의미한다. 웹과 모바일 환경을 나누는 기준은 화면의 너비이며, 이를 계산해주는 mq
라는 변수의 값에 따라 달라진다. 각 환경에 맞춘 이미지를 따로 준비하여 웹 환경일 때는 웹에 맞춘 이미지를 보여주고 모바일일 때는 모바일에 맞춘 이미지를 보여주도록 한다. 그러나, 현재는 화면의 너비가 달라져도 똑같은 해상도의 이미지만을 받아 온다.
앞서 엘지 그램일 때 저해상도의 이미지가 출력되고, 맥북일 때도 화면 너비와 상관없이 똑같은 고해상도의 이미지가 나온다고 했다. 그렇다면 화면 너비가 제대로 계산되지 않았던 게 아닐까?
화면 너비 계산 로직은 다음과 같다.
mounted() {
this.promotion = window.promotion
if (window.visualViewport) {
this.mq = this.breakpoint(window.visualViewport.width)
} else {
this.mq = this.breakpoint(window.innerWidth)
}
// ...
}
methods: {
breakpoint(vWidth) {
if (768 < vWidth) {
return 'lg'
} else {
return 'sm'
}
},
// ...
}
window.visualViewport
속성 혹은 window.innerWidth
속성을 통해 브라우저 너비를 계산하는 로직이다. 이 두 속성이 제대로 계산되지 않는다면 너비에 따른 이미지 변화가 제대로 이루어지지 않았을 수 있다.
일단 크롬으로 테스트해본 결과 엘지 그램과 맥북 두 환경 모두에서 window.visualViewport.width
가 불러와지지 않는 경우는 없었다. canIUse에 따르면 IE일 때와 타 브라우저의 예전 버전의 경우에는 해당 속성이 지원되지 않는 경우가 있지만, 같은 최신 크롬에서는 디바이스 상관 없이 화면 너비는 제대로 계산된다. 따라서 이 가설 말고 다른 가능성을 생각해보도록 한다.
디바이스의 너비는 엘지 그램과 맥북 둘 다 잘 받아와지는 것 같다. 따라서 디바이스 너비 자체는 문제가 아니다. 그럼 이미지를 받아오는 srcset
에 문제가 있을 수 있지 않을까?
srcset
로직을 다시 한 번 더 보자.
<img v-else
class="main_graphic img-500"
:src="resource('web/main_graphic@1x.png')"
:srcset="resourceJoin([
{path: resource('web/main_graphic@1x.png'), x: 1},
{path: resource('web/main_graphic@2x.png'), x: 2}
])"
>
resourceJoin()
은 Vue 메서드이다. 이 친구는 srcset
에 들어갈 속성을 string으로 가공하는 역할을 한다.
resourceJoin(pathList) {
return pathList.map(x => `${x.path} ${x.x}x`).join(', ')
},
즉 resourceJoin()
함수를 거쳐 나오는 srcset 값은 다음과 같다.
:srcset="web/main_graphic@1x.png 1x, web/main_graphic@2x.png 2x"
srcset
의 x
값은 디바이스의 DPR(devicePixelRatio)이다.
DPR이란 1인치당 CSS 기준 픽셀 수 대비 1인치당 디바이스의 실제 픽셀 수를 의미한다. 디바이스의 해상도가 높아질수록 DPR의 값은 커진다.
위의 코드의 의도는 화면의 해상도에 따라 저해상도외 고해상도 이미지를 구분하여 화면에 띄워주는 것일 테다. DPR 값이 1이면 1x
에 해당하는 이미지(여기서는 web/main_graphic@1x.png
경로에 있는 이미지)를, 2이면 2x
에 해당하는 이미지(web/main_graphic@2x.png
경로에 있는 이미지)를 불러오는 로직이다. 이를 통해서 화면의 크기나 해상도에 따라서 얼마든지 최적화된 이미지 파일을 불러올 수 있다.
문제는 이렇게 했을 때 엘지 그램 컴퓨터의 경우에는 화면이 충분히 넓은데도 불구하고 해상도가 낮은 이미지만 화면에 노출된다는 것이었다. srcset
에서 이미지를 받아오는 기준이 화면 너비가 아닌 DPR이라 문제가 된 걸까?
srcset
의 기준이 되었던 DPR값을 살펴보도록 하자. 자바스크립트를 활용하여 이 값을 알 수 있는데, 다음과 같이 콘솔에 입력해 본다.
window.devicePixelRatio
엘지 그램의 해당 값은 1이지만, 맥북의 값은 2이다. 즉, 화면의 크기가 아닌 디바이스의 DPR 값에 따라 다른 해상도의 이미지를 받아왔으므로 엘지 그램에서는 계속해서 낮은 해상도 이미지를 받아왔던 것이다! 따라서 srcset
의 x
값을 w
값으로 변경하여 DPR 값이 아닌 화면의 너비(width) 값으로 분기가 가능하도록 조치한다.
resourceJoinWithWidth(pathList) {
return pathList.map(x => `${x.path} ${x.w}w`).join(', ')
},
너비를 기준으로 srcset
속성 값으로 들어갈 문자열을 만드는 함수를 새로 작성하고, img
태그의 srcset
속성에 이를 넣는다.
<img v-else
class="main_graphic img-500"
:src="resource('web/main_graphic@1x.png')"
:srcset="resourceJoin([
{path: resource('web/main_graphic@1x.png'), w: 768},
{path: resource('web/main_graphic@2x.png'), w: 1000}
])"
>
이제 브라우저 너비가 768px 이하라면 1x 해상도의 이미지가, 그 이상이면 2x 해상도의 이미지가 출력될 것이다.
제대로 받아오는 것을 볼 수 있다!