ぼかしBlob
CSS装飾 / 動く素材トップ
半透明のグラデーション楕円がゆっくり回転・浮遊する。
試して調整する#
判断#
向く場面: 柔らかな光彩感を足したいヒーローやセクション背景。
避ける場面: 精密な図や写真の細部を隠したくない画面。
用意したプリセットは淡い青。名前で選んだあと、実際に文字を重ねた状態で速さと密度を詰めます。
組み込み条件#
依存: CSS @keyframes のみ。外部ライブラリ不要。
負荷: Canvas 不使用。動きを減らす設定では静止。
再利用: position:absolute の layer として1画面1か所。
単一HTMLをコピーする#
下のコードだけで実物サンプルと同じ画面が動きます。調整欄を動かすと、値を反映したコードへ更新されます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,follow">
<title>ぼかしBlob 実物サンプル</title>
<style>
:root {
color-scheme: light dark;
--ink-light:#1a2421; --mut-light:#5a6a72; --signal-light:#2a7ab8; --bg-light:#f4f8fb;
--ink-dark:#f4f8fb; --mut-dark:#a8bac7; --signal-dark:#48b8ff; --bg-dark:#07111f;
}
* { box-sizing:border-box; }
html,body { margin:0; min-height:100%; }
body { font-family:"Hiragino Sans","Yu Gothic UI",system-ui,sans-serif; transition:background .3s,color .3s; }
html[data-demo-theme="light"], html[data-demo-theme="light"] body {
background:var(--bg-light); color:var(--ink-light); --ink:var(--ink-light); --mut:var(--mut-light); --signal:var(--signal-light); }
html[data-demo-theme="dark"], html[data-demo-theme="dark"] body {
background:var(--bg-dark); color:var(--ink-dark); --ink:var(--ink-dark); --mut:var(--mut-dark); --signal:var(--signal-dark); }
@media (prefers-color-scheme:light) {
html:not([data-demo-theme]), html:not([data-demo-theme]) body {
background:var(--bg-light); color:var(--ink-light); --ink:var(--ink-light); --mut:var(--mut-light); --signal:var(--signal-light); } }
@media (prefers-color-scheme:dark) {
html:not([data-demo-theme]), html:not([data-demo-theme]) body {
background:var(--bg-dark); color:var(--ink-dark); --ink:var(--ink-dark); --mut:var(--mut-dark); --signal:var(--signal-dark); } }
.sample { position:relative; min-height:100dvh; overflow:hidden; isolation:isolate; display:grid; align-items:end; }
.effect { position:absolute; inset:0; z-index:0; }
.effect canvas { display:block; width:100%; height:100%; touch-action:manipulation; }
.copy { position:relative; z-index:1; width:min(42rem,100%); padding:clamp(2rem,7vw,6rem); }
.label { margin:0 0 1rem; color:var(--signal); font:600 .72rem/1.4 ui-monospace,Consolas,monospace; letter-spacing:.16em; }
h1 { margin:0; max-width:14ch; font-size:clamp(2rem,5.5vw,4.5rem); line-height:1.05; letter-spacing:-.04em; }
.lead { margin:1.5rem 0 0; max-width:36rem; color:var(--mut); font-size:clamp(.95rem,1.4vw,1.1rem); line-height:1.85; }
.readout { position:absolute; inset:1.5rem 1.5rem auto auto; margin:0; color:var(--mut); font:500 .68rem/1.6 ui-monospace,Consolas,monospace; letter-spacing:.1em; writing-mode:vertical-rl; z-index:2; }
.theme-toggle { position:absolute; inset:1.5rem auto auto 1.5rem; z-index:2; display:flex; gap:.25rem; }
.theme-toggle button {
min-block-size:36px; padding-inline:.75rem; font:600 .72rem/1 ui-monospace,Consolas,monospace;
letter-spacing:.08em; border:1px solid var(--mut); background:transparent; color:var(--ink); cursor:pointer; border-radius:4px; }
.theme-toggle button[aria-pressed="true"] { background:var(--signal); color:var(--bg-dark); border-color:var(--signal); }
@media (max-width:640px) { .copy{padding:2rem 1.25rem 3rem}.readout{display:none} }
.bare .copy, .bare .readout, .bare .theme-toggle { display:none; }
</style>
<style>
.bg-shape-layer,.bg-glow-layer,.bg-grid-layer,.bg-streak-layer{
position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
.bg-shape-item{
position:absolute;border-radius:40px;filter:blur(80px);opacity:.8;
animation:rotate-float var(--shape-dur,30s) infinite linear}
@keyframes rotate-float{
0%{transform:translate(0,0) rotate(0deg)}
33%{transform:translate(40px,-60px) rotate(120deg)}
66%{transform:translate(-30px,30px) rotate(240deg)}
100%{transform:translate(0,0) rotate(360deg)}}
.bg-glow-item{
position:absolute;transform:translate(-50%,-50%);border-radius:50%;
animation:glow-pulse var(--glow-dur,6s) ease-in-out infinite}
@keyframes glow-pulse{
0%,100%{opacity:.7;transform:translate(-50%,-50%) scale(1)}
50%{opacity:1;transform:translate(-50%,-50%) scale(1.08)}}
.bg-streak-item{
position:absolute;inset:-20% -10%;
animation:streak-shift var(--streak-dur,20s) ease-in-out infinite alternate}
@keyframes streak-shift{
0%{transform:translateX(-8%)}100%{transform:translateX(8%)}}
.paused .bg-shape-item,.paused .bg-glow-item,.paused .bg-streak-item{
animation-play-state:paused}
@media (prefers-reduced-motion:reduce){
.bg-shape-item,.bg-glow-item,.bg-streak-item{animation:none}}
</style>
</head>
<body>
<script id="effect-config" type="application/json">{"color":"#dbeafe","speed":1.0}</script>
<script>if (/[?&]bare=1/.test(location.search)) document.documentElement.classList.add('bare');
if (/[?&]tap=1/.test(location.search)) { addEventListener('load', function () {
setTimeout(function () { [[0.5,0.5],[0.3,0.6],[0.7,0.4]].forEach(function (p, i) {
setTimeout(function () { document.dispatchEvent(new MouseEvent('click',
{clientX: innerWidth * p[0], clientY: innerHeight * p[1], bubbles: true})); }, i * 220);
}); }, 300); }); }</script>
<script>(function(){var m=location.hash.match(/[#&]cfg=([^&]*)/);if(!m)return;
try{JSON.parse(decodeURIComponent(m[1]));document.getElementById('effect-config').textContent=decodeURIComponent(m[1]);}catch(e){}})();</script>
<script>(function(){
var root=document.documentElement, cfgNode=document.getElementById('effect-config');
var themeKeys=[];
function syncTheme(t){
root.setAttribute('data-demo-theme',t);
document.querySelectorAll('.theme-toggle button').forEach(function(b){
b.setAttribute('aria-pressed',b.dataset.theme===t?'true':'false');});
if(!themeKeys.length)return;
try{var cfg=JSON.parse(cfgNode.textContent||'{}');
themeKeys.forEach(function(k){cfg[k]=t==='dark'?0:1;});
cfgNode.textContent=JSON.stringify(cfg);}catch(e){}
}
var fixed=null;
if(fixed){syncTheme(fixed);}else{var q=location.search.match(/[?&]theme=(light|dark)/); if(q) syncTheme(q[1]);}
addEventListener('DOMContentLoaded',function(){
document.querySelectorAll('.theme-toggle button').forEach(function(b){
b.addEventListener('click',function(){syncTheme(b.dataset.theme);});});});})();</script>
<main class="sample">
<div class="theme-toggle" aria-label="配色切替">
<button type="button" data-theme="light" aria-pressed="false">LIGHT</button>
<button type="button" data-theme="dark" aria-pressed="false">DARK</button>
</div>
<div class="effect" aria-hidden="true"><div class="bg-shape-layer" aria-hidden="true"><div class="bg-shape-item" style="top:-80px;left:-120px;width:700px;height:400px;background:linear-gradient(135deg,rgba(219,234,254,0.6),rgba(191,219,254,0.3))"></div><div class="bg-shape-item" style="bottom:-60px;right:-150px;width:650px;height:380px;background:linear-gradient(135deg,rgba(224,242,254,0.55),rgba(186,230,253,0.25));animation-delay:-8s;animation-direction:reverse"></div><div class="bg-shape-item" style="top:30%;left:40%;width:500px;height:300px;background:linear-gradient(135deg,rgba(207,250,254,0.4),rgba(165,243,252,0.15));animation-delay:-3s"></div></div></div>
<div class="copy">
<p class="label">CSS DECORATION</p>
<h1>ぼかしBlob</h1>
<p class="lead">半透明のグラデーション楕円がゆっくり回転・浮遊する。柔らかな光彩感を足したいヒーローやセクション背景。</p>
</div>
<p class="readout">単一HTML・動きを減らす設定に対応</p>
</main>
<script>
const configNode = document.getElementById('effect-config');
const schema = {"color": {"type": "color", "fallback": "#dbeafe"}, "speed": {"type": "number", "min": 0.3, "max": 2, "integer": false}};
function clean(source) {
const out = {};
Object.keys(schema).forEach(function (k) {
var rule = schema[k], v = source[k];
if (rule.type === 'color') out[k] = /^#[0-9a-f]{6}$/i.test(String(v)) ? String(v) : rule.fallback || '#ffffff';
else if (rule.type === 'select') out[k] = (rule.options.indexOf(v) >= 0) ? v : rule.options[0];
else { var n = Number(v); if (!isFinite(n)) n = rule.min; out[k] = Math.min(rule.max, Math.max(rule.min, n)); }
});
return out;
}
var root = document.documentElement, layer = document.querySelector('.effect');
var reduced = matchMedia('(prefers-reduced-motion: reduce)'), active = true;
function hex2rgba(hex, a) {
var h = hex.replace('#','');
return 'rgba(' + parseInt(h.slice(0,2),16) + ',' + parseInt(h.slice(2,4),16) + ','
+ parseInt(h.slice(4,6),16) + ',' + a + ')';
}
function report() {
root.dataset.materialActive = String(active);
root.dataset.materialRunning = String(active && !document.hidden && !reduced.matches);
}
function apply(cfg) {
cfg = clean(cfg); configNode.textContent = JSON.stringify(cfg);
layer.innerHTML = "<div class=\"bg-shape-layer\" aria-hidden=\"true\"><div class=\"bg-shape-item\" style=\"top:-80px;left:-120px;width:700px;height:400px;background:linear-gradient(135deg,rgba(219,234,254,0.6),rgba(191,219,254,0.3))\"></div><div class=\"bg-shape-item\" style=\"bottom:-60px;right:-150px;width:650px;height:380px;background:linear-gradient(135deg,rgba(224,242,254,0.55),rgba(186,230,253,0.25));animation-delay:-8s;animation-direction:reverse\"></div><div class=\"bg-shape-item\" style=\"top:30%;left:40%;width:500px;height:300px;background:linear-gradient(135deg,rgba(207,250,254,0.4),rgba(165,243,252,0.15));animation-delay:-3s\"></div></div>";
root.style.setProperty('--shape-dur', (30 / cfg.speed).toFixed(1) + 's');
layer.querySelectorAll('.bg-shape-item').forEach(function (el, i) {
if (i === 0) el.style.background = 'linear-gradient(135deg,' + hex2rgba(cfg.color, 0.6) + ',' + hex2rgba(cfg.color, 0.3) + ')';
});
report();
}
function refresh() {
root.classList.toggle('paused', !active || document.hidden || reduced.matches);
report();
}
apply(JSON.parse(configNode.textContent));
addEventListener('message', function (e) {
var d = e.data || {};
if (d.type === 'material-config') apply(d.values || {});
if (d.type === 'material-active') { active = d.active === true; refresh(); }
});
document.addEventListener('visibilitychange', refresh);
reduced.addEventListener('change', refresh);
addEventListener('pagehide', function () { active = false; refresh(); }, {once:true});
</script>
</body>
</html>
AIエージェントへ渡す場合は、「このHTMLを対象ページのヒーロー背景へ組み込み、文字と主要ボタンを前面に残してください」と添えます。
組み込み時の確認#
prefers-reduced-motion: reduceで静止する- 画面外と非表示タブで描画が止まる
- 背景を止めても文字の階層と構図が成立する
- 文字とのコントラストを実際の配色で測る
- 装飾が主要操作の当たり判定を覆わない