png, jpg, webp 비교
png
무손실압축 방식
알파채널(투명도) 지원 => 배경색을 투명하게 해서 뒤의 요소가 보이는 이미지 만들기 가능
고화질 혹은 투명도 정보가 필요하면 png 사용
jpg
압축 시 손실 많이 발생
더 작은 사이즈로 줄일 수 있음 => 고화질 혹은 투명도 정보가 필요한게 아니면 jpg 사용
webp
무손실압축, 손실압축 모두 제공(최신이미지 포맷) => png, jpg 비해서 효율적인 이미지 압축
(png 대비 26%, jpg 대비 25~34% 고효율)
하지만 최신 포맷(2010년 발표)이라서 지원하지 않는 브라우저가 있을 수 있음
- 사이즈: png > jpg > webp
- 화질: png === webp > jpg
- 호환성: png === jpg > webp
압축해보기
구글에서 만든 이미지 포맷 변경이 가능한 웹사이트입니다.
다음에서 비트맵 이미지 확장자를 변경할 수 있습니다(jpg, png, webp)
저는 여기서 jpg를 webp 확장자로 바꾸려고 합니다.
jpg vs webp 로딩 속도 비교
webp가 차이가 나게 다운로드 시간도 빠르고, 용량도 적은 것을 확인할 수 있습니다.
jpg로 렌더링 시, 용량 및 다운로드 시간
webp로 렌더링 시, 용량 및 다운로드 시간
webp를 최우선으로, 만약 호환이 안된다면 jpg 혹은 png로 바꾸면 딱 좋을 듯!
webp를 최우선으로, 만약 호환이 안된다면 jpg 혹은 png로 바꾸려면,
picture 태그 안에 꼭 넣어야합니다.
picture 태그는 다양한 타입의 이미지를 렌더링하는 컨테이너로 사용됩니다.
// 예시: 뷰포트별 이미지 변경
<picture>
<source media="(min-width:650px)" srcset="img_pink_flowers.jpg">
<source media="(min-width:465px)" srcset="img_white_flower.jpg">
<img src="img_orange_flowers.jpg" alt="Flowers" style="width:auto;">
</picture>
// 예시: 이미지 포맷별 구분
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="img_orange_flowers.jpg" alt="Flowers">
</picture>
다음과 같이 적용할 수 있습니다.
import React, { useEffect, useRef } from "react";
function Card(props) {
const imgRef = useRef(null);
useEffect(() => {
const options = {};
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const target = entry.target;
const previousSibling = target.previousSibling;
console.log("is intersecting", entry.target.dataset.src);
target.src = target.dataset.src;
previousSibling.srcset = previousSibling.dataset.srcset;
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(callback, options);
observer.observe(imgRef.current);
}, []);
return (
<div className="Card text-center">
<picture>
<source data-srcset={props.webp} type="image/webp" />
<img data-src={props.image} ref={imgRef} />
</picture>
<div className="p-5 font-semibold text-gray-700 text-xl md:text-lg lg:text-xl keep-all">
{props.children}
</div>
</div>
);
}
export default Card;
import React, { useEffect, useRef } from "react";
import BannerVideo from "../components/BannerVideo";
import ThreeColumns from "../components/ThreeColumns";
import TwoColumns from "../components/TwoColumns";
import Card from "../components/Card";
import Meta from "../components/Meta";
import main1_webp from "../assets/_main1.webp";
import main2_webp from "../assets/_main2.webp";
import main3_webp from "../assets/_main3.webp";
import main_items_webp from "../assets/_main-items.webp";
import main_parts_webp from "../assets/_main-parts.webp";
import main_styles_webp from "../assets/_main-styles.webp";
import main1 from "../assets/_main1.jpg";
import main2 from "../assets/_main2.jpg";
import main3 from "../assets/_main3.jpg";
import main_items from "../assets/_main-items.jpg";
import main_parts from "../assets/_main-parts.jpg";
import main_styles from "../assets/_main-styles.jpg";
function MainPage(props) {
const imgEl1 = useRef(null);
const imgEl2 = useRef(null);
const imgEl3 = useRef(null);
useEffect(() => {
const options = {};
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log(entry.target.dataset.src);
const sourceEl = entry.target.previousSibling;
sourceEl.srcset = sourceEl.dataset.srcset;
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
};
let observer = new IntersectionObserver(callback, options);
observer.observe(imgEl1.current);
observer.observe(imgEl2.current);
observer.observe(imgEl3.current);
}, []);
return (
<div className="MainPage -mt-16">
<BannerVideo />
<div className="mx-auto">
<ThreeColumns
columns={[
<Card webp={main1_webp} image={main1}>
롱보드는 아주 재밌습니다.
</Card>,
<Card webp={main2_webp} image={main2}>
롱보드를 타면 아주 신납니다.
</Card>,
<Card webp={main3_webp} image={main3}>
롱보드는 굉장히 재밌습니다.
</Card>,
]}
/>
<TwoColumns
bgColor={"#f4f4f4"}
columns={[
<picture>
<source data-srcset={main_items_webp} type="image/webp" />
<img data-src={main_items} ref={imgEl1} alt="" />
</picture>,
<Meta
title={"Items"}
content={
"롱보드는 기본적으로 데크가 크기 때문에 입맛에 따라 정말 여러가지로 변형된 형태가 나올수 있습니다. 실제로 데크마다 가지는 모양, 재질, 무게는 천차만별인데, 본인의 라이딩 스타일에 맞춰 롱보드를 구매하시는게 좋습니다."
}
btnLink={"/items"}
/>,
]}
/>
<TwoColumns
bgColor={"#fafafa"}
columns={[
<Meta
title={"Parts of Longboard"}
content={
"롱보드는 데크, 트럭, 휠, 킹핀, 베어링 등 여러 부품들로 구성됩니다. 롱보드를 타다보면 조금씩 고장나는 부품이 있기 마련인데, 이럴때를 위해 롱보들의 부품들에 대해서 알고 있으면 큰 도움이 됩니다."
}
btnLink={"/part"}
/>,
<picture>
<source data-srcset={main_parts_webp} type="image/webp" />
<img data-src={main_parts} ref={imgEl2} alt="" />
</picture>,
]}
mobileReverse={true}
/>
<TwoColumns
bgColor={"#f4f4f4"}
columns={[
<picture>
<source data-srcset={main_styles_webp} type="image/webp" />
<img data-src={main_styles} ref={imgEl3} alt="" />
</picture>,
<Meta
title={"Riding Styles"}
content={
"롱보드 라이딩 스타일에는 크게 프리스타일, 다운힐, 프리라이딩, 댄싱이 있습니다. 보통 롱보드는 라이딩 스타일에 따라 데크의 모양이 조금씩 달라집니다. 많은 롱보드 매니아들이 각 쓰임새에 맞는 보드들을 소유하고 있습니다."
}
btnLink={"/riding-styles"}
/>,
]}
/>
</div>
</div>
);
}
export default MainPage;