ふわふわ浮遊キャラ群
パステル色のキャラクターたちが各々の周期でふわふわ漂い、順番にシュッと弾みます。ゆるふわ系のギャラリーやランディングの賑わいに。サイン波だけで作る軽量な群体アニメーションです。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura: メンバーのゆるキャラがふわふわ漂う公式サイトの賑わい背景 -->
<div class="stage" aria-label="ふわふわ浮遊する桜キャラ6体">
<div class="floaty" style="--i:0; --size:64px; --c:#FFD9E0; left:12%; top:28%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
<circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
<path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
<path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
</svg>
</div>
<div class="floaty" style="--i:1; --size:70px; --c:#FFC2D6; left:34%; top:60%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
<circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
<path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
<path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
</svg>
</div>
<div class="floaty" style="--i:2; --size:76px; --c:#FBE3EC; left:52%; top:20%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
<circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
<path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
<path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
</svg>
</div>
<div class="floaty" style="--i:3; --size:82px; --c:#FFB8CE; left:70%; top:54%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
<circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
<path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
<path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
</svg>
</div>
<div class="floaty" style="--i:4; --size:88px; --c:#FFD9E0; left:84%; top:30%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
<circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
<path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
<path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
</svg>
</div>
<div class="floaty" style="--i:5; --size:94px; --c:#FFC2D6; left:24%; top:74%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
<circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
<path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
<path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
</svg>
</div>
<p class="wordmark" aria-hidden="true">桜 <span>OFFICIAL</span></p>
</div>
CSS
:root{
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #1a1020; }
.stage {
position: relative;
width: 100%;
height: 100vh;
min-height: 220px;
max-height: 100%;
overflow: hidden;
background: radial-gradient(120% 90% at 50% 0%, #2a1730 0%, #1a1020 70%);
}
.floaty {
position: absolute;
width: var(--size);
height: var(--size);
margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
will-change: transform;
animation: floaty calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate;
animation-delay: calc(var(--i) * -0.7s);
}
.blob {
display: block; width: 100%; height: 100%;
transform-origin: center bottom;
transition: transform .4s var(--ease-spring);
}
.floaty.active .blob { transform: scale(1.25); }
.shadow {
position: absolute; left: 50%; bottom: -8%;
width: 62%; height: 12%; margin-left: -31%;
background: rgba(0,0,0,.4);
filter: blur(6px);
border-radius: 50%;
transform-origin: center;
animation: footy calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate-reverse;
animation-delay: calc(var(--i) * -0.7s);
}
.wordmark {
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
margin: 0; display: flex; align-items: baseline; gap: 10px;
font-family: "Hiragino Mincho ProN", serif; font-weight: 700;
font-size: clamp(40px, 12vw, 96px);
color: rgba(255, 209, 224, .14);
letter-spacing: .04em; pointer-events: none;
}
.wordmark span { font-family: system-ui, sans-serif; font-size: .22em; letter-spacing: .4em; }
@keyframes floaty {
0% { transform: translate(-8px, -14px) rotate(-4deg); }
100% { transform: translate(8px, 14px) rotate(4deg); }
}
@keyframes footy { 0% { transform: scaleX(.85); } 100% { transform: scaleX(1); } }
JavaScript
// 浮遊は CSS。順番にシュッと弾む疑似hoverだけ制御する(デモと同じロジック)。
(() => {
const items = [...document.querySelectorAll('.floaty')];
if (!items.length) return;
let i = 0;
const tick = () => {
items.forEach(el => el.classList.remove('active'));
items[i % items.length].classList.add('active');
i++;
};
tick();
setInterval(tick, 2200);
})();
実装ガイド
使いどころ
ゆるい世界観のトップやキャラクター系サイトの賑わい背景に。複数キャラが各々の周期で漂い順に弾むことで「生きている」印象を与えます。
実装時の注意点
浮遊はCSSのinfiniteアニメ(translate/rotate)で、個体ごとに周期と負のanimation-delayを散らして位相をずらします。順番に弾む疑似hoverだけJSのsetIntervalでクラスを付与。影を浮遊と逆位相で伸縮させると接地感が出ます。
対応ブラウザ
CSS transform/animation・カスタムプロパティは全モダンブラウザで安定し、入力前提なく自動で動きます。prefers-reduced-motion時は弾みを止める配慮を入れ、対応状況は実機で確認してください。
よくある失敗
全個体に同じdelayを与えると一斉に動いて不自然です。負のdelayで位相をばらしましょう。弾みのscaleを大きくしすぎると酔いやすく、要素数を増やしすぎると低スペック端末で重くなります。
応用例
blob形状をマスコットに差し替える、色をブランドカラーに、クリックで反応させる、視差で奥行きを付けるなどに展開できます。
コード
HTML
<!-- ふわふわ浮遊キャラ群:サイン波で漂い、順番にシュッと弾む -->
<div class="stage" aria-label="ふわふわ浮遊するキャラクター6体">
<div class="floaty" style="--i:0; --size:64px; --c:#FFD9E0; left:12%; top:28%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:1; --size:70px; --c:#D9F2FF; left:34%; top:60%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:2; --size:76px; --c:#FFF3C9; left:52%; top:20%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:3; --size:82px; --c:#E2FFE9; left:70%; top:54%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:4; --size:88px; --c:#FFD9E0; left:84%; top:30%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:5; --size:94px; --c:#D9F2FF; left:24%; top:74%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
</div>
CSS
:root{
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0F1117; }
.stage {
position: relative;
width: 100%;
height: 100vh;
min-height: 220px;
max-height: 100%;
overflow: hidden;
background: #0F1117;
}
/* 各個体。--i で周期・遅延を散らして位相をずらす */
.floaty {
position: absolute;
width: var(--size);
height: var(--size);
margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2); /* left/top を中心基準に */
will-change: transform;
animation: floaty calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate;
animation-delay: calc(var(--i) * -0.7s);
}
.blob {
display: block; width: 100%; height: 100%;
transform-origin: center bottom;
transition: transform .4s var(--ease-spring);
}
/* 疑似hover:順番に .active が回り、シュッと拡大 */
.floaty.active .blob { transform: scale(1.25); }
/* 影:浮遊と逆位相で伸縮 */
.shadow {
position: absolute; left: 50%; bottom: -8%;
width: 62%; height: 12%; margin-left: -31%;
background: rgba(0,0,0,.35);
filter: blur(6px);
border-radius: 50%;
transform-origin: center;
animation: footy calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate-reverse;
animation-delay: calc(var(--i) * -0.7s);
}
@keyframes floaty {
0% { transform: translate(-8px, -14px) rotate(-4deg); }
100% { transform: translate(8px, 14px) rotate(4deg); }
}
@keyframes footy {
0% { transform: scaleX(.85); }
100% { transform: scaleX(1); }
}
JavaScript
// 浮遊は CSS の infinite アニメ。ここでは「順番にシュッと弾む」疑似hoverだけ制御する。
(() => {
const items = [...document.querySelectorAll('.floaty')];
if (!items.length) return; // null安全
let i = 0;
const tick = () => {
items.forEach(el => el.classList.remove('active'));
items[i % items.length].classList.add('active');
i++;
};
tick();
// 座標は CSS 任せ。クラス付与のみなので setInterval でよい
setInterval(tick, 2200);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ふわふわ浮遊キャラ群」の効果を追加してください。
# 追加してほしい効果
ふわふわ浮遊キャラ群(アニメーション & トランジション)
パステル色のキャラクターたちが各々の周期でふわふわ漂い、順番にシュッと弾みます。ゆるふわ系のギャラリーやランディングの賑わいに。サイン波だけで作る軽量な群体アニメーションです。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ふわふわ浮遊キャラ群:サイン波で漂い、順番にシュッと弾む -->
<div class="stage" aria-label="ふわふわ浮遊するキャラクター6体">
<div class="floaty" style="--i:0; --size:64px; --c:#FFD9E0; left:12%; top:28%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:1; --size:70px; --c:#D9F2FF; left:34%; top:60%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:2; --size:76px; --c:#FFF3C9; left:52%; top:20%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:3; --size:82px; --c:#E2FFE9; left:70%; top:54%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:4; --size:88px; --c:#FFD9E0; left:84%; top:30%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="floaty" style="--i:5; --size:94px; --c:#D9F2FF; left:24%; top:74%;" aria-hidden="true">
<span class="shadow"></span>
<svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
<circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
<circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
<path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
</svg>
</div>
</div>
【CSS】
:root{
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0F1117; }
.stage {
position: relative;
width: 100%;
height: 100vh;
min-height: 220px;
max-height: 100%;
overflow: hidden;
background: #0F1117;
}
/* 各個体。--i で周期・遅延を散らして位相をずらす */
.floaty {
position: absolute;
width: var(--size);
height: var(--size);
margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2); /* left/top を中心基準に */
will-change: transform;
animation: floaty calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate;
animation-delay: calc(var(--i) * -0.7s);
}
.blob {
display: block; width: 100%; height: 100%;
transform-origin: center bottom;
transition: transform .4s var(--ease-spring);
}
/* 疑似hover:順番に .active が回り、シュッと拡大 */
.floaty.active .blob { transform: scale(1.25); }
/* 影:浮遊と逆位相で伸縮 */
.shadow {
position: absolute; left: 50%; bottom: -8%;
width: 62%; height: 12%; margin-left: -31%;
background: rgba(0,0,0,.35);
filter: blur(6px);
border-radius: 50%;
transform-origin: center;
animation: footy calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate-reverse;
animation-delay: calc(var(--i) * -0.7s);
}
@keyframes floaty {
0% { transform: translate(-8px, -14px) rotate(-4deg); }
100% { transform: translate(8px, 14px) rotate(4deg); }
}
@keyframes footy {
0% { transform: scaleX(.85); }
100% { transform: scaleX(1); }
}
【JavaScript】
// 浮遊は CSS の infinite アニメ。ここでは「順番にシュッと弾む」疑似hoverだけ制御する。
(() => {
const items = [...document.querySelectorAll('.floaty')];
if (!items.length) return; // null安全
let i = 0;
const tick = () => {
items.forEach(el => el.classList.remove('active'));
items[i % items.length].classList.add('active');
i++;
};
tick();
// 座標は CSS 任せ。クラス付与のみなので setInterval でよい
setInterval(tick, 2200);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。