스플래시 스크린

Splash Screen

앱이 켜지는 짧은 순간 로고만 보여주는 정적인 화면. 로딩 애니메이션이 아니라 시스템이 그려주는 스냅샷입니다.

다른 이름: Launch screen런치 스크린
···
html
<div class="stage"><div class="phone"><div class="notch"></div><div class="screen" id="screen">
<div class="homeUnder">
  <div class="topbar2">Home</div>
  <div class="grid2"><div class="card3"></div><div class="card3"></div><div class="card3"></div><div class="card3"></div></div>
</div>
<div class="splash" id="splash"><div class="logo" id="logo"></div></div></div><div class="home"></div></div></div>
css
.stage{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}
.phone{position:relative;height:90vmin;max-height:640px;aspect-ratio:9/19.5;background:#08080c;
  border-radius:20%/8.6%;box-shadow:0 0 0 2px rgba(255,255,255,.08) inset,0 12px 30px rgba(0,0,0,.35)}
.phone .notch{position:absolute;left:50%;top:0;transform:translateX(-50%);width:38%;height:5.6%;
  background:#08080c;border-radius:0 0 46% 46%;z-index:80}
.phone .screen{position:absolute;top:7.6%;left:3%;right:3%;bottom:3%;border-radius:11%/4.6%;overflow:hidden;
  background:var(--surface);color:var(--fg)}
.phone .home{position:absolute;left:50%;bottom:1.1%;transform:translateX(-50%);width:32%;height:3px;
  border-radius:2px;background:rgba(255,255,255,.45);z-index:80}
.pfx{position:absolute;left:0;top:0;width:16px;height:16px;margin:-8px 0 0 -8px;border-radius:50%;
  background:color-mix(in srgb,var(--accent) 55%,transparent);
  box-shadow:0 0 0 6px color-mix(in srgb,var(--accent) 15%,transparent);pointer-events:none;z-index:999;
  transition:opacity .2s}
.pfx.hold{width:30px;height:30px;margin:-15px 0 0 -15px;
  box-shadow:0 0 0 9px color-mix(in srgb,var(--accent) 20%,transparent);
  transition:width .5s linear,height .5s linear,margin .5s linear,box-shadow .5s linear,opacity .2s}

.homeUnder{position:absolute;inset:0;background:var(--surface)}
.topbar2{padding:7% 8% 4%;font-weight:800;font-size:clamp(8px,3vmin,12px);border-bottom:1px solid var(--line)}
.grid2{padding:6% 8%;display:grid;grid-template-columns:1fr 1fr;gap:6%}
.card3{aspect-ratio:1;border-radius:10%;background:var(--line)}
.splash{position:absolute;inset:0;display:grid;place-items:center;background:var(--accent);z-index:5;transition:opacity .5s}
.splash.hide{opacity:0;pointer-events:none}
.logo{width:clamp(22px,15vmin,40px);height:clamp(22px,15vmin,40px);border-radius:26%;background:#fff;
  transform:scale(.5);opacity:0;transition:transform .45s cubic-bezier(.34,1.56,.64,1),opacity .3s}
.logo.show{transform:scale(1);opacity:1}
js
const pfx=document.createElement('div');pfx.className='pfx';document.body.appendChild(pfx);
const screenEl=document.getElementById('screen');
let pfxAuto=true;
function pfxHide(){pfx.style.opacity='0';}
addEventListener('pointerdown',()=>{pfxAuto=false;pfxHide();},{capture:true});
function pfxAt(fx,fy){
  const r=screenEl.getBoundingClientRect();
  const x=r.left+r.width*fx, y=r.top+r.height*fy;
  pfx.style.transform='translate('+x+'px,'+y+'px)';
  return {x,y};
}
function pfxWalk(x0,y0,x1,y1,ms){
  return new Promise(res=>{
    const start=performance.now();
    function step(now){
      if(!pfxAuto) return res();
      const p=Math.min(1,(now-start)/ms), e=1-Math.pow(1-p,3);
      pfxAt(x0+(x1-x0)*e, y0+(y1-y0)*e);
      if(p<1) requestAnimationFrame(step); else res();
    }
    requestAnimationFrame(step);
  });
}
function pfxWait(ms){
  return new Promise(res=>{
    const start=performance.now();
    function step(now){ if(!pfxAuto) return res(); if(now-start<ms) requestAnimationFrame(step); else res(); }
    requestAnimationFrame(step);
  });
}

const splash=document.getElementById('splash'), logo=document.getElementById('logo');
function skip(){ splash.classList.add('hide'); }
splash.addEventListener('pointerdown',()=>{ pfxAuto=false; pfxHide(); skip(); });
async function autoSeq(){
  while(pfxAuto){
    splash.classList.remove('hide'); logo.classList.remove('show'); void splash.offsetWidth;
    pfxAt(0.5,0.5);
    await pfxWait(200); if(!pfxAuto)break;
    logo.classList.add('show');
    await pfxWait(950); if(!pfxAuto)break;
    skip();
    await pfxWait(1300);
  }
}
autoSeq();

iOS는 `LaunchScreen` 스토리보드(또는 Info.plist의 `UILaunchScreen`)를, Android는 12부터 `SplashScreen` API를 통해 앱이 켜지는 첫 프레임을 관리한다. 둘 다 공통된 제약이 있다 — 이 화면은 네트워크 요청도, 동적으로 바뀌는 텍스트도 없는 완전히 정적인 스냅샷이어야 한다. 진행률 표시가 아니라 "앱이 준비되는 동안 잠깐 보여주는 정적 그림"이라는 원래 뜻에 가깝다.

실제 코드가 실행되기 전에 시스템이 그려주는 화면이라 로고를 화려하게 애니메이션시키는 데는 한계가 있다 — Android 12+는 아이콘이 살짝 확대되며 사라지는 기본 전환을 자체 제공하지만, 그 이상의 연출은 진짜 첫 화면이 그려진 뒤 앱 코드가 이어받아야 한다.

가장 흔한 실수는 스플래시를 브랜딩 기회로 여겨 로고에 긴 페이드인·회전 애니메이션을 넣는 것이다. 체감 실행 속도만 늦출 뿐이라, 최대한 짧게 보여주고 실제 콘텐츠(또는 스켈레톤)로 최대한 빨리 넘어가는 것이 정석이다.

언제 쓰나

콜드 스타트(앱을 아예 새로 켤 때)에만 씁니다. 화면 전환이나 데이터 로딩 중에는 스플래시를 재사용하지 말고 스피너·스켈레톤을 씁니다.