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

이미지 전환 효과 9종 — 캔버스 픽셀 계산으로 완성되는 리빌

이미지 전환 효과 아홉 가지 가운데 하나는 픽셀의 대각선 좌표로 통과 시각을 계산해 화면을 비스듬히 쓸고, 하나는 격자 칸마다 노이즈 문턱값을 미리 계산해 불규칙하지만 재현되는 순서로 드러나며, 하나는 고정 시드로 가로줄 오프셋을 계산해 매번 같은 순서로 글리치가 정리됩니다.

아홉 개 모두 requestAnimationFrame 으로 매 프레임 진행률을 다시 계산하고, 클릭·Enter·창 크기 변경(resize) 세 가지 입력만 듣습니다. Math.random 은 한 번도 부르지 않고 선형합동생성기(LCG) 하나로 장면을 만들기 때문에, 새로고침해도 색 배치와 노이즈 결이 항상 같습니다. 라이브러리도 WebGL 도 CDN 도 없이 캔버스 2D 컨텍스트와 자바스크립트 계산만으로 화면 전체를 다음 장면으로 넘기는 사례들로, css 이미지 전환 애니메이션이 흔히 흉내 내는 결과를 픽셀 단위 계산으로 직접 완성했습니다.

데모

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

아홉 가지 이미지 전환 효과 — 계산 한 겹이 어디서 도는가

아홉 개를 나열한 순서는 계산이 도는 자리를 기준으로 잡았습니다. 01·02·07 은 픽셀마다 좌표값을 미리 계산해 두고 그 값과 진행률을 비교하는 필드 방식이고, 03·09 는 노이즈나 사인 곡선처럼 함수 하나로 경계를 그리는 방식이며, 04·05·06·08 은 화면을 블록·조각·줄·점으로 나눠 각 조각의 순서를 미리 계산해 두는 방식입니다. 셋 다 결과는 "이미지 A가 이미지 B로 바뀐다"는 같은 문제를 풀지만, 어디에 계산 한 겹을 얹느냐가 다릅니다.

값 노이즈로 문턱값을 칸마다 미리 계산해 두는 03 항목은 전형적인 디졸브 이미지 연출이라서, 노이즈 주파수 상수 하나만 옮겨도 흩어지는 결 자체가 완전히 달라집니다. 아래 표는 각 항목을 3초 렌더 창에서 재생했을 때 실측한 값입니다. 누적면적은 캔버스 픽셀이 이전 색과 달라진 비율을 24프레임 캡처 동안 합산한 값이고, 움직인프레임은 그 23개 델타(프레임 간 차이) 가운데 실제로 픽셀이 바뀐 델타 수입니다.

항목 누적면적 강도평균 움직인프레임
01 대각선 리빌 48.760% 126.40 23/23
02 시계 원형 리빌 48.249% 132.60 23/23
03 노이즈 디졸브 48.761% 169.80 20/23
04 모자이크 리졸브 48.051% 147.40 22/23
05 파편 낙하 전환 48.444% 83.50 23/23
06 스캔라인 글리치 47.115% 158.80 23/23
07 잉크 번짐 리빌 48.568% 115.00 23/23
08 망점 리빌 34.422% 79.90 21/23
09 물결 리빌 47.283% 163.50 22/23

08 망점 리빌만 누적면적이 낮은 것은 점 사이 여백이 원래 구조상 남기 때문이고, 나머지 여덟은 화면 대부분이 매 델타마다 바뀌는 장면형 전환입니다.

패턴별 코드

아래 아홉 개는 실제 데모의 PRECOMPUTE(장면이 바뀔 때 한 번 계산)와 RENDER(p)(진행률 p 마다 다시 그리는 함수)를 그대로 옮긴 조각입니다. 값을 바꾸는 상수는 코드 위 주석에 적어 두었습니다.

01대각선 좌표로 계산되는 화면 전환 와이프

화면 전체를 덮은 캔버스가 각 픽셀의 대각선 좌표(가로·세로 위치의 평균)를 미리 한 번 계산해 두고, 진행률이 그 값을 지나는 픽셀부터 아래 이미지 색으로 바꿔 씁니다. 경계 부드러움 폭(EDGE_SOFT) 만큼만 색을 섞어서 선이 톱니처럼 보이지 않게 합니다.

  function PRECOMPUTE() {
    var iw = cv.width, ih = cv.height;
    dataA = bctxA.getImageData(0, 0, iw, ih).data;
    dataB = bctxB.getImageData(0, 0, iw, ih).data;
    outImg = ctx.createImageData(iw, ih);
    tField = new Float32Array(iw * ih);
    for (var y = 0; y < ih; y++) {
      for (var x = 0; x < iw; x++) tField[y * iw + x] = (x / iw + y / ih) / 2;
    }
  }

  function RENDER(p) {
    var o = outImg.data;
    for (var i = 0, n = tField.length; i < n; i++) {
      var t = tField[i], k = i * 4, reveal;
      if (t <= p) reveal = 1;
      else if (t <= p + EDGE_SOFT) reveal = 1 - (t - p) / EDGE_SOFT;
      else reveal = 0;
      o[k] = dataB[k] + (dataA[k] - dataB[k]) * reveal;
      o[k + 1] = dataB[k + 1] + (dataA[k + 1] - dataB[k + 1]) * reveal;
      o[k + 2] = dataB[k + 2] + (dataA[k + 2] - dataB[k + 2]) * reveal;
      o[k + 3] = 255;
    }
    ctx.putImageData(outImg, 0, 0);
  }
대각선 좌표로 계산되는 화면 전환 와이프
새 창에서 열기

01 · 대각선 좌표로 계산되는 화면 전환 와이프 — 측정 누적면적 48.76% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

tFieldoutImgputImageData

02각도로 계산되는 시계 방향 원형 리빌

캔버스 중심을 기준으로 각 픽셀의 각도를 atan2 로 미리 계산해 두고, 진행률을 각도(0~2π)로 바꿔 그 값을 지나는 픽셀부터 다음 장면을 드러냅니다. 경계 위에는 지금 각도를 가리키는 바늘 선을 하나 더 그려서 시계가 도는 느낌을 보탭니다.

  function RENDER(p) {
    var o = outImg.data;
    for (var i = 0, n = angField.length; i < n; i++) {
      var t = angField[i], k = i * 4, reveal;
      if (t <= p) reveal = 1;
      else if (t <= p + EDGE_SOFT) reveal = 1 - (t - p) / EDGE_SOFT;
      else reveal = 0;
      o[k] = dataB[k] + (dataA[k] - dataB[k]) * reveal;
      o[k + 1] = dataB[k + 1] + (dataA[k + 1] - dataB[k + 1]) * reveal;
      o[k + 2] = dataB[k + 2] + (dataA[k + 2] - dataB[k + 2]) * reveal;
      o[k + 3] = 255;
    }
    ctx.putImageData(outImg, 0, 0);
    ctx.save();
    ctx.strokeStyle = ACCENT; ctx.lineWidth = 2; ctx.globalAlpha = .8;
    var ang = p * Math.PI * 2 - Math.PI / 2;
    var r = Math.min(W, H) * 0.42;
    ctx.beginPath();
    ctx.moveTo(cx0 / dpr, cy0 / dpr);
    ctx.lineTo(cx0 / dpr + Math.cos(ang) * r, cy0 / dpr + Math.sin(ang) * r);
    ctx.stroke();
    ctx.restore();
  }
각도로 계산되는 시계 방향 원형 리빌
새 창에서 열기

02 · 각도로 계산되는 시계 방향 원형 리빌 — 측정 누적면적 48.249% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

angFieldstrokeStyleputImageData

03값 노이즈 필드로 픽셀마다 등장 순서를 계산하는 디졸브

화면을 격자 칸으로 나눈 뒤 칸마다 2D 값 노이즈(noise2)로 고유한 문턱값을 미리 계산해 둡니다. 진행률이 그 문턱을 넘는 칸부터 다음 장면 조각을 그려 넣기 때문에, 같은 시드라면 흩어지는 순서가 매번 똑같습니다.

  function PRECOMPUTE() {
    cols = Math.ceil(W / NOISE_CELL_PX);
    rows = Math.ceil(H / NOISE_CELL_PX);
    cellNoise = new Float32Array(cols * rows);
    for (var r = 0; r < rows; r++) {
      for (var c = 0; c < cols; c++) cellNoise[r * cols + c] = noise2(c * NOISE_FREQ, r * NOISE_FREQ);
    }
  }

  function RENDER(p) {
    ctx.drawImage(bufB, 0, 0, W, H);
    var cw = Math.round(NOISE_CELL_PX * dpr), ch = Math.round(NOISE_CELL_PX * dpr);
    for (var r = 0; r < rows; r++) {
      for (var c = 0; c < cols; c++) {
        if (cellNoise[r * cols + c] <= p) {
          var sx = Math.round(c * NOISE_CELL_PX * dpr), sy = Math.round(r * NOISE_CELL_PX * dpr);
          ctx.drawImage(bufA, sx, sy, cw, ch, c * NOISE_CELL_PX, r * NOISE_CELL_PX, NOISE_CELL_PX, NOISE_CELL_PX);
        }
      }
    }
  }
값 노이즈 필드로 픽셀마다 등장 순서를 계산하는 디졸브
새 창에서 열기

03 · 값 노이즈 필드로 픽셀마다 등장 순서를 계산하는 디졸브 — 측정 누적면적 48.761% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

buildPermcellNoisefadeN

04블록 크기가 시간으로 계산되어 줄어드는 모자이크 리졸브

각 칸은 자기 차례가 되기 전까지 앞 장면을 그대로 보여주다가, 행·열로 미리 계산해 둔 순서에 이르면 그 칸의 평균색을 채운 사각형이 겹쳐 나타나며 매 프레임 높이를 줄여 나갑니다. 사각형 높이가 낮아지는 만큼 그 아래 이미 그려 둔 뒤 장면이 드러나고, 높이가 0 이 되면 그 칸에는 뒤 장면만 남습니다. 칸마다 차례가 다르게 오기 때문에 아직 차례가 안 된 칸의 앞 장면과 차례를 마친 칸의 뒤 장면이 한 프레임 안에 섞여 있어 마치 모자이크가 녹아내리는 것처럼 보입니다.

  function RENDER(p) {
    ctx.drawImage(bufB, 0, 0, W, H);
    for (var r = 0; r < rows; r++) {
      for (var c = 0; c < cols; c++) {
        var order = meltOrder[r * cols + c];
        var bx = c * BLOCK_PX, by = r * BLOCK_PX;
        if (order > p) {
          var sx = Math.round(c * BLOCK_PX * dpr), sy = Math.round(r * BLOCK_PX * dpr);
          ctx.drawImage(bufA, sx, sy, Math.round(BLOCK_PX * dpr), Math.round(BLOCK_PX * dpr), bx, by, BLOCK_PX, BLOCK_PX);
        } else if (order > p - MELT_WINDOW) {
          var u = (p - order) / MELT_WINDOW;
          var h = BLOCK_PX * (1 - u);
          ctx.fillStyle = avgColor[r * cols + c];
          ctx.fillRect(bx, by + BLOCK_PX * u * 0.6, BLOCK_PX, h);
        }
      }
    }
  }
블록 크기가 시간으로 계산되어 줄어드는 모자이크 리졸브
새 창에서 열기

04 · 블록 크기가 시간으로 계산되어 줄어드는 모자이크 리졸브 — 측정 누적면적 48.051% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

avgColormeltOrderdrawImage

05삼각 조각마다 낙하를 계산하는 파편 전환

화면을 정사각 칸마다 두 개씩, 총 여러 개의 삼각 조각으로 나눈 뒤 각 조각 중심에서 원점까지의 거리를 미리 계산해 둡니다. 진행률이 그 거리를 지나는 조각부터 아래로 떨어지듯 이동하며 투명해지고, 그 자리에는 뒤 장면이 드러납니다.

  function RENDER(p) {
    ctx.drawImage(bufB, 0, 0, W, H);
    for (var i = 0; i < shards.length; i++) {
      var s = shards[i];
      if (s.dist > p) {
        ctx.save(); clipTri(s.tri);
        ctx.drawImage(bufA, 0, 0, cv.width, cv.height, 0, 0, W, H);
        ctx.restore();
      } else {
        var u = Math.min(1, (p - s.dist) / FALL_WINDOW);
        if (u < 1) {
          var off = u * u * FALL_DIST_PX;
          ctx.save();
          ctx.globalAlpha = 1 - u;
          ctx.translate(0, off);
          clipTri(s.tri);
          ctx.drawImage(bufA, 0, 0, cv.width, cv.height, 0, -off, W, H);
          ctx.restore();
        }
      }
    }
  }
삼각 조각마다 낙하를 계산하는 파편 전환
새 창에서 열기

05 · 삼각 조각마다 낙하를 계산하는 파편 전환 — 측정 누적면적 48.444% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

clipTriglobalAlphamaxDist

06가로줄 오프셋을 고정 시드로 계산하는 글리치 전환

화면을 얇은 가로줄로 잘게 나눈 뒤, 고정된 시드값 하나로 각 줄이 좌우로 얼마나 밀릴지를 미리 다 계산해 둡니다. 진행률이 그 줄의 순번을 지나는 순간 앞 장면 대신 뒤 장면을 그리기 시작하고, 순번에 가까운 줄일수록 계산해 둔 오프셋만큼 잠깐 어긋났다가 제자리로 모입니다.

  function PRECOMPUTE() {
    rowsN = Math.ceil(H / ROW_PX);
    var rng = makeLcg(SEED_GLITCH); // 고정 시드 — 프레임마다 다시 뽑지 않는다
    rowOffset = new Float32Array(rowsN);
    rowOrder = new Float32Array(rowsN);
    for (var i = 0; i < rowsN; i++) {
      rowOffset[i] = (rng() * 2 - 1) * GLITCH_MAX_PX;
      rowOrder[i] = i / rowsN;
    }
  }

  function RENDER(p) {
    for (var i = 0; i < rowsN; i++) {
      var order = rowOrder[i];
      var src = order <= p ? bufA : bufB;
      var near = Math.abs(order - p);
      var dx = near < GLITCH_WINDOW ? rowOffset[i] * (1 - near / GLITCH_WINDOW) : 0;
      var dy = i * ROW_PX;
      var sh = Math.min(ROW_PX, H - dy);
      if (sh <= 0) continue;
      var sy = Math.round(dy * dpr), shPx = Math.round(sh * dpr);
      ctx.drawImage(src, 0, sy, cv.width, shPx, dx, dy, W, sh);
    }
  }
가로줄 오프셋을 고정 시드로 계산하는 글리치 전환
새 창에서 열기

06 · 가로줄 오프셋을 고정 시드로 계산하는 글리치 전환 — 측정 누적면적 47.115% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

makeLcgrowOffsetrowOrder

07거리 필드로 번짐 반경을 계산하는 잉크 리빌

임의의 시작점 몇 개에서 화면의 모든 픽셀까지 거리를 미리 계산해 거리장(distance field)을 만들어 둡니다. 진행률이 그 거리를 넘어서는 픽셀부터 색이 바뀌는데, 거리에 값 노이즈를 살짝 섞어 둬서 원이 아니라 잉크가 번지듯 불규칙한 가장자리로 넓어집니다.

  function RENDER(p) {
    var o = outImg.data;
    for (var i = 0, n = distField.length; i < n; i++) {
      var t = distField[i], k = i * 4, reveal;
      if (t <= p) reveal = 1;
      else if (t <= p + EDGE_SOFT) reveal = 1 - (t - p) / EDGE_SOFT;
      else reveal = 0;
      o[k] = dataB[k] + (dataA[k] - dataB[k]) * reveal;
      o[k + 1] = dataB[k + 1] + (dataA[k + 1] - dataB[k + 1]) * reveal;
      o[k + 2] = dataB[k + 2] + (dataA[k + 2] - dataB[k + 2]) * reveal;
      o[k + 3] = 255;
    }
    ctx.putImageData(outImg, 0, 0);
  }
거리 필드로 번짐 반경을 계산하는 잉크 리빌
새 창에서 열기

07 · 거리 필드로 번짐 반경을 계산하는 잉크 리빌 — 측정 누적면적 48.568% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

distFieldnoiseIbuildPerm

08명암값으로 반지름을 계산하는 망점 리빌

이미지를 작은 격자로 나눈 뒤 칸마다 평균 밝기(0.299r+0.587g+0.114b)를 구해 점의 최대 반지름으로 미리 바꿔 둡니다. 진행률이 오를수록 모든 점이 함께 커지면서 인쇄 망점처럼 이미지를 채우는데, 밝은 칸일수록 점이 커서 먼저 눈에 띕니다.

  function PRECOMPUTE() {
    var data = bctxA.getImageData(0, 0, cv.width, cv.height).data;
    var cols = Math.ceil(W / DOT_PX), rows = Math.ceil(H / DOT_PX);
    dotsX = []; dotsY = []; dotR = []; dotColor = [];
    for (var r = 0; r < rows; r++) {
      for (var c = 0; c < cols; c++) {
        var cx = c * DOT_PX + DOT_PX / 2, cy = r * DOT_PX + DOT_PX / 2;
        var sx = Math.min(cv.width - 1, Math.round(cx * dpr)), sy = Math.min(cv.height - 1, Math.round(cy * dpr));
        var k = (sy * cv.width + sx) * 4;
        var rr = data[k], gg = data[k + 1], bb = data[k + 2];
        var bri = (0.299 * rr + 0.587 * gg + 0.114 * bb) / 255; // 밝기 -> 반지름
        dotsX.push(cx); dotsY.push(cy);
        dotR.push(bri * DOT_PX * DOT_MAX_SCALE);
        dotColor.push("rgb(" + rr + "," + gg + "," + bb + ")");
      }
    }
  }
명암값으로 반지름을 계산하는 망점 리빌
새 창에서 열기

08 · 명암값으로 반지름을 계산하는 망점 리빌 — 측정 누적면적 34.422% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

dotsXdotRdotColor

09사인 곡선 위상차로 경계를 계산하는 물결 리빌

화면을 가로지르는 경계선의 높이를 열(세로줄)마다 사인 곡선과 열 번호로 계산한 위상차로 정합니다. 열마다 조금씩 다른 위상으로 흔들리기 때문에 직선이 아니라 물결치는 수면 같은 곡선이 아래에서 위로 올라오며 다음 장면을 드러냅니다.

  var SEED_A = 901, SEED_B = 902;
  var PLAY_MS = 1000;
  var WAVE_AMPLITUDE_PX = 10; // 물결 진폭 — 변형: 10px -> 26px(더 거친 물결)
  var WAVE_FREQ = 0.045;
  var WAVE_PHASE_SPEED = 5.5;
  var COL_STEP = 3;

  function PRECOMPUTE() {}

  function RENDER(p) {
    ctx.drawImage(bufB, 0, 0, W, H);
    var baseY = H * (1 - p);
    var amp = WAVE_AMPLITUDE_PX * Math.sin(p * Math.PI);
    for (var x = 0; x < W; x += COL_STEP) {
      var y = baseY + Math.sin(x * WAVE_FREQ + p * WAVE_PHASE_SPEED) * amp;
      if (y >= H) continue;
      y = Math.max(0, y);
      ctx.save();
      ctx.beginPath(); ctx.rect(x, y, COL_STEP, H - y + 1); ctx.clip();
      ctx.drawImage(bufA, 0, 0, W, H);
      ctx.restore();
    }
  }
사인 곡선 위상차로 경계를 계산하는 물결 리빌
새 창에서 열기

09 · 사인 곡선 위상차로 경계를 계산하는 물결 리빌 — 측정 누적면적 47.283% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

baseYbeginPathdrawImage

값이 화면을 바꾸는 곳 — 대각 좌표·노이즈 문턱·고정 시드

계산이 도는 위치는 다르지만 상수 하나가 바뀌면 화면 전체의 인상이 바뀐다는 점은 아홉 개가 같습니다. 01·07 의 EDGE_SOFT 는 경계를 얼마나 흐릴지 정하고, 03 의 NOISE_CELL_PX 는 한 번에 나타나는 덩어리 크기를, 09 의 WAVE_AMPLITUDE_PX 는 물결이 얼마나 거칠어질지 정합니다. 세 상수 모두 값 하나만 옮기면 되니, zip 을 열어 그 줄만 고쳐 보면 계산이 화면에 어떻게 반영되는지 바로 확인할 수 있습니다.

이웃 가지

어디서 깨지는가 — 접근성·reduced-motion·저사양

아홉 개가 공통으로 겪는 함정은 캔버스 크기입니다. fit() 이 패널의 실제 픽셀 크기(devicePixelRatio 포함)로 캔버스를 다시 잡고 두 장면 버퍼를 새로 그리는데, 이 재계산을 건너뛰고 CSS 로만 캔버스 요소 크기를 늘리면 픽셀 필드와 실제 화면 크기가 어긋나 경계선이 비뚤어집니다. resize 리스너 하나로 이 문제를 막고 있으니, 컨테이너 크기를 자바스크립트로 강제로 바꾸는 프레임워크에 옮겨 쓸 때는 그 시점에 fit() 을 다시 불러야 합니다. 이 편에서 아홉 개 소스를 통째로 담은 파일을 열 때 압축 비밀번호는 zd738fjg 이고, 아래 카드에서 받을 수 있습니다.

동작 줄이기(prefers-reduced-motion) 설정에서는 아홉 개 모두 같은 방식으로 반응합니다. 자동으로 도는 시연 루프(driveTick)를 아예 시작하지 않고 is-still 클래스만 추가한 뒤, 진행률을 0.5 로 고정해 RENDER(0.5) 를 한 번만 불러 중간 장면을 그립니다. 이 상태에서도 클릭이나 Enter 를 누르면 진행률이 0과 1 사이를 즉시 오가며 다음 장면으로 바뀌므로, 움직임은 줄었어도 전환 자체를 확인하는 기능은 남습니다.

키보드·스크린리더

각 데모 패널에는 role="button" 과 tabindex="0", 그리고 지금 재생 중인 효과 이름이 들어간 aria-label 이 붙어 있어 탭 키만으로 초점을 옮기고 Enter 또는 Space 로 다시 재생할 수 있습니다. 캔버스 자체는 aria-hidden="true" 로 감춰서 스크린리더가 픽셀 그림을 읽으려 하지 않고, 안내 문구는 패널 위 rv__hint 요소가 대신 전달합니다.

변형 세 가지와 접근성

이름 값 느낌
대각선 리빌 경계 부드러움 폭 값 조정 01 EDGE_SOFT 0.05 → 0.015 경계선이 더 날카롭게 잘리는 쪽에 가까워집니다
노이즈 디졸브 격자 칸 크기 값 조정 03 NOISE_CELL_PX 16px → 8px 한 번에 나타나는 덩어리가 작아져 더 잘게 흩어집니다
물결 리빌 사인 위상 진폭 값 조정 09 WAVE_AMPLITUDE_PX 10px → 26px 경계선의 물결 굴곡이 더 뚜렷하고 거칠어집니다

세 변형 모두 반응형·접근성 코드는 건드리지 않습니다. fit()·resize 리스너·is-still 분기는 상수 값과 무관하게 그대로 동작하므로, 값만 바꾼 사본을 만들어도 저사양 기기나 동작 줄이기 설정에서 깨지지 않습니다.

화면 전환 이펙트 · 이미지 전환 효과 예제는 어디서 보나?

브라우저가 픽셀을 직접 읽고 쓰는 방식은 MDN 의 getImageData 와 putImageData 문서에 정리되어 있고, 프레임을 언제 다시 그릴지는 requestAnimationFrame 문서를 보면 됩니다. 위 아홉 개는 이 API 세 개와 삼각형 잘라내기(clip)만으로 만든 결과이니, 직접 검색해 표준 동작을 확인하면서 압축을 풀어 코드를 그대로 옮겨 써도 됩니다.

FAQ

이 아홉 개 전환 효과는 실제로 반복 실행해도 항상 같은 모양으로 나오나요?

그렇습니다. 아홉 개 모두 Math.random 을 한 번도 부르지 않고 선형합동생성기(LCG) 하나로 장면과 노이즈를 만들기 때문에, 같은 시드값이면 새로고침을 몇 번 해도 색 배치와 흩어지는 순서가 그대로 반복됩니다.

이 화면 전환 이펙트 아홉 개를 다른 프로젝트에도 그대로 옮겨 써도 되나요?

네, 라이브러리나 CDN 없이 순수 캔버스 2D 와 자바스크립트만으로 짜여 있어서 파일만 그대로 옮기면 됩니다. 아홉 개 소스 전체는 위 zip 카드에서 비밀번호로 압축을 풀어 받을 수 있습니다.

느린 기기나 동작 줄이기 설정에서는 어떻게 보이나요?

prefers-reduced-motion 이 켜져 있으면 자동 시연 루프가 아예 돌지 않고 진행률 0.5 인 중간 장면 하나만 정지해서 보여줍니다. 클릭이나 Enter 를 누르면 그 자리에서 즉시 다음 장면으로 바뀌므로, 계산 자체는 그대로 남고 자동으로 움직이는 부분만 꺼집니다.

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

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