GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

css 배경 애니메이션 효과 9가지 — 깊이 층 3D 장면

02번 층은 거리만큼 초점이 흐려지고, 07번 층은 거리만큼 그림자가 옅어지며, 09번 장면은 그 두 계산을 같은 화면 위에 겹쳐 놓는다. 이 아홉 가지 css 배경 애니메이션 효과는 층마다 다른 계산 하나로 깊이감을 만든다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

css 배경 애니메이션 효과 9가지 — 층마다 다른 계산이 쌓여 만드는 깊이감

아홉 항목은 모두 내부 계산 해상도를 CALC_W=160·CALC_H=100 으로 고정해 두고 화면 크기에 맞춰 비율(sx, sy)만 늘려 그린다. 여덟 항목은 seedRand() 로 씨앗을 고정한 난수로 시작 위치나 속도를 정해 매번 같은 순서로 재생되고, 유일한 예외인 04번은 애초에 난수를 쓰지 않는 고정 격자라 매번 완전히 같은 모양으로 시작한다. 층 하나에 적용되는 계산은 거리 배수·흐림 반경·주기 이동·투영 좌표·화가 알고리즘식 정렬·지수 감쇠·그림자 변환·크기 환산 중 하나(또는 09처럼 여럿)이고, 이 값들이 서로 다른 조합으로 섞이며 아홉 가지 다른 깊이감이 된다.

깊이 층 종류별 코드

여기 아홉 개 발췌는 값이 계산되는 지점만 담았고 소스 전체는 압축 파일에 들어 있다. 아홉 항목 모두 3D 라이브러리나 WebGL 없이 canvas 2D 컨텍스트 위에서 거리·흐림·그림자·기울기 값만 매 프레임 계산해서 그린다.

01층마다 다른 속도로 밀리는 도형 시차 배경

도형마다 가상의 거리값을 부여해 포인터 이동량에 그 거리에 반비례하는 배수를 곱하고, 가까운 도형은 크게 밀리고 먼 도형은 조금만 밀린다. distanceMultiplier 값은 0.18~3.1 사이로 한 번 더 잘라내 어떤 거리라도 도형이 화면 밖으로 튕겨 나가지 않고, 포인터가 없을 때는 시간 기반 가상 경로가 대신 좌표를 만들어 자동으로 재생된다. 24장 중 23장에서 도형이 움직였고 누적면적은 20.6%, 강도 평균은 61.1이었다.

층마다 다른 속도로 밀리는 도형 시차 배경
새 창에서 열기

01 · 층마다 다른 속도로 밀리는 도형 시차 배경 — 측정 누적면적 20.586% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

distanceMultiplierINERTIA_LERP_RATEPARALLAX_REF_DISTANCE_PX
  function distanceMultiplier(distancePx) {
    var m = PARALLAX_REF_DISTANCE_PX / distancePx;
    return Math.max(0.18, Math.min(3.1, m));
  }

  var shapes = [];
  for (var i = 0; i < SHAPE_COUNT; i += 1) {
    var distancePx = 26 + rnd() * 150;
    var ang = rnd() * Math.PI * 2;
    var rad = 14 + rnd() * 62;
    shapes.push({
      distancePx: distancePx,

02거리만큼 초점이 흐려지는 보케 흐림 배경

각 도형의 거리값을 흐림 반경으로 변환해 실시간으로 filter blur 값을 다시 계산해, 초점면에서 멀어질수록 도형 윤곽이 더 크게 번진다. 포인터가 없을 때도 시간에 따라 흐르는 장면이 계속 재생되지만, 초점면이 두 도형 거리 사이 평탄한 구간을 지날 때는 흐림 반경이 잠깐 거의 바뀌지 않는다. 24장 중 15장에서 흐림 정도가 바뀌었고 누적면적은 17.0%, 강도 평균은 15.1이었다.

거리만큼 초점이 흐려지는 보케 흐림 배경
새 창에서 열기

02 · 거리만큼 초점이 흐려지는 보케 흐림 배경 — 측정 누적면적 16.966% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

blurForDepthDEPTH_BLUR_MAX_PXfocalDepthAt
  function blurForDepth(distancePx, focalDepth) {
    var diff = Math.abs(distancePx - focalDepth);
    return Math.max(0, Math.min(DEPTH_BLUR_MAX_PX, diff / 9));
  }

  var elapsed = 0;
  var cw = 480, ch = 300;

03층별로 천천히 흘러가는 그라데이션 메시 배경

여러 겹의 흐린 그라데이션 덩어리에 서로 다른 이동 주기를 줘 겹쳐 그리고, 가까운 층일수록 더 크고 빠르게, 먼 층일수록 작고 느리게 흐른다. MESH_LAYER_COUNT 는 5로 고정돼 있어 층 수를 늘리려면 배열 길이 자체를 바꿔야 한다. 24장 중 23장에서 덩어리가 움직였고 누적면적은 52.5%, 강도 평균은 20.9였다.

층별로 천천히 흘러가는 그라데이션 메시 배경
새 창에서 열기

03 · 층별로 천천히 흘러가는 그라데이션 메시 배경 — 측정 누적면적 52.471% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

blobPositionAtMESH_DRIFT_PERIOD_BASE_MSMESH_LAYER_COUNT
  function blobPositionAt(layer, t) {
    var ax = Math.sin(t / layer.period * Math.PI * 2 + layer.phaseX) * layer.reachPx;
    var ay = Math.cos(t / layer.period * Math.PI * 2.3 + layer.phaseY) * layer.reachPx * 0.7;
    return { x: layer.centerX + ax, y: layer.centerY + ay };
  }

  var elapsed = 0;
  var cw = 480, ch = 300;

  function drawScene(nowMs) {

04지평선으로 좁아지는 원근 격자 바닥

격자선 좌표를 원근 투영 공식으로 계산해 지평선에 가까울수록 선 사이 간격을 좁히고, 포인터가 움직이면 시점 위치도 같이 다시 계산한다. zWorld 값이 카메라에 너무 가까워지면 4 밑으로 내려가지 않게 잘라, 격자선이 무한히 커지며 화면을 뒤덮는 계산 사고를 막는다. 24장 중 23장에서 격자가 움직였고 누적면적은 23.4%, 강도 평균은 39.8이었다.

지평선으로 좁아지는 원근 격자 바닥
새 창에서 열기

04 · 지평선으로 좁아지는 원근 격자 바닥 — 측정 누적면적 23.426% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

projectGridPointGRID_PERSPECTIVE_PXnudgeCamHeight
  function projectGridPoint(xWorld, zWorld) {
    var z = Math.max(4, zWorld);
    var scale = GRID_PERSPECTIVE_PX / (z * 40);
    return {
      x: CALC_W * 0.5 + (xWorld - viewOffsetX) * scale,
      y: HORIZON_Y + camHeight * scale
    };
  }

05포인터를 따라 기우는 궤도 위성 고리

작은 도형들의 위치를 구면 좌표식으로 매 프레임 계산하고 z값 순서로 다시 그려, 포인터 위치에 따라 고리 전체가 기울면서도 앞뒤 가림이 맞는다. zSortSatellites 는 매 프레임 위성 전체를 z값 순으로 다시 정렬해 그리므로, 기울기가 바뀌어 앞뒤 순서가 뒤집혀도 그리는 순서가 함께 뒤집힌다. 24장 중 23장에서 위성이 움직였고 누적면적은 10.2%, 강도 평균은 106.0이었다.

포인터를 따라 기우는 궤도 위성 고리
새 창에서 열기

05 · 포인터를 따라 기우는 궤도 위성 고리 — 측정 누적면적 10.212% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

zSortSatellitesORBIT_TILT_MAX_DEGcomputeTransformed
  function computeTransformed(sat, t) {
    var ang = satelliteAngleAt(sat, t) + azimuthOffset;
    var r = RING_RADIUS * sat.radiusScale;
    var lx = Math.cos(ang) * r;
    var lz = Math.sin(ang) * r;
    var tiltRad = tiltDeg * Math.PI / 180;
    var ry = -lz * Math.sin(tiltRad);
    var rz = lz * Math.cos(tiltRad);
    return { x: lx, y: ry, z: rz };
  }

  function zSortSatellites(list, t) {
    var withZ = list.map(function (sat) {
      var p = computeTransformed(sat, t);
      return { sat: sat, p: p };
    });
    withZ.sort(function (a, b) { return a.p.z - b.p.z; });
    return withZ;
  }

06층마다 옅어지는 빛줄기 배경

빛줄기 여러 겹을 그리고 각 겹의 불투명도를 거리값에 지수함수로 곱해 줄여, 가까운 겹은 선명하고 먼 겹은 배경에 거의 스며든다. Math.exp(-LIGHT_SHAFT_DECAY * depth) 로 옅어지는 정도를 지수적으로 줄이기 때문에, 거리 값이 조금만 늘어도 불투명도는 초반에 빠르게 떨어지고 이후엔 완만해진다. 24장 중 23장에서 빛줄기가 움직였고 누적면적은 30.5%, 강도 평균은 21.6이었다.

층마다 옅어지는 빛줄기 배경
새 창에서 열기

06 · 층마다 옅어지는 빛줄기 배경 — 측정 누적면적 30.528% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

shaftOpacityForDepthLIGHT_SHAFT_DECAYSHAFT_LAYER_COUNT
  function depthFor(shaft, t) {
    return shaft.distanceBase + Math.sin(t * shaft.swaySpeed + shaft.phase) * shaft.swayRange;
  }

  function shaftOpacityForDepth(depth) {
    var v = Math.exp(-LIGHT_SHAFT_DECAY * depth) * MAX_OPACITY;
    return Math.max(0, Math.min(MAX_OPACITY, v));
  }

07높이만큼 그림자가 커지는 부유 카드

카드마다 떠 있는 높이값을 그림자의 흐림 반경과 옅기로 변환해, 높이 뜬 카드일수록 그림자가 크고 연해지고 낮게 뜬 카드는 그림자가 진하고 작다. shadowForHeight 는 높이값을 0~1 사이 비율로 정규화한 뒤 흐림·불투명도·가로 폭 세 값을 한 번에 계산해, 카드가 뜬 높이만 알면 그림자 모양 전체가 정해진다. 24장 중 23장에서 카드가 움직였고 누적면적은 21.4%, 강도 평균은 129.0이었다.

높이만큼 그림자가 커지는 부유 카드
새 창에서 열기

07 · 높이만큼 그림자가 커지는 부유 카드 — 측정 누적면적 21.405% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

shadowForHeightcardBobAtCARD_FLOAT_AMPLITUDE_PX
  function cardBobAt(card, t) {
    return (Math.sin(t * card.bobSpeed + card.phase) * 0.5 + 0.5) * CARD_FLOAT_AMPLITUDE_PX;
  }

  function shadowForHeight(h) {
    var norm = h / CARD_FLOAT_AMPLITUDE_PX;
    return {
      blur: 3 + norm * 13,
      alpha: 0.4 - norm * 0.26,
      scaleX: 1 + norm * 0.35
    };
  }

08거리에 따라 실시간으로 커지고 작아지는 입자 배경

입자마다 z값을 시간에 따라 조금씩 바꾸고 그 값을 화면 크기로 매 프레임 다시 환산해, 다가오는 입자는 점점 커지고 멀어지는 입자는 점점 작아진다. zAt 은 시간에 따라 줄어드는 z값이 0 밑으로 내려가면 PARTICLE_Z_MAX 만큼 다시 더해 뒤쪽에서 새 입자로 나타나게 하므로, 입자 수는 늘지도 줄지도 않는다. 24장 중 23장에서 입자가 움직였고 누적면적은 30.6%, 강도 평균은 94.4였다.

거리에 따라 실시간으로 커지고 작아지는 입자 배경
새 창에서 열기

08 · 거리에 따라 실시간으로 커지고 작아지는 입자 배경 — 측정 누적면적 30.615% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

sizeForDepthzAdvanceSpeedPARTICLE_Z_MAX
  function zAdvanceSpeed(p) {
    return 0.012 * p.speedScale;
  }

  function zAt(p, t) {
    var raw = (p.zOffset - t * zAdvanceSpeed(p)) % PARTICLE_Z_MAX;
    if (raw < 0) raw += PARTICLE_Z_MAX;
    return raw + 1;
  }

  function sizeForDepth(z) {
    var v = (FOCAL / z) * 3.4;
    return Math.max(0.8, Math.min(7, v));
  }

09여러 깊이 층을 한 화면에 합친 히어로 배경 장면

시차·흐림·원근 격자·궤도·그림자·크기 변화를 같은 배경 위에서 동시에 계산해, 포인터가 움직이면 여러 층이 한 몸처럼 함께 반응한다. 이 장면은 01의 거리 배수, 04의 원근 투영, 05의 궤도 기울기 계산을 그대로 가져오되 HERO_ 접두가 붙은 자신만의 상수(HERO_PARALLAX_REF_DISTANCE_PX=200·HERO_BLUR_MAX_PX=10·HERO_ORBIT_TILT_MAX_DEG=16)로 세기만 다시 잡는다. 24장 중 23장에서 장면이 움직였고 누적면적은 23.1%, 강도 평균은 59.8이었다.

여러 깊이 층을 한 화면에 합친 히어로 배경 장면
새 창에서 열기

09 · 여러 깊이 층을 한 화면에 합친 히어로 배경 장면 — 측정 누적면적 23.106% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

HERO_ORBIT_TILT_MAX_DEGHERO_PARALLAX_REF_DISTANCE_PXHERO_BLUR_MAX_PX
  function distanceMultiplier(distancePx) {
    var m = HERO_PARALLAX_REF_DISTANCE_PX / Math.max(20, distancePx);
    return Math.max(0.2, Math.min(2.6, m));
  }

  function projectGridPoint(xWorld, zWorld, viewOffsetX) {
    var z = Math.max(4, zWorld);
    var scale = GRID_PERSPECTIVE_PX / (z * 40);
    return { x: CALC_W * 0.5 + (xWorld - viewOffsetX) * scale, y: HORIZON_Y + camHeight * scale };
  }

  var elapsed = 0, scrollZ = 0;
  var cw = 480, ch = 300;

  function drawScene(t, dtS) {
    var sx = cw / CALC_W, sy = ch / CALC_H;
    var viewOffsetX = nx * 42;
    var tiltDeg = ny * HERO_ORBIT_TILT_MAX_DEG;
    var azimuthOffset = nx * 0.7;

    var targetOffsetX = nx * 14, targetOffsetY = ny * 9;
    var rate = Math.min(1, INERTIA_LERP_RATE * dtS);
    offsetCur += (targetOffsetX - offsetCur) * rate;
    offsetCurY += (targetOffsetY - offsetCurY) * rate;
    var pointerDistancePx = Math.max(20, Math.hypot(offsetCur, offsetCurY) * 6 + 60);

잰 css 배경 애니메이션 9항목의 실제 값 — 24장 캡처 기준

항목 누적면적 평균 강도 움직인 프레임
01 층마다 다른 속도로 밀리는 도형 시차 배경 20.6% 61.1 23/23
02 거리만큼 초점이 흐려지는 보케 흐림 배경 17.0% 15.1 15/23
03 층별로 천천히 흘러가는 그라데이션 메시 배경 52.5% 20.9 23/23
04 지평선으로 좁아지는 원근 격자 바닥 23.4% 39.8 23/23
05 포인터를 따라 기우는 궤도 위성 고리 10.2% 106.0 23/23
06 층마다 옅어지는 빛줄기 배경 30.5% 21.6 23/23
07 높이만큼 그림자가 커지는 부유 카드 21.4% 129.0 23/23
08 거리에 따라 실시간으로 커지고 작아지는 입자 배경 30.6% 94.4 23/23
09 여러 깊이 층을 한 화면에 합친 히어로 배경 장면 23.1% 59.8 23/23

값이 깊이감을 바꾸는 곳 — 거리·흐림·그림자·기울기

PARALLAX_REF_DISTANCE_PX 는 01번 도형의 배수 계산 기준 거리를 정한다. 값을 낮추면 가까운 도형도 배수가 작게 나와 전체적으로 덜 밀리고, 값을 올리면 같은 거리라도 배수가 커져 도형이 더 크게 튕기듯 움직인다. GRID_PERSPECTIVE_PX 는 04번 격자가 css 3d perspective 계산을 흉내 낼 때 쓰는 값이라, 값을 줄이면 격자가 지평선 쪽으로 더 급하게 좁아져 원근감이 과장되고 값을 늘리면 완만하게 좁아져 평평한 배경처럼 보인다. ORBIT_TILT_MAX_DEG 는 05번 궤도가 기울 수 있는 최대 각도라, 값을 낮추면 포인터를 크게 움직여도 고리가 거의 눕지 않고 값을 올리면 조금만 움직여도 고리가 크게 기운다.

어디서 깨지는가 — 동작 줄임 설정·좁은 화면·층이 많을 때

깊이 층 아홉 개는 전부 matchMedia('(prefers-reduced-motion: reduce)') 로 동작 줄임 설정을 읽어 자동 재생 루프를 멈춘다 — 포인터로 움직이는 01·02·04·05·09 다섯 항목은 방향키 입력이 들어오면 그 프레임만 다시 계산해 화면을 갱신한다. 좁은 화면에서는 @media (max-width: 340px) 로 캡션 글자 크기만 10px 로 줄어들 뿐 캔버스 내부 계산 해상도는 그대로라, 화면이 작아질수록 층과 층 사이 깊이 차이가 시각적으로 더 좁아 보인다. 03·06·07·08 항목은 입력 없이 시간에 따라서만 흘러가고, 09번 히어로 장면은 여섯 계산(시차·흐림·투영·궤도·그림자·크기)을 한 화면 위에서 동시에 처리해 층이 많을수록 프레임마다 계산량도 함께 늘어난다. 깊이 층 아홉 개의 원본 소스는 zip 압축 파일로 묶여 있고, 여는 비밀번호는 gt5gztk5 이다.

변형 세 가지와 접근성

이름 값 느낌
최대 흐림 반경 조정 02 DEPTH_BLUR_MAX_PX 14 → 28 값을 줄이면 가장 먼 층도 살짝만 흐려져 장면이 납작해 보이고, 값을 늘리면 먼 층이 크게 번져 앞뒤 거리 차이가 뚜렷해진다.
원근감 강도 조정 04 GRID_PERSPECTIVE_PX 800 → 400 값을 줄이면 바닥 격자가 더 급하게 좁아져 원근감이 과장되고, 값을 늘리면 격자선이 완만하게 좁아져 장면이 평평한 배경처럼 보인다.
궤도 기울기 최대각 조정 05 ORBIT_TILT_MAX_DEG 18 → 32 값을 줄이면 포인터가 많이 움직여도 궤도가 거의 눕지 않고, 값을 늘리면 포인터가 조금만 움직여도 궤도가 크게 기울어 보인다.

포인터가 있는 01·02·04·05·09 항목은 방향키로도 같은 포인터 좌표를 옮길 수 있어 마우스 없이도 장면을 확인할 수 있고, 03·06·07·08 항목은 애초에 입력이 필요 없는 자동 재생이라 prefers-reduced-motion 값이 켜지면 그대로 멈춘다.

css 3d transform 은 배경 장면에서 어떤 계산을 하는가?

실제로는 계산 방식이 다르다. 아홉 항목 모두 SCSS 안에 transform·perspective 속성을 쓰지 않고, canvas 2D 컨텍스트 위에서 거리 배수·투영 좌표·화가 알고리즘식 정렬·지수 감쇠 같은 수식을 매 프레임 좌표와 스타일 값으로 직접 바꿔 그린다. 04번은 원근 투영 공식으로 좌표를 계산해 지평선에 가까운 격자선일수록 간격을 좁히고, 05번은 구면 좌표로 위성 위치를 구한 뒤 z값 순서로 다시 그려 앞뒤 가림을 맞춘다. 여러 층을 겹쳐 한 줄로 붙이는 배경 계산은 CSS 배경 애니메이션 9종 — 한 줄 복붙으로 가, 포인터를 따라 도는 회전 계산은 마우스를 따라오는 CSS 3D 회전 장면 9종 이 각각 다룬다.

css 배경 애니메이션 9개에서 자주 나오는 질문

css 배경 그라데이션 애니메이션도 이 아홉 개 안에 있나요?

있다. 03번이 여러 겹의 흐린 그라데이션 덩어리를 서로 다른 주기로 움직여 겹쳐 그리는 방식이라, 층마다 크기와 속도만 다를 뿐 같은 계산을 공유한다.

포인터가 없는 화면에서도 장면이 움직이나요?

움직인다. 01·04·05·09 항목은 포인터가 없으면 시간 기반 가상 경로가 대신 좌표를 만들어 자동으로 재생되고, 02번은 포인터와 상관없이 항상 흐르는 시간 위에 초점 위치만 얹는 구조라 가만히 둬도 이미 움직이고 있다.

층을 더 늘려도 되나요?

소스에서 배열 길이나 카운트 상수를 늘리면 층이 늘어난다. 다만 05번처럼 위성 전체를 매 프레임 z값으로 다시 정렬하는 항목은 층(위성) 수가 늘수록 정렬과 그리기 계산량도 함께 늘어난다는 점을 감안해야 한다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.