アニメーション・シェーダー背景
フルスクリーンの板ポリにフラグメントシェーダーで描く、時間で流れるグラデと簡易ノイズの背景。マウスで色相が微妙に変化します。ヒーローや背景演出に最適です。
外部ライブラリ: https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura:流れる桜色シェーダー背景のニュース/新曲告知ヒーロー -->
<section class="sk-hero" aria-label="Sakura トップ">
<!-- 背景:時間で流れる桜色グラデ+ノイズ -->
<canvas id="shaderbg" class="sk-hero__canvas" aria-hidden="true"></canvas>
<div class="sk-hero__fallback" id="sk-fallback" hidden></div>
<header class="sk-bar">
<span class="sk-logo">🌸 Sakura</span>
<nav class="sk-nav">
<a href="#">メンバー</a>
<a href="#">ライブ</a>
<a href="#">楽曲</a>
</nav>
</header>
<div class="sk-hero__body">
<span class="sk-kicker">NEW SINGLE</span>
<h1 class="sk-title">きみと、春の<br>まんなかで。</h1>
<p class="sk-lead">7人組アイドルグループ Sakura、<br>4thシングルが本日配信スタート。</p>
<div class="sk-cta">
<button class="sk-btn" type="button">MVを見る</button>
<span class="sk-date">2026.06.07 RELEASE</span>
</div>
</div>
</section>
CSS
/* Sakura:桜色シェーダー背景のヒーロー */
:root {
--pink: #ffd1e0;
--pink-deep: #ff8fb3;
--white: #ffffff;
--gray: #f3f0f2;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
background: var(--pink);
}
.sk-hero {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
color: #5a2b3d;
}
.sk-hero__canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
/* フォールバック:桜色の静的グラデ */
.sk-hero__fallback {
position: absolute;
inset: 0;
background: linear-gradient(135deg, #ffe3ee 0%, var(--pink) 50%, #ffc0d8 100%);
}
.sk-bar {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 26px;
}
.sk-logo {
font-size: 18px;
font-weight: 800;
letter-spacing: 0.04em;
color: #fff;
text-shadow: 0 2px 8px rgba(214, 94, 140, 0.4);
}
.sk-nav { display: flex; gap: 20px; }
.sk-nav a {
color: rgba(255, 255, 255, 0.95);
text-decoration: none;
font-size: 13px;
font-weight: 600;
text-shadow: 0 1px 6px rgba(214, 94, 140, 0.35);
transition: opacity 0.2s ease;
}
.sk-nav a:hover { opacity: 0.75; }
.sk-hero__body {
position: relative;
z-index: 2;
max-width: 440px;
padding: 34px 26px;
color: #fff;
}
.sk-kicker {
font-size: 11px;
letter-spacing: 0.3em;
font-weight: 700;
text-shadow: 0 1px 6px rgba(214, 94, 140, 0.4);
}
.sk-title {
margin: 14px 0 14px;
font-size: 38px;
line-height: 1.3;
font-weight: 800;
text-shadow: 0 3px 16px rgba(180, 70, 110, 0.4);
}
.sk-lead {
margin: 0 0 24px;
font-size: 14px;
line-height: 1.9;
text-shadow: 0 1px 8px rgba(180, 70, 110, 0.35);
}
.sk-cta { display: flex; align-items: center; gap: 18px; }
.sk-btn {
font: inherit;
font-size: 14px;
font-weight: 800;
color: var(--pink-deep);
background: #fff;
border: none;
padding: 12px 28px;
border-radius: 999px;
cursor: pointer;
box-shadow: 0 8px 22px rgba(214, 94, 140, 0.4);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.sk-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(214, 94, 140, 0.55); }
.sk-btn:active { transform: translateY(0); }
.sk-date {
font-size: 12px;
letter-spacing: 0.08em;
font-weight: 600;
text-shadow: 0 1px 6px rgba(180, 70, 110, 0.35);
}
@media (prefers-reduced-motion: reduce) {
.sk-btn { transition: none; }
}
JavaScript
// Sakura ヒーロー:フラグメントシェーダーで流れる桜色グラデ+簡易ノイズ背景
(function () {
"use strict";
const canvas = document.getElementById("shaderbg");
const fallback = document.getElementById("sk-fallback");
// THREE未読込やcanvas不在なら安全に終了し、フォールバックを表示
if (!canvas || typeof THREE === "undefined") {
if (fallback) fallback.hidden = false;
return;
}
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let renderer;
try {
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
} catch (e) {
if (fallback) fallback.hidden = false;
return;
}
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const uniforms = {
time: { value: 0 },
resolution: { value: new THREE.Vector2(1, 1) },
shift: { value: 0 }, // マウスで動く色みのオフセット
};
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
// 桜ピンク〜白〜淡ピンクの間を流れるグラデ
const fragmentShader = `
precision highp float;
varying vec2 vUv;
uniform float time;
uniform vec2 resolution;
uniform float shift;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
u.y);
}
float fbm(vec2 p) {
float v = 0.0, a = 0.5;
for (int i = 0; i < 4; i++) { v += a * noise(p); p *= 2.0; a *= 0.5; }
return v;
}
void main() {
vec2 uv = vUv;
uv.x *= resolution.x / resolution.y;
float t = time * 0.06;
float n = fbm(uv * 2.0 + vec2(t, t * 0.5));
n += 0.4 * fbm(uv * 4.0 - vec2(t * 0.6, t));
// 桜のパレット
vec3 deep = vec3(1.0, 0.56, 0.70); // 濃いめの桜
vec3 mid = vec3(1.0, 0.82, 0.88); // 桜ピンク
vec3 light = vec3(1.0, 0.96, 0.98); // ほぼ白
float m = clamp(n + 0.25 * sin(vUv.y * 3.0 + time * 0.2) + shift, 0.0, 1.0);
vec3 col = mix(deep, mid, smoothstep(0.0, 0.55, m));
col = mix(col, light, smoothstep(0.55, 1.0, m));
// 上部をやや明るく、奥行きを出す
col += 0.06 * (1.0 - vUv.y);
gl_FragColor = vec4(col, 1.0);
}
`;
const material = new THREE.ShaderMaterial({ uniforms, vertexShader, fragmentShader });
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(mesh);
// マウスX位置で色みをわずかに動かす
let targetShift = 0;
canvas.addEventListener("pointermove", (e) => {
const r = canvas.getBoundingClientRect();
targetShift = ((e.clientX - r.left) / (r.width || 1) - 0.5) * 0.3;
});
function resize() {
const w = canvas.clientWidth || 1;
const h = canvas.clientHeight || 1;
renderer.setSize(w, h, false);
uniforms.resolution.value.set(w, h);
}
resize();
window.addEventListener("resize", resize);
let raf = 0;
let running = true;
const start = performance.now();
function animate(now) {
const t = (now - start) * 0.001;
if (!reduceMotion) uniforms.time.value = t;
uniforms.shift.value += (targetShift - uniforms.shift.value) * 0.05;
renderer.render(scene, camera);
raf = requestAnimationFrame(animate);
}
animate(start);
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
if (running) { cancelAnimationFrame(raf); running = false; }
} else if (!running) {
running = true;
raf = requestAnimationFrame(animate);
}
});
window.addEventListener("beforeunload", () => cancelAnimationFrame(raf));
})();
コード
HTML
<!-- フラグメントシェーダーで描く流れるグラデ背景 -->
<div class="stage">
<canvas id="shaderbg" aria-label="アニメーション・シェーダー背景"></canvas>
<div class="fallback" id="sg-fallback" hidden>WebGL を表示できない環境です</div>
<div class="caption">
<span class="badge">Shader</span>
<h2>Animated Gradient</h2>
<p>時間で流れる手続き的グラデ・マウスで色相が変化</p>
</div>
</div>
CSS
/* 配色変数 */
:root {
--ink: #f3f6ff;
--accent: #ffd1a8;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Segoe UI", system-ui, sans-serif;
overflow: hidden;
}
.stage {
position: relative;
width: 100%;
height: 360px;
/* シェーダー未対応時の控えめな下地 */
background: linear-gradient(135deg, #2a1a4a 0%, #6a3d8c 50%, #c46a8c 100%);
}
#shaderbg {
display: block;
width: 100%;
height: 100%;
}
/* WebGL非対応時のフォールバック表示 */
.fallback {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--ink);
font-size: 14px;
letter-spacing: .06em;
text-shadow: 0 2px 10px rgba(0, 0, 0, .6);
}
/* hidden 属性を尊重(CSSの display 指定が [hidden] を上書きしないように) */
.fallback[hidden] { display: none; }
.caption {
position: absolute;
left: 28px;
bottom: 24px;
color: var(--ink);
text-shadow: 0 2px 14px rgba(0, 0, 0, .55);
pointer-events: none;
}
.badge {
display: inline-block;
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
background: rgba(255, 209, 168, .18);
border: 1px solid rgba(255, 209, 168, .5);
color: var(--accent);
margin-bottom: 10px;
}
.caption h2 {
font-size: 22px;
font-weight: 700;
}
.caption p {
margin-top: 4px;
font-size: 13px;
opacity: .8;
}
JavaScript
// アニメーション・シェーダー背景:板ポリ+フラグメントシェーダーで流れるグラデ+簡易ノイズ
(function () {
"use strict";
const canvas = document.getElementById("shaderbg");
const fallback = document.getElementById("sg-fallback");
// THREE未読込やcanvas不在なら安全に終了し、フォールバックを表示
if (!canvas || typeof THREE === "undefined") {
if (fallback) fallback.hidden = false;
return;
}
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// WebGL初期化(失敗時はフォールバック)
let renderer;
try {
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
} catch (e) {
if (fallback) fallback.hidden = false;
return;
}
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const scene = new THREE.Scene();
// スクリーン全面を覆う正射影カメラ
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
// フラグメントシェーダー:流れるグラデ+valueノイズ+色相シフト
const uniforms = {
time: { value: 0 },
resolution: { value: new THREE.Vector2(1, 1) },
hue: { value: 0 }, // マウスで動く色相オフセット
};
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
const fragmentShader = `
precision highp float;
varying vec2 vUv;
uniform float time;
uniform vec2 resolution;
uniform float hue;
// 擬似乱数とvalueノイズ
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f); // スムーズ補間
return mix(
mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
u.y);
}
// 重ね合わせで雲状のうねりを作る
float fbm(vec2 p) {
float v = 0.0, a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * noise(p);
p *= 2.0;
a *= 0.5;
}
return v;
}
// HSV → RGB
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
// アスペクト比を補正したUV
vec2 uv = vUv;
uv.x *= resolution.x / resolution.y;
float t = time * 0.08;
// 時間で流れるノイズ場
float n = fbm(uv * 2.2 + vec2(t, t * 0.6));
n += 0.4 * fbm(uv * 4.0 - vec2(t * 0.7, t));
// 流れる色相(位置+時間+ノイズ+マウス)
float h = fract(0.6 + uv.x * 0.15 + uv.y * 0.1 + n * 0.35 + time * 0.01 + hue);
float s = 0.55 + 0.2 * n;
float v = 0.55 + 0.4 * n;
vec3 col = hsv2rgb(vec3(h, s, v));
// 中央をわずかに明るくして奥行きを出す
float d = distance(vUv, vec2(0.5));
col *= 1.0 - 0.35 * d;
gl_FragColor = vec4(col, 1.0);
}
`;
const material = new THREE.ShaderMaterial({ uniforms, vertexShader, fragmentShader });
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(mesh);
// マウスのX位置で色相オフセットを少し動かす
let targetHue = 0;
canvas.addEventListener("pointermove", (e) => {
const r = canvas.getBoundingClientRect();
targetHue = ((e.clientX - r.left) / (r.width || 1) - 0.5) * 0.4;
});
function resize() {
const w = canvas.clientWidth || 1;
const h = canvas.clientHeight || 1;
renderer.setSize(w, h, false);
uniforms.resolution.value.set(w, h);
}
resize();
window.addEventListener("resize", resize);
let raf = 0;
let running = true;
const start = performance.now();
function animate(now) {
const t = (now - start) * 0.001;
if (!reduceMotion) uniforms.time.value = t;
// 色相をマウス方向へイージング
uniforms.hue.value += (targetHue - uniforms.hue.value) * 0.05;
renderer.render(scene, camera);
raf = requestAnimationFrame(animate);
}
animate(start);
// タブ非表示で停止、復帰で再開
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
if (running) { cancelAnimationFrame(raf); running = false; }
} else if (!running) {
running = true;
raf = requestAnimationFrame(animate);
}
});
window.addEventListener("beforeunload", () => cancelAnimationFrame(raf));
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「アニメーション・シェーダー背景」の効果を追加してください。
# 追加してほしい効果
アニメーション・シェーダー背景(WebGL / Three.js)
フルスクリーンの板ポリにフラグメントシェーダーで描く、時間で流れるグラデと簡易ノイズの背景。マウスで色相が微妙に変化します。ヒーローや背景演出に最適です。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- フラグメントシェーダーで描く流れるグラデ背景 -->
<div class="stage">
<canvas id="shaderbg" aria-label="アニメーション・シェーダー背景"></canvas>
<div class="fallback" id="sg-fallback" hidden>WebGL を表示できない環境です</div>
<div class="caption">
<span class="badge">Shader</span>
<h2>Animated Gradient</h2>
<p>時間で流れる手続き的グラデ・マウスで色相が変化</p>
</div>
</div>
【CSS】
/* 配色変数 */
:root {
--ink: #f3f6ff;
--accent: #ffd1a8;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Segoe UI", system-ui, sans-serif;
overflow: hidden;
}
.stage {
position: relative;
width: 100%;
height: 360px;
/* シェーダー未対応時の控えめな下地 */
background: linear-gradient(135deg, #2a1a4a 0%, #6a3d8c 50%, #c46a8c 100%);
}
#shaderbg {
display: block;
width: 100%;
height: 100%;
}
/* WebGL非対応時のフォールバック表示 */
.fallback {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--ink);
font-size: 14px;
letter-spacing: .06em;
text-shadow: 0 2px 10px rgba(0, 0, 0, .6);
}
/* hidden 属性を尊重(CSSの display 指定が [hidden] を上書きしないように) */
.fallback[hidden] { display: none; }
.caption {
position: absolute;
left: 28px;
bottom: 24px;
color: var(--ink);
text-shadow: 0 2px 14px rgba(0, 0, 0, .55);
pointer-events: none;
}
.badge {
display: inline-block;
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
background: rgba(255, 209, 168, .18);
border: 1px solid rgba(255, 209, 168, .5);
color: var(--accent);
margin-bottom: 10px;
}
.caption h2 {
font-size: 22px;
font-weight: 700;
}
.caption p {
margin-top: 4px;
font-size: 13px;
opacity: .8;
}
【JavaScript】
// アニメーション・シェーダー背景:板ポリ+フラグメントシェーダーで流れるグラデ+簡易ノイズ
(function () {
"use strict";
const canvas = document.getElementById("shaderbg");
const fallback = document.getElementById("sg-fallback");
// THREE未読込やcanvas不在なら安全に終了し、フォールバックを表示
if (!canvas || typeof THREE === "undefined") {
if (fallback) fallback.hidden = false;
return;
}
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// WebGL初期化(失敗時はフォールバック)
let renderer;
try {
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
} catch (e) {
if (fallback) fallback.hidden = false;
return;
}
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const scene = new THREE.Scene();
// スクリーン全面を覆う正射影カメラ
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
// フラグメントシェーダー:流れるグラデ+valueノイズ+色相シフト
const uniforms = {
time: { value: 0 },
resolution: { value: new THREE.Vector2(1, 1) },
hue: { value: 0 }, // マウスで動く色相オフセット
};
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
const fragmentShader = `
precision highp float;
varying vec2 vUv;
uniform float time;
uniform vec2 resolution;
uniform float hue;
// 擬似乱数とvalueノイズ
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f); // スムーズ補間
return mix(
mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
u.y);
}
// 重ね合わせで雲状のうねりを作る
float fbm(vec2 p) {
float v = 0.0, a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * noise(p);
p *= 2.0;
a *= 0.5;
}
return v;
}
// HSV → RGB
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
// アスペクト比を補正したUV
vec2 uv = vUv;
uv.x *= resolution.x / resolution.y;
float t = time * 0.08;
// 時間で流れるノイズ場
float n = fbm(uv * 2.2 + vec2(t, t * 0.6));
n += 0.4 * fbm(uv * 4.0 - vec2(t * 0.7, t));
// 流れる色相(位置+時間+ノイズ+マウス)
float h = fract(0.6 + uv.x * 0.15 + uv.y * 0.1 + n * 0.35 + time * 0.01 + hue);
float s = 0.55 + 0.2 * n;
float v = 0.55 + 0.4 * n;
vec3 col = hsv2rgb(vec3(h, s, v));
// 中央をわずかに明るくして奥行きを出す
float d = distance(vUv, vec2(0.5));
col *= 1.0 - 0.35 * d;
gl_FragColor = vec4(col, 1.0);
}
`;
const material = new THREE.ShaderMaterial({ uniforms, vertexShader, fragmentShader });
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(mesh);
// マウスのX位置で色相オフセットを少し動かす
let targetHue = 0;
canvas.addEventListener("pointermove", (e) => {
const r = canvas.getBoundingClientRect();
targetHue = ((e.clientX - r.left) / (r.width || 1) - 0.5) * 0.4;
});
function resize() {
const w = canvas.clientWidth || 1;
const h = canvas.clientHeight || 1;
renderer.setSize(w, h, false);
uniforms.resolution.value.set(w, h);
}
resize();
window.addEventListener("resize", resize);
let raf = 0;
let running = true;
const start = performance.now();
function animate(now) {
const t = (now - start) * 0.001;
if (!reduceMotion) uniforms.time.value = t;
// 色相をマウス方向へイージング
uniforms.hue.value += (targetHue - uniforms.hue.value) * 0.05;
renderer.render(scene, camera);
raf = requestAnimationFrame(animate);
}
animate(start);
// タブ非表示で停止、復帰で再開
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
if (running) { cancelAnimationFrame(raf); running = false; }
} else if (!running) {
running = true;
raf = requestAnimationFrame(animate);
}
});
window.addEventListener("beforeunload", () => cancelAnimationFrame(raf));
})();
# 外部ライブラリ
https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。