금요일 오후, 인쇄 기능 구현 업무를 받았습니다.
레퍼런스 코드도 있고, 기능 구현자체는 어렵지 않기에, 칼퇴근할 수 있다는 부푼 희망을 안고 작업에 임했습니다.
하지만... 퇴근시간이 훌쩍 지나도 작업은 끝나지 않았습니다 🥲
이유는, 인쇄할 화면과 인쇄된 화면이 스타일이 계속해서 맞지 않은 버그가 있었기 때문입니다.
수많은 삽질 끝에 해답을 찾을 수 있었지만, 들였던 시간에 비해 너무나 간단한 해법을 찾아버렸고 저처럼 뻘짓을 안하고 다른 분들의 시간을 아껴드리고자 글로 해결과정을 작성해보았습니다.
print는 브라우저에서 제공해주는 API로 iframe을 생성 한뒤에 메서드를 호출하여 print 기능을 사용할 수 있습니다.
미리보기 페이지와 iframe은 요소가 조금 달랐습니다. A4 용지에 깔끔하게 보여야 하기 때문에 미리 보기 페이지와는 다른 레이아웃을 적용해야하고, header와 footer도 디자인이 달라서 새롭게 만들어야했습니다.
const generateBusinessCard = (person) => `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${person.name} - 명함</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.business-card {
width: 350px;
margin: 20px auto;
border: 2px solid #000;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
border-radius: 8px;
overflow: hidden;
}
...
</style>
</head>
<body>
<div class="company-logo"></div>
<div class="business-card">
<div class="header">
<h2>${person.name}</h2>
<div class="title">${person.position}</div>
</div>
<div class="info">
<div class="info-item">
<span class="icon">📧</span>
<span>${person.email}</span>
</div>
<div class="info-item">
<span class="icon">📱</span>
<span>${person.phone}</span>
</div>
<div class="info-item">
<span class="icon">🏢</span>
<span>${person.company}</span>
</div>
<div class="highlight">
${person.motto}
</div>
</div>
</div>
</body>
</html>
`;
// print를 보여줄 iframe생성 및 핸들링
const printUtils = (data) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
if (iframe.contentDocument) {
iframe.contentDocument.write(generateBusinessCard(data));
iframe.contentDocument.close();
}
iframe.contentWindow?.addEventListener('afterprint', () => {
document.body.removeChild(iframe);
});
setTimeout(() => {
iframe.contentWindow?.print();
}, 500);
};
// 사용
<Button onClick={() => printUtils(data)} />

인쇄는 잘 동작하지만, 문제가 하나 있었습니다. 미리보기 페이지와 iframe의 인쇄할 화면이 CSS가 상이하게 나오고 있었습니다.
예를 들어, border의 두께가 작아진다던지, background-color가 안보인다던지, 등등의 문제가 있었습니다.


미리보기 페이지는 React/MantineCSS 를 사용하고 있어, Pure HTML/CSS와는 동작방식이 차이가 있기에 초점을 그곳에 두고 계속해서 디버깅을 Cascading을 일일히 추적해보면서 css를 수정해봤습니다.
그래도 문제가 해결되지 않아 최후의 수단인 !important를 사용해서 css를 강제해봤지만, 문제는 해결되지 않았습니다...
border: 1px solid black !important;
border: 3px solid black !important;
border: 5px solid #000000 !important;
// 울고 싶다... 🤪
cascading에서 벗어나 다른 관점으로 생각해봤습니다.
인쇄 관련하여 구글링을 시도해봤습니다. 찾아본 결과, 인쇄를 할 때 동일한 문제를 겪는 사람들이 꽤 많았습니다. 대채로 색상이 제대로 인쇄되지 않는다는 제 문제와 비슷한 문제들이었습니다.
문제의 해결법은 바로 설정으로 '배경 그래픽'을 활성화 시켜줘야 한다는 것이었습니다.


우오오... ! 활성화 이후 실제 인쇄 테스트를 거친 결과 제대로 색상이 나오고 있었습니다 🎉
이제 '배경 그래픽'을 자동으로 활성화 해주면 됩니다. CSS에서 그 해답을 찾을 수 있었습니다.
바로 print-color-adjust CSS 프로퍼티입니다. 속성 이름 그대로 print color의 적용 값을 지정할 수 있습니다.
해당 속성의 값은 economy, exact 두 가지가 있습니다. 문제의 원인은 이 프로퍼티의 기본값이 economy 였습니다.
economy: 브라우저가 잉크/토너 절약을 위해 자유롭게 조정할 수 있는 모드exact: 개발자가 지정한 색상과 스타일을 정확히 그대로 출력하는 모드기본값이 economy 였기에 잉크/토너를 절약하기 위해 브라우저가 스스로 조정하고 있던 것이 원인이었네요
<style>
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
color-adjust: exact !important;
}
...
</style>

긴 디버깅 이후, 브라우저를 원망하며 '왜 이렇게 동작할까?'를 고민해봤습니다.
print-color-adjust가 동작하는 방식을 찾아보았지만, 마땅한 정보가 나오지는 않았습니다.
따라서 혼자만의 가설로 economy 없이 모든 인쇄를 그대로 바로 해준다고 가정을 해봤습니다. 이 때, 발생할 수 있는 문제점은 '잉크 값'이라는 생각이 들었습니다.


카트리지와 컬러 프린트는 가격이 꽤 나갑니다. 이미지나 색상을 많이 추가할수록 잉크가 더 나갈겁니다.
브라우저는 인쇄물을 읽기에 지장이 없는 요소들은 색상을 변경하여 잉크를 최소한으로 사용하여 유저의 지갑을 지켜주는 방향으로 설계 된 것 같습니다.
예시 화면을 보면, 배경 색상이 적용되어있고, 폰트 색상이 white일 경우는 배경색을 빼버리고 폰트 색을 black, gray 계열로 변경한 것을 확인할 수 있었습니다.
따라서 컬러 프린트를 사용하지만, 프린트의 잉크를 최소화하기 위해 설계되었으리라 추측됩니다.
브라우저 개발자들은 사실 클라이언트에 매우 진심이었음을 알 수 있었습니다. 유저들의 프린트 리소스도 챙겨주니까 말이죠
저는 야근을 해버렸지만, 이건 제가 디버깅 방식을 잘못 채택했기에 발생한 문제라 생각하고, 그들의 진심을 다시 느낄 수 있는 시간이었습니다.
서비스를 만드는 입장에서 어디까지 유저를 고려할 수 있는지 다시 한번 고민하게 되었습니다.
오 저도 이번에 진행하고 있는 프로젝트에서 print api 사용 할 것 같은데
덕분에 같은 문제가 발생하더라도 대처가 쉽게 될 것 같네요 감사합니다.
추가로 추측이라고는 하지만 개발자의 의도를 파악하시는 과정에서 한수 배우고 갑니다~!
금요일 칼퇴 노리다가 야근한 썰 너무 공감돼요 😂 특히 !important 쓰다가 울고 싶었다는 부분에서 빵터졌습니다 ㅋㅋ 저도 비슷한 경험이 있어서 그 답답함이 그대로 느껴지네요. 브라우저가 잉크 절약을 위해 economy 모드를 기본값으로 한다는 관점도 흥미로웠어요.
'print-color-adjust' 속성값에 대해 처음 알게 되었습니다! print기능을 만들게 된다면, 기획자분과 어떻게 고안을 할 지 토론을 해보아야겠네요! 좋은 글 잘 읽었습니다! 고생 많으셨습니다.
ㅋㅋㅋ 너무 공감가네요 하나의 썰을 보는 것 같아 재미있게 읽고 갑니다.