왜 화면과 다르게 인쇄되지?! - PRINT CSS 디버깅 여정

꾸개·2025년 8월 17일
0

Web

목록 보기
3/3
post-thumbnail

분노의 야근 회고

금요일 오후, 인쇄 기능 구현 업무를 받았습니다.

레퍼런스 코드도 있고, 기능 구현자체는 어렵지 않기에, 칼퇴근할 수 있다는 부푼 희망을 안고 작업에 임했습니다.

하지만... 퇴근시간이 훌쩍 지나도 작업은 끝나지 않았습니다 🥲

이유는, 인쇄할 화면과 인쇄된 화면이 스타일이 계속해서 맞지 않은 버그가 있었기 때문입니다.

수많은 삽질 끝에 해답을 찾을 수 있었지만, 들였던 시간에 비해 너무나 간단한 해법을 찾아버렸고 저처럼 뻘짓을 안하고 다른 분들의 시간을 아껴드리고자 글로 해결과정을 작성해보았습니다.

인쇄하기

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으로 html이 나옵니다.

왜 화면과 다르게 나오지?!

인쇄는 잘 동작하지만, 문제가 하나 있었습니다. 미리보기 페이지와 iframe의 인쇄할 화면이 CSS가 상이하게 나오고 있었습니다.

예를 들어, border의 두께가 작아진다던지, background-color가 안보인다던지, 등등의 문제가 있었습니다.

  • 렌더링되는 미리보기는 첫 번째인데, 인쇄를 시도하면 상이하게 나왔습니다.

디버깅

Cascading 의심

미리보기 페이지는 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 계열로 변경한 것을 확인할 수 있었습니다.

따라서 컬러 프린트를 사용하지만, 프린트의 잉크를 최소화하기 위해 설계되었으리라 추측됩니다.

클라이언트를 생각하는 개발자

브라우저 개발자들은 사실 클라이언트에 매우 진심이었음을 알 수 있었습니다. 유저들의 프린트 리소스도 챙겨주니까 말이죠

저는 야근을 해버렸지만, 이건 제가 디버깅 방식을 잘못 채택했기에 발생한 문제라 생각하고, 그들의 진심을 다시 느낄 수 있는 시간이었습니다.

서비스를 만드는 입장에서 어디까지 유저를 고려할 수 있는지 다시 한번 고민하게 되었습니다.

참고

profile
내 꿈은 프론트 왕

11개의 댓글

comment-user-thumbnail
2025년 8월 17일

레퍼런스 코드도 있고, 기능 구현자체는 어렵지 않기에, 칼퇴근할 수 있다는 부푼 희망을 안고 작업에 임했습니다.

ㅋㅋㅋ 너무 공감가네요 하나의 썰을 보는 것 같아 재미있게 읽고 갑니다.

답글 달기
comment-user-thumbnail
2025년 8월 17일

이코노미라는 옵션이 있는거를 처음알았습니다! 재밌게 작성해주셔서 잘 읽고 갑니다b

답글 달기
comment-user-thumbnail
2025년 8월 21일

오 저도 이번에 진행하고 있는 프로젝트에서 print api 사용 할 것 같은데
덕분에 같은 문제가 발생하더라도 대처가 쉽게 될 것 같네요 감사합니다.
추가로 추측이라고는 하지만 개발자의 의도를 파악하시는 과정에서 한수 배우고 갑니다~!

답글 달기
comment-user-thumbnail
2025년 8월 23일

금요일 칼퇴 노리다가 야근한 썰 너무 공감돼요 😂 특히 !important 쓰다가 울고 싶었다는 부분에서 빵터졌습니다 ㅋㅋ 저도 비슷한 경험이 있어서 그 답답함이 그대로 느껴지네요. 브라우저가 잉크 절약을 위해 economy 모드를 기본값으로 한다는 관점도 흥미로웠어요.

답글 달기
comment-user-thumbnail
2025년 8월 24일

오오! 나중에 사용해보고 싶은 기능이었는데, 덕분에 주의 사항을 미리 알 수 있게 됐습니다!
좋은 글 감사합니다!

답글 달기
comment-user-thumbnail
2025년 8월 24일

저도 예전에 웹 페이지를 보고서 형태의 pdf로 다운로드 받을 수 있게 작업을 한적이 있었는데 비슷한 경험을 한적이 있어서 공감하고 갑니다..👍

답글 달기
comment-user-thumbnail
2025년 8월 25일

'print-color-adjust' 속성값에 대해 처음 알게 되었습니다! print기능을 만들게 된다면, 기획자분과 어떻게 고안을 할 지 토론을 해보아야겠네요! 좋은 글 잘 읽었습니다! 고생 많으셨습니다.

답글 달기
comment-user-thumbnail
2025년 8월 28일

우와 너무 스트레스 받았을거같아요..
간단한 체크박스 하나로 해결되다니 ㅋㅋㅋㅋ 그래도 덕분에(?) 지식하나 얻구 갑니다.. 어디가서 코난처럼 해결해줄수있겠네요 . 재미있게 읽었어요!

답글 달기
comment-user-thumbnail
2025년 8월 30일

원인을 파악하기 쉽지 않아 디버깅하기 어려웠겠네요..!
개발은 정말 항상 기대와는 다르게 진행되는 것 같아요ㅎㅎ
글 너무 재밌게 읽었어요!

답글 달기
comment-user-thumbnail
2025년 8월 31일

예전에 프린트 기능을 구현한적이 있었는데 이렇게 디테일하게 진행하지 않았어서 모르고 지나쳤던 문제인데 많은 도움이 되었습니다!

답글 달기
comment-user-thumbnail
2025년 8월 31일

분노의 야근회고 ㅋㅋㅋㅋㅋ 이 글로 시작한 계기가 엄청 재밌네요 저도 새로운 옵션들도 많이 알아가고 너무 유익했습니다 감사해요!

답글 달기