05Content Transition Where a Noise-intensity Value Fades Out
A noise-intensity value spikes to its maximum for an instant to flash the screen white, then as the value fades to zero the content underneath becomes clear.
Best forTab switches and notification-banner entrances
NOISE_BURST_DURATIONframeCounterhash01

<main class="stage">
<div class="nb is-demo" id="nb">
<p class="nb__hint"><span>Click or press Enter and the noise-burst value spikes to its maximum then settles back down to 0</span></p>
<div class="nb__panel" id="panel" tabindex="0" role="button" aria-label="Replay notification banner transition where noise-burst value fades out">
<canvas class="nb__cv" id="cv" aria-hidden="true"></canvas>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 계산 겹(노이즈 버스트 렌더)은 index.html 의 PRECOMPUTE()/RENDER() 에 있다. 여기는 무대·레이아웃만.
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-cream;
@include stage($stage-ink);
.nb {
--scene-bg1: #{$subject-blue-deep};
--scene-bg2: #{$subject-blue};
--scene-card: #{$subject-cream};
--scene-icon: #{$subject-mint};
--scene-line: #{$subject-blue-light};
--accent: #{$color};
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-2;
width: min(400px, 92vw);
}
.nb__hint {
margin: 0;
color: rgba($subject-cream, .78);
font-size: 12px;
font-weight: 500;
line-height: 1.35;
text-align: center;
}
.nb__panel {
position: relative;
width: 100%;
height: 176px;
border-radius: $r-card;
overflow: hidden;
cursor: pointer;
box-shadow: 0 0 0 1px rgba($subject-cream, .14);
transition: box-shadow $duration $easing;
}
.nb__panel:hover {
box-shadow: 0 0 0 1px $color;
}
.nb__panel:focus-visible {
outline: 2px solid $color;
outline-offset: 2px;
}
.nb__cv {
display: block;
width: 100%;
height: 100%;
}
@media (max-width: 340px) {
.nb__hint { font-size: 11px; }
.nb__panel, .nb__cv { height: 120px; }
}
@media (prefers-reduced-motion: reduce) {
.nb__panel { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.nb {
--scene-bg1: #0a4fe8;
--scene-bg2: #2f6df6;
--scene-card: #fff7e6;
--scene-icon: #4cd4a6;
--scene-line: #5b8cf8;
--accent: #fff7e6;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
width: min(400px, 92vw);
}
.nb__hint {
margin: 0;
color: rgba(255, 247, 230, 0.78);
font-size: 12px;
font-weight: 500;
line-height: 1.35;
text-align: center;
}
.nb__panel {
position: relative;
width: 100%;
height: 176px;
border-radius: 16px;
overflow: hidden;
cursor: pointer;
box-shadow: 0 0 0 1px rgba(255, 247, 230, 0.14);
transition: box-shadow 2s ease-out;
}
.nb__panel:hover {
box-shadow: 0 0 0 1px #fff7e6;
}
.nb__panel:focus-visible {
outline: 2px solid #fff7e6;
outline-offset: 2px;
}
.nb__cv {
display: block;
width: 100%;
height: 100%;
}
@media (max-width: 340px) {
.nb__hint {
font-size: 11px;
}
.nb__panel, .nb__cv {
height: 120px;
}
}
@media (prefers-reduced-motion: reduce) {
.nb__panel {
transition: none;
}
}(function () {
var root = document.getElementById('nb');
var panel = document.getElementById('panel');
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var isDemo = true;
// 결정론 난수 — Math.random 을 쓰지 않는다. LCG(선형합동생성기) 하나로 장면을 만든다.
function makeLcg(seed) {
var s = seed >>> 0;
return function () { s = (s * 1664525 + 1013904223) >>> 0; return s / 4294967296; };
}
var css = getComputedStyle(root);
function tok(name, fb) { var v = css.getPropertyValue(name); return v ? (v.trim() || fb) : fb; }
var BG1 = tok('--scene-bg1', '#1c2f66');
var BG2 = tok('--scene-bg2', '#2f6df6');
var CARD = tok('--scene-card', '#fff7e6');
var ICON = tok('--scene-icon', '#8be6c4');
var LINE = tok('--scene-line', '#9fc3ff');
var W = 0, H = 0, dpr = 1;
var buf = document.createElement('canvas');
var bctx = buf.getContext('2d');
// 장면(알림 배너가 탭 위에 뜬 자리) — 전부 캔버스로 직접 그린다. 외부 사진·파일 0.
function paintScene(bc, w, h, seed) {
var rng = makeLcg(seed);
var g = bc.createLinearGradient(0, 0, 0, h);
g.addColorStop(0, BG1); g.addColorStop(1, BG2);
bc.fillStyle = g;
bc.fillRect(0, 0, w, h);
var bx = w * 0.1, by = h * 0.2, bw = w * 0.8, bh = h * 0.6, r = Math.min(bw, bh) * 0.12;
bc.fillStyle = CARD;
if (bc.roundRect) { bc.beginPath(); bc.roundRect(bx, by, bw, bh, r); bc.fill(); }
else { bc.fillRect(bx, by, bw, bh); }
bc.fillStyle = ICON;
bc.beginPath();
bc.arc(bx + bh * 0.24, by + bh * 0.5, bh * 0.16, 0, Math.PI * 2);
bc.fill();
bc.fillStyle = LINE;
for (var i = 0; i < 4; i++) {
var lw = bw * (0.42 - rng() * 0.14);
var ly = by + bh * 0.28 + i * bh * 0.14;
bc.fillRect(bx + bh * 0.46, ly, lw, bh * 0.07);
}
}
var SEED = 705;
var NOISE_BURST_DURATION = 560;
var data, outImg, iw, ih;
function PRECOMPUTE() {
iw = cv.width; ih = cv.height;
data = bctx.getImageData(0, 0, iw, ih).data;
outImg = ctx.createImageData(iw, ih);
}
function hash01(n) {
n = (n ^ 61) ^ (n >>> 16);
n = (n + (n << 3)) | 0;
n = n ^ (n >>> 4);
n = Math.imul(n, 0x27d4eb2d);
n = n ^ (n >>> 15);
return (n >>> 0) / 4294967295;
}
var frameCounter = 0;
function clamp255(v) { return v < 0 ? 0 : (v > 255 ? 255 : v); }
function RENDER(p) {
frameCounter = (frameCounter + 1) >>> 0;
var o = outImg.data;
var fSalt = Math.imul(frameCounter, 2246822519) >>> 0;
for (var y = 0; y < ih; y++) {
var ySalt = Math.imul(y, 668265263) >>> 0;
var base = y * iw;
for (var x = 0; x < iw; x++) {
var seed = (Math.imul(x, 374761393) ^ ySalt ^ fSalt) >>> 0;
var h0 = hash01(seed);
var h1 = hash01(seed ^ 0x9e3779b9);
var h2 = hash01(seed ^ 0x85ebca6b);
var h3 = hash01(seed ^ 0xc2b2ae35);
var g = 198 + h0 * 57;
var nr = clamp255(g + (h1 - 0.5) * 60);
var ng = clamp255(g + (h2 - 0.5) * 60);
var nb = clamp255(g + (h3 - 0.5) * 60);
var k = (base + x) * 4;
o[k] = data[k] * (1 - p) + nr * p;
o[k + 1] = data[k + 1] * (1 - p) + ng * p;
o[k + 2] = data[k + 2] * (1 - p) + nb * p;
o[k + 3] = 255;
}
}
ctx.putImageData(outImg, 0, 0);
}
function fit() {
var r = panel.getBoundingClientRect();
dpr = window.devicePixelRatio || 1;
W = r.width; H = r.height;
cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr);
buf.width = cv.width; buf.height = cv.height;
paintScene(bctx, buf.width, buf.height, SEED);
PRECOMPUTE();
}
function human() { if (isDemo) { root.classList.remove('is-demo'); isDemo = false; } }
var progress = 0, target = 0, playT0 = 0, playFrom = 0, playing = false;
function easeInOut(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }
function play() {
human();
playFrom = progress; target = progress < 0.5 ? 1 : 0;
playT0 = 0; playing = true;
}
function stepPlay(now) {
if (!playing) return;
if (!playT0) playT0 = now;
var t = Math.min(1, (now - playT0) / NOISE_BURST_DURATION);
progress = playFrom + (target - playFrom) * easeInOut(t);
if (t >= 1) { playing = false; progress = target; }
}
var DRIVE_PERIOD_MS = 2000;
var driveStart = 0;
function triangle(frac) { return frac < 0.5 ? frac * 2 : 2 - frac * 2; }
function driveTick(now) {
if (!driveStart) driveStart = now;
if (isDemo) {
var frac = ((now - driveStart) % DRIVE_PERIOD_MS) / DRIVE_PERIOD_MS;
progress = triangle(frac);
}
requestAnimationFrame(driveTick);
}
function frame(now) {
stepPlay(now);
RENDER(progress);
requestAnimationFrame(frame);
}
window.addEventListener('resize', fit);
fit();
if (reduce) {
root.classList.add('is-still');
progress = 0; RENDER(progress);
panel.addEventListener('click', function () { human(); progress = progress < 0.5 ? 1 : 0; RENDER(progress); });
panel.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); human(); progress = progress < 0.5 ? 1 : 0; RENDER(progress); }
});
} else {
panel.addEventListener('click', play);
panel.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); play(); }
});
requestAnimationFrame(frame);
requestAnimationFrame(driveTick);
}
})();import { useEffect, useRef } from "react";
import styles from "./NoiseBurst.module.scss";
export default function NoiseBurst() {
const rootRef = useRef<HTMLDivElement | null>(null);
const panelRef = useRef<HTMLDivElement | null>(null);
const cvRef = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
const root = rootRef.current;
const panel = panelRef.current;
const cv = cvRef.current;
if (!root || !panel || !cv) return;
const ctx = cv.getContext("2d");
if (!ctx) return;
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let isDemo = true;
// 결정론 난수 — Math.random 을 쓰지 않는다. LCG(선형합동생성기) 하나로 장면을 만든다.
function makeLcg(seed: number) {
let s = seed >>> 0;
return function () {
s = (s * 1664525 + 1013904223) >>> 0;
return s / 4294967296;
};
}
const css = getComputedStyle(root);
function tok(name: string, fb: string) {
const v = css.getPropertyValue(name);
return v ? v.trim() || fb : fb;
}
const BG1 = tok("--scene-bg1", "#1c2f66");
const BG2 = tok("--scene-bg2", "#2f6df6");
const CARD = tok("--scene-card", "#fff7e6");
const ICON = tok("--scene-icon", "#8be6c4");
const LINE = tok("--scene-line", "#9fc3ff");
let W = 0;
let H = 0;
let dpr = 1;
const buf = document.createElement("canvas");
const bctx = buf.getContext("2d");
if (!bctx) return;
// 장면(알림 배너가 탭 위에 뜬 자리) — 전부 캔버스로 직접 그린다. 외부 사진·파일 0.
function paintScene(bc: CanvasRenderingContext2D, w: number, h: number, seed: number) {
const rng = makeLcg(seed);
const g = bc.createLinearGradient(0, 0, 0, h);
g.addColorStop(0, BG1);
g.addColorStop(1, BG2);
bc.fillStyle = g;
bc.fillRect(0, 0, w, h);
const bx = w * 0.1;
const by = h * 0.2;
const bw = w * 0.8;
const bh = h * 0.6;
const r = Math.min(bw, bh) * 0.12;
bc.fillStyle = CARD;
if ((bc as any).roundRect) {
bc.beginPath();
(bc as any).roundRect(bx, by, bw, bh, r);
bc.fill();
} else {
bc.fillRect(bx, by, bw, bh);
}
bc.fillStyle = ICON;
bc.beginPath();
bc.arc(bx + bh * 0.24, by + bh * 0.5, bh * 0.16, 0, Math.PI * 2);
bc.fill();
bc.fillStyle = LINE;
for (let i = 0; i < 4; i++) {
const lw = bw * (0.42 - rng() * 0.14);
const ly = by + bh * 0.28 + i * bh * 0.14;
bc.fillRect(bx + bh * 0.46, ly, lw, bh * 0.07);
}
}
const SEED = 705;
const NOISE_BURST_DURATION = 560;
let data: Uint8ClampedArray | null = null;
let outImg: ImageData | null = null;
let iw = 0;
let ih = 0;
function PRECOMPUTE() {
iw = cv!.width;
ih = cv!.height;
data = bctx!.getImageData(0, 0, iw, ih).data;
outImg = ctx!.createImageData(iw, ih);
}
function hash01(n: number) {
n = (n ^ 61) ^ (n >>> 16);
n = (n + (n << 3)) | 0;
n = n ^ (n >>> 4);
n = Math.imul(n, 0x27d4eb2d);
n = n ^ (n >>> 15);
return (n >>> 0) / 4294967295;
}
let frameCounter = 0;
function clamp255(v: number) {
return v < 0 ? 0 : v > 255 ? 255 : v;
}
function RENDER(p: number) {
frameCounter = (frameCounter + 1) >>> 0;
const o = outImg!.data;
const d = data!;
const fSalt = Math.imul(frameCounter, 2246822519) >>> 0;
for (let y = 0; y < ih; y++) {
const ySalt = Math.imul(y, 668265263) >>> 0;
const base = y * iw;
for (let x = 0; x < iw; x++) {
const seed = (Math.imul(x, 374761393) ^ ySalt ^ fSalt) >>> 0;
const h0 = hash01(seed);
const h1 = hash01(seed ^ 0x9e3779b9);
const h2 = hash01(seed ^ 0x85ebca6b);
const h3 = hash01(seed ^ 0xc2b2ae35);
const g = 198 + h0 * 57;
const nr = clamp255(g + (h1 - 0.5) * 60);
const ng = clamp255(g + (h2 - 0.5) * 60);
const nb = clamp255(g + (h3 - 0.5) * 60);
const k = (base + x) * 4;
o[k] = d[k] * (1 - p) + nr * p;
o[k + 1] = d[k + 1] * (1 - p) + ng * p;
o[k + 2] = d[k + 2] * (1 - p) + nb * p;
o[k + 3] = 255;
}
}
ctx!.putImageData(outImg!, 0, 0);
}
function fit() {
const r = panel!.getBoundingClientRect();
dpr = window.devicePixelRatio || 1;
W = r.width;
H = r.height;
cv!.width = Math.round(W * dpr);
cv!.height = Math.round(H * dpr);
buf.width = cv!.width;
buf.height = cv!.height;
paintScene(bctx!, buf.width, buf.height, SEED);
PRECOMPUTE();
}
function human() {
if (isDemo) {
root!.classList.remove("is-demo");
isDemo = false;
}
}
let progress = 0;
let target = 0;
let playT0 = 0;
let playFrom = 0;
let playing = false;
function easeInOut(t: number) {
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
}
function play() {
human();
playFrom = progress;
target = progress < 0.5 ? 1 : 0;
playT0 = 0;
playing = true;
}
function stepPlay(now: number) {
if (!playing) return;
if (!playT0) playT0 = now;
const t = Math.min(1, (now - playT0) / NOISE_BURST_DURATION);
progress = playFrom + (target - playFrom) * easeInOut(t);
if (t >= 1) {
playing = false;
progress = target;
}
}
const DRIVE_PERIOD_MS = 2000;
let driveStart = 0;
let rafFrame = 0;
let rafDrive = 0;
function triangle(frac: number) {
return frac < 0.5 ? frac * 2 : 2 - frac * 2;
}
function driveTick(now: number) {
if (!driveStart) driveStart = now;
if (isDemo) {
const frac = ((now - driveStart) % DRIVE_PERIOD_MS) / DRIVE_PERIOD_MS;
progress = triangle(frac);
}
rafDrive = requestAnimationFrame(driveTick);
}
function frame(now: number) {
stepPlay(now);
RENDER(progress);
rafFrame = requestAnimationFrame(frame);
}
function onClick() {
play();
}
function onKeyDown(e: Event) {
const ke = e as KeyboardEvent;
if (ke.key === "Enter" || ke.key === " ") {
ke.preventDefault();
play();
}
}
function onClickReduce() {
human();
progress = progress < 0.5 ? 1 : 0;
RENDER(progress);
}
function onKeyDownReduce(e: Event) {
const ke = e as KeyboardEvent;
if (ke.key === "Enter" || ke.key === " ") {
ke.preventDefault();
human();
progress = progress < 0.5 ? 1 : 0;
RENDER(progress);
}
}
window.addEventListener("resize", fit);
fit();
if (reduce) {
root.classList.add("is-still");
progress = 0;
RENDER(progress);
panel.addEventListener("click", onClickReduce);
panel.addEventListener("keydown", onKeyDownReduce);
} else {
panel.addEventListener("click", onClick);
panel.addEventListener("keydown", onKeyDown);
rafFrame = requestAnimationFrame(frame);
rafDrive = requestAnimationFrame(driveTick);
}
return () => {
window.removeEventListener("resize", fit);
cancelAnimationFrame(rafFrame);
cancelAnimationFrame(rafDrive);
if (reduce) {
panel.removeEventListener("click", onClickReduce);
panel.removeEventListener("keydown", onKeyDownReduce);
} else {
panel.removeEventListener("click", onClick);
panel.removeEventListener("keydown", onKeyDown);
}
};
}, []);
return (
<main className={styles.stage}>
<div className={`${styles.root} is-demo`} ref={rootRef}>
<p className={styles.hint}>
<span>클릭하거나 Enter 를 누르면 노이즈 버스트 값이 최댓값까지 튀었다가 0 으로 잦아듭니다</span>
</p>
<div
className={styles.panel}
ref={panelRef}
tabIndex={0}
role="button"
aria-label="노이즈 버스트 값이 사그라드는 알림 배너 전환 다시 재생"
>
<canvas className={styles.cv} ref={cvRef} aria-hidden="true" />
</div>
</div>
</main>
);
}05 Content Transition Where a Noise-intensity Value Fades Out — Measured area 48.761% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · 9 CSS Glitch Effect Transitions (No Codepen)
The post this demo lives in
9 CSS Glitch Effect Transitions (No Codepen)This is a css glitch effect: An offset value pulls RGB channels apart on load, then back together. The same trick spikes See all demos in this post
If you like this effect - similar demos
Transitions06 Empty-state crossfade — a message that fades out the instant the item count value leaves zeroCart Drawer UI Patterns: 9 Motion Techniques
Transitions07 Transition a Noise-field Value Drifts a Dissolve9 CSS Page Transition Effects, Canvas Particles
Transitions08 Transition Where Cards Slide as a Sort-Order Value ChangesView Transitions API: 9 Patterns by One Value