https://stackoverflow.com/questions/6575159
Get image dimensions with Javascript before image has fully loaded
I've read about various kinds of ways getting image dimensions once an image has fully loaded, but would it be possible to get the dimensions of any image once it just started to load? I haven't f...
stackoverflow.com
// placeholder로 첫 이미지의 크기를 미리 적용
img.style.width = firstImageSize.width + "px";
img.style.height = firstImageSize.height + "px";
// naturalWidth/Height가 업데이트되면 placeholder 제거 (onload 전에도 가능)
const pollMetadata = () => {
if (img.naturalWidth > 0 && img.naturalHeight > 0) {
// 만약 자연 크기가 placeholder와 다르다면(또는 동일해도)
// 강제로 적용한 스타일을 제거해서 브라우저가 실제 크기로 표시하게 함
img.style.width = "";
img.style.height = "";
} else {
requestAnimationFrame(pollMetadata);
}
};
pollMetadata();
이미지의 메타데이터가 로드되었을 때 호출되는 이벤트는 없다 (onloadstart, onloadedmetadata 모두 <video> 요소를 위한 것임)
폴링을 이용해 <img> 요소의 naturalWidth와 naturalHeight가 변경되는 것을 감지하면 된다.
내 사례에서는 이미지가 로드되기 전까지 다른 이미지의 크기를 반영해놓는 용도로 사용했다.
최근 몇 주 동안, pc이지만 이미지 로드가 매우 느린 환경에서 (1mb짜리 이미지가 로드되는 데 20초 이상) 작업했다.
하지만 내가 서버를 소유한 게 아니라서 reponse로 정확한 이미지 크기를 줄 수 없는 상황.
적절하게 처리하지 못하면 페이지가 고장난 것처럼 보일 것이다.
간단한 스켈레톤 애니메이션을 만들어서 (스켈레톤 용 요소를 만드는 대신) background-color 속성에만 넣었다.
이러면 이미지가 반쯤 로드된 때, 이미지에서 로드된 부분은 이미지가 표시되고 로드되지 않은 부분은 스켈레톤이 대신 표시된다.
나는 이걸로 충분하다고 생각하고 내가 만든 새 뷰어 클라이언트를 이용하려고 했는데, 바로 문제 하나가 발견됐다.
로딩이 시작되어 조금이라도 표시되면 나머지 부분이 바로 스켈레톤으로 차지만, 로딩이 전혀 시작되지 않았다면 크기 자체가 표시되지 않는다.
스켈레톤 용 요소를 따로 만드는 게 가장 원만한 해결책이겠지만, 그렇게 되면 설계를 뜯어 고쳐야 했다.
내 해결 방식은, 다음 이미지가 로드되기 시작될 때까지 전 이미지의 크기를 임시로 사용하는 것이었다.
이러면 크기가 바뀌었으므로 표시는 되겠지만, 임시 크기를 떼고 이미지의 원래 크기를 사용하는 타이밍이 문제였다.
(현재 상황에서는 로딩이 반쯤 진행되어 onload가 호출되기 전인 상황도 흔히 있는 상황인데 그동안 '임시' 크기로 표시됨)
chatgpt한테 물어보니까, requestAnimationFrame을 써서 폴링으로 naturalWidth가 변경될 때까지 기다리라고 한다.
폴링은 할 때마다 성능에 문제를 일으킬까봐 걱정되는데, requestAnimationFrame을 쓰면 상관없다고 확답을 해줬다.
03.22 추가)
저건 안 통한다.
style의 width와 height를 사용해서 지정한 크기는, max-*와 결합되었을 때 가로-세로 비율을 유지하지 않는다. 로드가 시작되지 않은 <img> 태그의 width / height 속성도 마찬가지이다. 반대로 로드가 시작되어 naturalWidth와 naturalHeight가 지정된 <img> 태그는 가로-세로 비율이 유지되어서 당연히 이것도 적용되겠지- 하는 막연한 생각을 하고 있었다가 충격받았다.
이걸 해결하려면 가로-세로 비율을 계산해서 max-*에 걸리지 않을 만큼 작은 크기를 javascript로 계산해서 대입해주면 된다. 나는 대충 이런 느낌의 코드를 짰다:
function calculateLocalSize(w, h) {
const windowW = backgroundElement.scrollWidth;
const windowH = backgroundElement.scrollHeight;
// 창 크기에 비해 이미지 크기가 얼마나 큰지의 비율
// 크기가 같다면 1, 이미지가 크다면 1보다 작음, 창이 더 크면 1보다 큼
const ratioW = windowW / w;
const ratioH = windowH / h;
// 이미지의 가로 / 세로가 모두 창보다 작음: 그대로 반환 가능
if (ratioW >= 1 && ratioH >= 1) {
return { w, h };
}
// 이미지의 가로만 창보다 큼: 가로는 창 크기 사용, 세로는 ratioW를 곱해 반환 (비율이므로)
if (ratioW < 1 && ratioH >= 1) {
return { w: windowW, h: h * ratioW };
}
// 이미지의 세로만 창보다 큼: 가로는 ratioH를 곱해 반환, 세로는 창 크기 사용
if (ratioW >= 1 && ratioH < 1) {
return { w: w * ratioH, h: windowH };
}
// 이미지의 가로 / 세로 모두 창보다 큼: 둘을 비교해서 더 빽빽한 쪽 기준으로 맞춰야 함
if (ratioW < 1 && ratioH < 1) {
if (ratioW < ratioH) {
return { w: windowW, h: h * ratioW };
}
else {
return { w: w * ratioH, h: windowH };
}
}
}
이게 정말 하기 싫어서, aspect-ratio라던가, 여러 방법을 찾아 봤는데 전부 지금 나머지 코드베이스에서 이것저것을 바꿔야 되는 방향이어서 어쩔 수 없이 썼다.
img 태그와 max-* 의 상호작용은 내 생각엔 버그같다.