CSS / Tutorial / 동영상을 배경으로 사용하는 방법

동영상을 배경으로 쓰려면 부모를 position: relative로 만들고, video를 절대 위치로 겹친 뒤 object-fit: cover로 영역을 채웁니다. 텍스트는 별도 HTML로 위에 배치하고 영상이 재생되지 않아도 읽을 수 있는 기본 배경을 둡니다. 장식용 영상은 음소거하고, 사용자가 멈출 수 있는 제어와 동작 줄이기 설정도 함께 고려해야 합니다.

배경 영상·덮개·본문을 나누어 배치하기

video는 장식 레이어이고, 반투명 shade는 화면의 밝기가 달라져도 글자를 구별하기 위한 덮개입니다. 제목·설명·버튼은 그 위의 실제 HTML로 작성합니다. 영상 안에 제목을 굽는 방식은 화면 크기별 줄바꿈이나 보조 기술의 읽기와 별개이므로 핵심 내용은 HTML에 두는 편이 적합합니다.

부모의 isolation: isolate는 이 예제의 겹침을 별도 쌓임 맥락 안에서 관리합니다. 영상과 덮개의 음수 z-index가 페이지의 다른 영역 뒤로 빠지는 문제를 줄이고, overflow: hidden으로 부모 영역 밖의 영상 부분을 감춥니다. 음수 z-index를 모든 배경 배치의 필수 규칙으로 외우기보다 해당 레이어 구조와 함께 이해하세요.

준비 사항과 전체 예제

직접 촬영했거나 사용 권한을 확인한 짧은 MP4 파일을 준비하고 background.mp4라는 이름으로 저장합니다. 아래 코드는 같은 폴더의 video-background.html로 저장해 브라우저에서 여세요. 영상의 실제 코덱은 브라우저가 재생할 수 있어야 하며, MP4 확장자만으로 지원이 보장되지는 않습니다. 공개 사이트에서는 video의 src를 업로드한 실제 영상 URL로 바꿉니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>멈출 수 있는 배경 동영상</title>
  <style>
    body { margin: 20px; font: 16px/1.6 "Noto Sans KR", sans-serif; color: #203348; }
* { box-sizing: border-box; }
    body { margin: 0; }
    .hero { position: relative; isolation: isolate; overflow: hidden;
      min-height: 340px; display: grid; place-items: center;
      background: linear-gradient(135deg, #173f55, #102138); color: white; }
    .hero video, .shade { position: absolute; inset: 0; width: 100%; height: 100%; }
    .hero video { object-fit: cover; object-position: center; z-index: -2; }
    .shade { background: rgb(0 0 0 / 0.55); z-index: -1; pointer-events: none; }
    .hero-content { width: min(100%, 680px); padding: 32px 24px; text-align: center; }
    h1 { margin: 0 0 16px; font-size: clamp(28px, 5vw, 44px); line-height: 1.3; }
    button { min-height: 44px; padding: 8px 16px; font: inherit;
      color: #102138; background: white; border: 2px solid white; cursor: pointer; }
    button:focus-visible { outline: 3px solid #ffce68; outline-offset: 4px; }
    .status { min-height: 1.5em; margin: 8px 0 0; font-size: 14px; }
    main { max-width: 900px; margin: auto; padding: 24px; }
    h2 { font-size: 22px; }
  </style>
</head>
<body>
<section class="hero">
  <video id="background-video" src="background.mp4" muted loop playsinline preload="none" aria-hidden="true"></video>
  <div class="shade" aria-hidden="true"></div>
  <div class="hero-content">
    <h1>움직임보다 중요한 것은 내용입니다</h1>
    <p>배경 영상이 멈춰 있어도 제목과 설명을 읽을 수 있습니다.</p>
    <button id="toggle" type="button">배경 영상 재생</button>
    <p id="status" class="status" role="status"></p>
  </div>
</section>
<main>
  <h2>일반 본문</h2>
  <p>영상은 장식용 배경입니다. 중요한 정보는 실제 HTML로 제공합니다.</p>
</main>
<script>
  const video = document.querySelector('#background-video');
  const toggle = document.querySelector('#toggle');
  const status = document.querySelector('#status');
  const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
  function updateButton() {
    toggle.textContent = video.paused ? '배경 영상 재생' : '배경 영상 멈추기';
  }
  function showFallback() {
    video.pause();
    status.textContent = '영상을 재생하지 못했습니다. 기본 배경으로 내용을 볼 수 있습니다.';
    updateButton();
  }
  video.addEventListener('error', showFallback);
  async function startVideo() {
    try {
      await video.play();
      status.textContent = '';
    } catch (error) {
      showFallback();
    }
  }
  toggle.addEventListener('click', () => {
    if (video.paused) startVideo();
    else video.pause();
  });
  video.addEventListener('play', updateButton);
  video.addEventListener('pause', updateButton);
  reducedMotion.addEventListener('change', (event) => {
    if (event.matches) video.pause();
  });
  if (!reducedMotion.matches) startVideo();
</script>
</body>
</html>

파일을 준비하지 않았거나 재생이 차단된 경우에도 기본 그라데이션 위에 제목·설명·버튼이 표시됩니다. 영상이 있으면 영역을 채우되 가장자리가 일부 잘릴 수 있습니다. 일반 동작 환경에서는 음소거 자동 재생을 시도하고, 동작 줄이기 환경에서는 사용자가 재생 버튼을 누르기 전까지 시작하지 않습니다.

cover와 텍스트 대비를 확인하기

width: 100%와 height: 100%는 video 요소의 상자를 부모 영역에 맞춥니다. object-fit: cover는 영상 비율을 유지하면서 그 상자를 채우므로 원본 영상의 일부가 잘릴 수 있습니다. object-position: center 대신 70% center처럼 관심 영역을 옮기면 다른 부분을 중심으로 자를 수 있습니다.

같은 파일에서 cover만 contain으로 바꾸면 영상 전체가 보이지만 비율 차이에 따른 빈 공간이 남습니다. 비교 후 cover로 복원하세요. 부모의 min-height만 420px로 바꾸어 더 높은 영역에서도 영상이 찌그러지지 않는지 확인하고 340px로 되돌립니다. 제목이 길어지면 내용에 맞춰 부모도 높아질 수 있도록 높이를 강제로 고정하지 않았습니다.

덮개는 배경만 어둡게 합니다. 부모 전체에 opacity를 주면 글자와 버튼까지 흐려질 수 있으므로 이 목적에는 별도 레이어가 더 적합합니다. 밝고 어두운 장면이 바뀌는 실제 영상에서 제목·버튼·포커스 표시가 계속 구분되는지 확인해야 합니다.

자동 재생·일시 정지·동작 줄이기

muted는 음소거, loop는 반복, playsinline은 지원되는 모바일 환경에서 인라인 재생을 요청합니다. 무음 영상도 브라우저 정책·사용자 설정·절전 조건에 따라 자동 재생이 제한될 수 있으므로 video.play()가 반환하는 Promise의 실패를 처리합니다. 이 예제는 실패 시 기본 배경과 다시 재생할 버튼을 유지합니다.

재생 중 버튼을 누르면 pause()로 멈추고, 다시 누르면 재생을 이어갑니다. 버튼의 문구는 play·pause 이벤트에 맞춰 바뀝니다. 동작 줄이기 설정이 켜질 때도 영상을 멈추지만 사용자가 직접 재생 버튼을 눌러 선택하는 동작은 허용합니다. 설정이 꺼졌다는 이유로 무조건 자동 재개하지는 않습니다.

video는 장식용이므로 aria-hidden을 지정했고 기본 controls를 사용하지 않았습니다. 대신 실제 버튼으로 재생을 제어합니다. 영상 자체가 설명·대화·교육의 핵심이라면 장식용 배경으로 감추지 말고 접근 가능한 플레이어, 필요한 자막과 대체 설명을 별도로 제공해야 합니다.

용량·경로·모바일에서 확인할 항목

짧고 반복해도 자연스러운 장면을 선택하고 실제 표시 크기에 필요한 해상도·품질로 줄입니다. 거대한 원본을 그대로 자동 재생하면 데이터 사용량과 디코딩 부담이 커질 수 있습니다. preload="none"은 사전 로딩을 줄이려는 힌트이며, 재생을 시작하면 필요한 데이터는 다운로드됩니다. 모든 환경에서 네트워크 요청이 전혀 없다는 보장은 아닙니다.

검증할 때는 파일 이름의 대소문자와 경로, HTTP 응답과 MIME 형식, 코덱 지원, 자동 재생 거절 여부를 순서대로 확인합니다. 파일 경로를 잠시 존재하지 않는 이름으로 바꿔도 제목·설명·일반 본문이 남는지 확인한 뒤 원래대로 복원하세요. 모바일에서는 영상의 잘림, 가로 넘침, 긴 제목과 버튼의 겹침을 함께 확인합니다.

기본 요소와 속성은 MDN video 문서, 자동 재생 조건은 자동 재생 가이드, 비율 유지는 object-fit 문서를 참고할 수 있습니다.