TB-0007
Living Window
室内の窓をフレームに、4つの時間・天候シーンと飛行機、鳥、Rare Eventの動物を組み合わせたインタラクティブな世界表現です。真正面から接近して窓を突き抜ける飛行機や、突然現れるゴリラなど、予想外のスケール変化も組み込んでいます。
MOTION PREVIEW
静かな窓の向こうで、予想外の出来事が始まる。
昼・夕焼け・夜・雨の4シーンを眺めていると、鳥や飛行機が通過し、ときには窓いっぱいのゴリラや巨大な来訪者が突然現れます。真正面から接近する飛行機は、遠景から徐々に巨大化し、最後には窓を突き抜けるように画面を覆います。
LIVE DEMO
景色を切り替え、窓の向こうで起こる出来事を眺める。
DAY / SUNSET / NIGHT / RAINのボタンで窓の外のシーンを切り替えます。窓上でポインターを動かすと背景がわずかにパララックスします。通常イベントとRare Eventは一定間隔でランダムに発生し、鳥、飛行機、流れ星、UFO、動物などが現れます。runtime確認用として1〜4キーでシーン切替、Aキーで通常イベント、RキーでRare Eventを発生できます。
HOW TO INTERACT
Interactive Control
DAY / SUNSET / NIGHT / RAINのボタンで窓の外のシーンを切り替えます。窓上でポインターを動かすと背景がわずかにパララックスします。通常イベントとRare Eventは一定間隔でランダムに発生し、鳥、飛行機、流れ星、UFO、動物などが現れます。runtime確認用として1〜4キーでシーン切替、Aキーで通常イベント、RキーでRare Eventを発生できます。
ABOUT THIS BLOCK
Living Windowとは
Living Windowは、室内の窓をひとつのフレームとして、DAY・SUNSET・NIGHT・RAINの4つの景色と、飛行機・鳥・動物・天候などの出来事を組み合わせるインタラクティブなシーン表現です。
シーン切替では単純に背景色を変えるのではなく、昼の湖と山、夕焼けの水辺、月と都市の夜景、雨に煙る街という異なる世界へ切り替わります。窓の外だけでなく室内の色温度や明るさも連動し、同じ窓から時間や天候の違う世界を眺めている感覚を作ります。
通常イベントでは鳥の群れ、ツバメ、カモメ、紙飛行機、UFO、流れ星などが異なる方向と軌道で現れます。近距離の旅客機は窓枠いっぱいのスケールで横切り、真正面の飛行機は遠景から徐々に拡大し、最後には窓を突き抜けるように画面全体を覆います。
Rare Eventではゴリラ、キリン、猫、鹿、熊、フクロウ、カエルなどが突然現れます。常に動物を表示するのではなく、通常の静かな景色の中へ低頻度で予想外の出来事を差し込むことで、長く眺めるほど新しい発見が生まれる構成にしています。
背景とキャラクター素材はTipsBlocks用に独自制作し、HTML・CSS・Vanilla JavaScriptでレイヤー、シーン状態、Pointer Parallax、Web Animations APIによるイベントモーションを制御します。ホテル・旅行・住宅・ブランドサイト・ポートフォリオなど、窓や景色を利用したHeroや世界観演出へ応用できます。
- Technology
- CSS / HTML / JavaScript
- Interaction
- Interactive Control / Pointer Follow / Random Event / Scene Switch
- External Library
- None
- Reduced Motion
- Supported
HOW IT WORKS
背景・通過イベント・Rare Eventを独立レイヤーとして制御する。
4つの背景シーンを状態として管理し、シーン変更時に背景レイヤーと室内の色調を同期します。通常イベントとRare Eventは別タイマーで管理し、各素材を独立したPNGまたはSVGとして配置します。Web Animations APIで位置、拡大率、透明度、回転を時間軸に沿って変化させ、Pointer Parallaxでは窓内の背景レイヤーだけをわずかに移動させています。
-
01
4つの背景シーンを独立レイヤーで重ねる
DAY、SUNSET、NIGHT、RAINを同じ窓領域へ重ね、現在シーンだけを表示します。切替時はopacityとscaleを補間し、背景だけでなく室内側の色調もdata-sceneから連動させます。
-
02
通常イベントとRare Eventを別タイマーで管理する
鳥や飛行機などの通常イベントと、ゴリラやキリンなどのRare Eventを別々のタイマーで抽選します。Rare Eventの間隔を長くすることで、常に何かが動く状態ではなく、静けさの中へ突然出来事が入るリズムを作ります。
-
03
素材を独立画像として配置する
飛行機、鳥、動物、UFO、流れ星、雨、霧などを個別の画像アセットとして配置します。CSS図形で簡略化せず、同じ世界観のイラスト素材を動かすことで、出現時の存在感を保ちます。
-
04
Web Animations APIで通過軌道を変える
同じ左から右だけに固定せず、右から左、斜め、奥行き方向など複数のkeyframeを用意します。イベントごとに経路や時間を選び、繰り返し見ても同じ動きに感じにくい構成にします。
-
05
動物は移動ではなく突然出現させる
動物は画面を横切らせず、基本的にopacityと小さなscale変化で突然現れ、数秒後に消える演出にします。ゴリラは例外として窓いっぱいまでせり上がり、Rare Eventの強い見せ場にしています。
-
06
真正面の飛行機をスケールで接近させる
左右対称の正面飛行機を窓中央へ置き、遠景の小さな状態から複数の中間スケールを通して徐々に拡大します。接近中は輪郭を鮮明に保ち、最後の突き抜ける瞬間だけごく軽いblurとフラッシュを使います。
-
07
Pointer Parallaxで窓の奥行きを加える
窓内のポインター位置を中心からの距離へ変換し、背景と空の装飾へ異なる量のtranslateを適用します。移動量を小さく抑えることで、カメラ操作ではなく窓越しに少し視点が動く感覚を作ります。
-
08
天候レイヤーを景色より手前に重ねる
RAINでは霧、雨筋、車のライトを別レイヤーとして重ねます。窓ガラス側の要素を景色より手前へ置くことで、雨が背景そのものではなくガラス越しに見えているような奥行きを作ります。
CODE
シーン切替、ランダムイベント、巨大な接近演出のコードを見る。
HTMLは窓、4つの背景、天候レイヤー、通過オブジェクト、Rare Event、室内のカーテンと操作UIで構成します。CSSは窓枠、室内光、シーンのクロスフェード、天候表現を担当し、JavaScriptはシーン状態、イベント抽選、Web Animations APIによる飛行経路や動物の出現、Pointer Parallaxを制御します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>TB-0007 Living Window V7</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="app" id="app" data-scene="day">
<header class="brand"><span>TB-0007</span><strong>Living Window</strong></header>
<section class="room" aria-label="Living Window interactive scene">
<div class="window" id="window">
<div class="glass" id="glass">
<div class="scene scene-day active" data-scene-layer="day"></div>
<div class="scene scene-sunset" data-scene-layer="sunset"></div>
<div class="scene scene-night" data-scene-layer="night"></div>
<div class="scene scene-rain" data-scene-layer="rain"></div>
<div class="parallax sky-deco" id="skyDeco" aria-hidden="true">
<img class="cloud cloud-a" src="assets/sky/cloud-large.png" alt="">
<img class="cloud cloud-b" src="assets/sky/cloud-small.png" alt="">
<img class="stars" src="assets/sky/stars.png" alt="">
</div>
<div class="weather-layer" aria-hidden="true">
<img class="fog" src="assets/weather/fog.png" alt="">
<img class="rain-overlay" src="assets/weather/rain.png" alt="">
<img class="car-lights" src="assets/weather/car-lights.png" alt="">
</div>
<div class="actors" aria-hidden="true">
<img id="airplaneFar" class="actor actor-plane-far" src="assets/transit/airplane-far.png" alt="">
<img id="airplaneNear" class="actor actor-plane-near" src="assets/transit/airplane-near.png" alt="">
<img id="airplaneFront" class="actor actor-plane-front" src="assets/transit/airplane-headon.svg" alt="">
<img id="birdFlock" class="actor actor-flock" src="assets/transit/bird-flock.png" alt="">
<img id="swallow" class="actor actor-swallow" src="assets/transit/swallow.png" alt="">
<img id="seagull" class="actor actor-seagull" src="assets/transit/seagull.png" alt="">
<img id="paperPlane" class="actor actor-paper" src="assets/transit/paper-plane.png" alt="">
<img id="meteor" class="actor actor-meteor" src="assets/sky/meteor.png" alt="">
<img id="ufo" class="actor actor-ufo" src="assets/transit/ufo.png" alt="">
<img id="cat" class="rare rare-cat" src="assets/animals/cat.png" alt="">
<img id="gorilla" class="rare rare-gorilla" src="assets/animals/gorilla.png" alt="">
<img id="frog" class="rare rare-frog" src="assets/animals/frog.png" alt="">
<img id="owl" class="rare rare-owl" src="assets/animals/owl.png" alt="">
<img id="giraffe" class="rare rare-giraffe" src="assets/animals/giraffe.png" alt="">
<img id="deer" class="rare rare-deer" src="assets/animals/deer.png" alt="">
<img id="bear" class="rare rare-bear" src="assets/animals/bear.png" alt="">
</div>
<div class="rush-flash" id="rushFlash"></div>
<div class="glass-shine"></div>
</div>
<div class="frame-v"></div>
<div class="frame-h"></div>
<div class="sill"></div>
</div>
<div class="curtain curtain-left"></div>
<div class="curtain curtain-right"></div>
<div class="floor-shadow"></div>
</section>
<nav class="controls" aria-label="Scene selector">
<button class="scene-btn active" data-set-scene="day">DAY</button>
<button class="scene-btn" data-set-scene="sunset">SUNSET</button>
<button class="scene-btn" data-set-scene="night">NIGHT</button>
<button class="scene-btn" data-set-scene="rain">RAIN</button>
</nav>
<p class="hint">Move the pointer · watch for rare visitors</p>
</main>
<script src="script.js"></script>
</body>
</html> :root{
--room:#e8e2da;--room2:#d7cfc5;--ink:#23272c;--frame:#f6f3ee;--frame-edge:#d7d1c8;
}
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden}body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;background:#ddd5cb;color:var(--ink)}
.app{position:relative;width:100vw;height:100dvh;min-height:560px;overflow:hidden;background:linear-gradient(145deg,var(--room),var(--room2));transition:background 1.4s ease,color 1.2s ease}
.app::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 38%,rgba(255,255,255,.32),transparent 48%),linear-gradient(90deg,rgba(0,0,0,.025),transparent 24% 76%,rgba(0,0,0,.025));pointer-events:none}
.brand{position:absolute;z-index:80;left:4.5vw;top:4vh;display:flex;align-items:baseline;gap:12px;letter-spacing:.13em;font-size:11px;color:rgba(25,29,34,.52);transition:color 1s ease}.brand strong{font-size:14px;letter-spacing:.045em;color:rgba(25,29,34,.92)}
.room{position:absolute;inset:0}
.window{position:absolute;z-index:10;left:50%;top:46.5%;transform:translate(-50%,-50%);width:min(78vw,1180px);aspect-ratio:16/9;background:var(--frame);border:12px solid var(--frame);border-radius:5px;box-shadow:0 28px 55px rgba(33,30,27,.22),0 3px 0 var(--frame-edge),inset 0 0 0 2px rgba(255,255,255,.86);transition:box-shadow 1.3s ease,border-color 1.2s ease}
.glass{position:absolute;inset:0;overflow:hidden;background:#84bce3;isolation:isolate;perspective:1100px}
.scene{position:absolute;inset:-2%;background-position:center;background-size:cover;opacity:0;transform:scale(1.035);transition:opacity 1.25s ease,transform 1.8s cubic-bezier(.2,.75,.25,1);will-change:opacity,transform}.scene.active{opacity:1;transform:scale(1.0)}
.scene-day{background-image:url('assets/backgrounds/day.jpg')}.scene-sunset{background-image:url('assets/backgrounds/sunset.jpg')}.scene-night{background-image:url('assets/backgrounds/night.jpg')}.scene-rain{background-image:url('assets/backgrounds/rain.jpg')}
.sky-deco,.weather-layer,.actors{position:absolute;inset:0;pointer-events:none}.sky-deco{z-index:4;transition:transform .35s ease-out}.cloud{position:absolute;opacity:0;filter:drop-shadow(0 4px 9px rgba(41,74,101,.05));transition:opacity 1s ease}.cloud-a{left:-7%;top:5%;width:31%;animation:cloudDriftA 34s linear infinite}.cloud-b{right:-5%;top:18%;width:20%;animation:cloudDriftB 27s linear infinite}.stars{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;mix-blend-mode:screen;transition:opacity 1.2s ease;animation:twinkle 6s ease-in-out infinite alternate}
@keyframes cloudDriftA{50%{transform:translateX(75px)}}@keyframes cloudDriftB{50%{transform:translateX(-65px)}}@keyframes twinkle{from{opacity:.28}to{opacity:.58}}
.app[data-scene="day"] .cloud{opacity:.34}.app[data-scene="sunset"] .cloud{opacity:.15;filter:sepia(.25) saturate(1.25) hue-rotate(322deg)}.app[data-scene="night"] .stars{opacity:.42}
.fog,.rain-overlay,.car-lights{position:absolute;inset:-3%;width:106%;height:106%;object-fit:cover;opacity:0;transition:opacity 1.1s ease}.fog{z-index:6;mix-blend-mode:screen}.rain-overlay{z-index:9;object-fit:cover}.car-lights{z-index:5;mix-blend-mode:screen}.app[data-scene="rain"] .fog{opacity:.34;animation:fogFloat 12s ease-in-out infinite alternate}.app[data-scene="rain"] .rain-overlay{opacity:.65;animation:rainSlip 1.2s linear infinite}.app[data-scene="rain"] .car-lights{opacity:.32}
@keyframes fogFloat{to{transform:translateX(3%) scale(1.04)}}@keyframes rainSlip{from{transform:translateY(-1.5%)}to{transform:translateY(1.5%)}}
.glass-shine{position:absolute;z-index:40;inset:0;background:linear-gradient(112deg,rgba(255,255,255,.13),transparent 22% 67%,rgba(255,255,255,.045));mix-blend-mode:screen;pointer-events:none}
.frame-v,.frame-h{position:absolute;z-index:48;background:var(--frame);box-shadow:0 0 0 1px rgba(100,95,88,.08);pointer-events:none}.frame-v{left:50%;top:-12px;bottom:-12px;width:12px;transform:translateX(-50%)}.frame-h{left:-12px;right:-12px;top:58%;height:10px;transform:translateY(-50%)}.sill{position:absolute;z-index:52;left:-25px;right:-25px;bottom:-30px;height:32px;border-radius:2px;background:linear-gradient(#fbf8f3,#d8d1c7);box-shadow:0 11px 20px rgba(34,30,27,.16)}
.curtain{position:absolute;z-index:64;top:-3%;width:17vw;min-width:140px;height:91%;background:linear-gradient(90deg,#c8b8a5,#e4d8ca 38%,#c4b09a 67%,#e5d8c8);filter:drop-shadow(0 16px 22px rgba(45,36,28,.12));transition:filter 1s ease,background 1.2s ease;clip-path:polygon(0 0,100% 0,88% 100%,12% 100%)}.curtain::after{content:"";position:absolute;inset:0;background:repeating-linear-gradient(90deg,rgba(79,62,46,.12) 0 4%,transparent 4% 14%);opacity:.35}.curtain-left{left:-4%;transform:rotate(1deg)}.curtain-right{right:-4%;transform:scaleX(-1) rotate(1deg)}.floor-shadow{position:absolute;left:13%;right:13%;bottom:-4%;height:20%;border-radius:50%;background:radial-gradient(ellipse,rgba(50,42,35,.18),transparent 70%);filter:blur(18px)}
.actors{z-index:18}.actor,.rare{position:absolute;opacity:0;visibility:hidden;pointer-events:none;will-change:transform,opacity,left,top,right,bottom}.actor{z-index:18}.rare{z-index:30}.actor-plane-far{width:18%;filter:drop-shadow(0 4px 4px rgba(0,0,0,.12))}.actor-plane-near{width:42%;filter:drop-shadow(0 14px 12px rgba(0,0,0,.18))}.actor-plane-front{width:18%;z-index:37;filter:drop-shadow(0 16px 18px rgba(0,0,0,.2));transform-origin:50% 50%}.actor-flock{width:16%}.actor-swallow{width:9%}.actor-seagull{width:15%}.actor-paper{width:8%}.actor-meteor{width:20%;filter:drop-shadow(0 0 10px rgba(177,226,255,.65))}.actor-ufo{width:24%;filter:drop-shadow(0 8px 15px rgba(52,212,255,.25))}
.rare-gorilla{z-index:35;filter:drop-shadow(0 20px 20px rgba(0,0,0,.25))}.rare-frog{z-index:42;filter:drop-shadow(0 8px 9px rgba(0,0,0,.18))}.rare-giraffe{z-index:34}.rare-owl{z-index:32}.rare-cat{z-index:33}.rare-deer,.rare-bear{z-index:20;filter:drop-shadow(0 7px 6px rgba(0,0,0,.12))}
.controls{position:absolute;z-index:90;left:50%;bottom:3.4vh;transform:translateX(-50%);display:flex;gap:4px;padding:5px;border-radius:999px;background:rgba(250,248,245,.72);border:1px solid rgba(255,255,255,.7);box-shadow:0 12px 32px rgba(34,29,25,.16);backdrop-filter:blur(18px)}.scene-btn{appearance:none;border:0;background:transparent;padding:10px 16px;border-radius:999px;cursor:pointer;color:#645d56;font-size:10px;letter-spacing:.13em;transition:.3s}.scene-btn:hover{transform:translateY(-1px)}.scene-btn.active{background:#292622;color:#fff}.hint{position:absolute;z-index:82;left:50%;bottom:9vh;transform:translateX(-50%);margin:0;font-size:10px;letter-spacing:.1em;color:rgba(45,41,38,.43);white-space:nowrap;transition:color 1s ease}
.app[data-scene="sunset"]{--room:#dfc0aa;--room2:#bfa798}.app[data-scene="night"]{--room:#343a47;--room2:#252a34;--frame:#e9e7e3;--frame-edge:#b9bcc2}.app[data-scene="rain"]{--room:#788186;--room2:#666f74;--frame:#ebedeb;--frame-edge:#bcc4c7}.app[data-scene="night"] .brand,.app[data-scene="rain"] .brand,.app[data-scene="night"] .hint,.app[data-scene="rain"] .hint{color:rgba(246,248,250,.55)}.app[data-scene="night"] .brand strong,.app[data-scene="rain"] .brand strong{color:#fff}.app[data-scene="night"] .curtain,.app[data-scene="rain"] .curtain{filter:brightness(.62) saturate(.7) drop-shadow(0 16px 22px rgba(0,0,0,.2))}.app[data-scene="night"] .controls,.app[data-scene="rain"] .controls{background:rgba(20,24,30,.62);border-color:rgba(255,255,255,.11)}.app[data-scene="night"] .scene-btn,.app[data-scene="rain"] .scene-btn{color:rgba(255,255,255,.66)}.app[data-scene="night"] .scene-btn.active,.app[data-scene="rain"] .scene-btn.active{background:#f0f2f3;color:#20242a}
@media(max-width:820px){.window{width:92vw;aspect-ratio:1.12/1;top:45%;border-width:9px}.frame-v{width:9px}.frame-h{height:8px;top:63%}.curtain{width:23vw}.controls{bottom:2.2vh;width:calc(100% - 20px);justify-content:center}.scene-btn{flex:1;padding:10px 6px;font-size:9px}.hint{display:none}.brand{left:4vw;top:2.7vh}.actor-plane-near{width:66%}.actor-plane-front{width:36%}.actor-plane-far{width:29%}.actor-flock{width:25%}.actor-ufo{width:35%}}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}
.rush-flash{position:absolute;z-index:38;inset:0;background:rgba(8,14,22,0);pointer-events:none}.rush-flash.flash{animation:rushFlash .72s ease-out forwards}@keyframes rushFlash{0%{background:rgba(8,14,22,0)}38%{background:rgba(8,14,22,.62)}58%{background:rgba(248,250,252,.5)}100%{background:rgba(8,14,22,0)}}
(()=>{
const app=document.getElementById('app');
const win=document.getElementById('window');
const skyDeco=document.getElementById('skyDeco');
const rushFlash=document.getElementById('rushFlash');
const layers=[...document.querySelectorAll('[data-scene-layer]')];
const buttons=[...document.querySelectorAll('[data-set-scene]')];
const el=id=>document.getElementById(id);
const actors={
airplaneFar:el('airplaneFar'),airplaneNear:el('airplaneNear'),airplaneFront:el('airplaneFront'),birdFlock:el('birdFlock'),swallow:el('swallow'),seagull:el('seagull'),paperPlane:el('paperPlane'),meteor:el('meteor'),ufo:el('ufo'),
cat:el('cat'),gorilla:el('gorilla'),frog:el('frog'),owl:el('owl'),giraffe:el('giraffe'),deer:el('deer'),bear:el('bear')
};
let scene='day', ambientTimer=null, rareTimer=null, generation=0;
const rand=(a,b)=>Math.random()*(b-a)+a;
const pick=a=>a[Math.floor(Math.random()*a.length)];
function resetNode(node){
if(node._anim){ try{ node._anim.cancel(); }catch(e){} }
node._anim=null;
node.style.visibility='hidden';
node.style.opacity='0';
['left','top','right','bottom','width','transform','filter'].forEach(k=>node.style[k]='');
}
function hideAll(){ Object.values(actors).forEach(resetNode); }
function play(node,keyframes,options={}){
resetNode(node);
node.style.visibility='visible';
node.style.opacity='0';
const anim=node.animate(keyframes,{duration:options.duration||2200,easing:options.easing||'ease',fill:'forwards'});
node._anim=anim;
const clean=()=>{ if(node._anim===anim) resetNode(node); };
anim.onfinish=clean; anim.oncancel=clean;
}
function setScene(next){
scene=next; generation++; app.dataset.scene=next;
layers.forEach(x=>x.classList.toggle('active',x.dataset.sceneLayer===next));
buttons.forEach(x=>x.classList.toggle('active',x.dataset.setScene===next));
hideAll();
scheduleAmbient(2200); scheduleRare(6800);
}
buttons.forEach(b=>b.addEventListener('click',()=>setScene(b.dataset.setScene)));
function giantPlane(){
const node=actors.airplaneNear;
node.style.width = window.innerWidth<820 ? '88%' : '76%';
const routes=[
[
{left:'-86%',top:`${rand(2,12)}%`,opacity:0,transform:`scale(${rand(.96,1.06)}) rotate(${rand(-11,-5)}deg)`},
{left:'-60%',top:`${rand(2,12)}%`,opacity:1,transform:`scale(${rand(.98,1.08)}) rotate(${rand(-8,-3)}deg)`,offset:.12},
{left:'16%',top:`${rand(6,18)}%`,opacity:1,transform:`scale(${rand(1.02,1.12)}) rotate(${rand(-2,3)}deg)`,offset:.48},
{left:'108%',top:`${rand(10,22)}%`,opacity:1,transform:`scale(${rand(1.02,1.14)}) rotate(${rand(4,10)}deg)`,offset:.88},
{left:'124%',top:`${rand(12,24)}%`,opacity:0,transform:`scale(${rand(.92,1.04)}) rotate(${rand(4,10)}deg)`}
],
[
{left:'108%',top:`${rand(2,14)}%`,opacity:0,transform:`scale(${rand(.96,1.06)}) rotate(${rand(5,11)}deg) scaleX(-1)`},
{left:'82%',top:`${rand(2,14)}%`,opacity:1,transform:`scale(${rand(.98,1.08)}) rotate(${rand(3,7)}deg) scaleX(-1)`,offset:.12},
{left:'8%',top:`${rand(6,18)}%`,opacity:1,transform:`scale(${rand(1.02,1.12)}) rotate(${rand(-1,3)}deg) scaleX(-1)`,offset:.48},
{left:'-84%',top:`${rand(8,24)}%`,opacity:1,transform:`scale(${rand(1.02,1.14)}) rotate(${rand(-8,-4)}deg) scaleX(-1)`,offset:.88},
{left:'-112%',top:`${rand(10,26)}%`,opacity:0,transform:`scale(${rand(.92,1.04)}) rotate(${rand(-9,-4)}deg) scaleX(-1)`}
]
];
play(node,pick(routes),{duration:rand(3200,4200),easing:'cubic-bezier(.14,.72,.28,1)'});
}
function frontPlaneRush(){
const node=actors.airplaneFront;
node.style.width = window.innerWidth<820 ? '42%' : '28%';
node.style.left='50%';
node.style.top='50%';
node.style.transformOrigin='50% 50%';
if(rushFlash){ rushFlash.classList.remove('flash'); }
const driftX=rand(-1.4,1.4), driftY=rand(-1.1,1.1);
const duration=5600;
const kf=[
{left:'50%',top:'50%',opacity:0,transform:'translate(-50%,-50%) scale(.06) rotate(0deg)',filter:'blur(0px)'},
{left:`${50+driftX*.18}%`,top:`${50+driftY*.16}%`,opacity:.95,transform:'translate(-50%,-50%) scale(.11) rotate(0deg)',offset:.10,filter:'blur(0px)'},
{left:`${50+driftX*.34}%`,top:`${50+driftY*.28}%`,opacity:1,transform:'translate(-50%,-50%) scale(.18) rotate(.05deg)',offset:.24,filter:'blur(0px)'},
{left:`${50+driftX*.52}%`,top:`${50+driftY*.42}%`,opacity:1,transform:'translate(-50%,-50%) scale(.30) rotate(.08deg)',offset:.40,filter:'blur(0px)'},
{left:`${50+driftX*.74}%`,top:`${50+driftY*.58}%`,opacity:1,transform:'translate(-50%,-50%) scale(.52) rotate(.1deg)',offset:.58,filter:'blur(0px)'},
{left:`${50+driftX}%`,top:`${50+driftY}%`,opacity:1,transform:'translate(-50%,-50%) scale(.90) rotate(.12deg)',offset:.74,filter:'blur(0px)'},
{left:'50%',top:'50%',opacity:1,transform:'translate(-50%,-50%) scale(1.55) rotate(.14deg)',offset:.84,filter:'blur(0px)'},
{left:'50%',top:'50%',opacity:1,transform:'translate(-50%,-50%) scale(2.45) rotate(.18deg)',offset:.91,filter:'blur(0px)'},
{left:'50%',top:'50%',opacity:1,transform:'translate(-50%,-50%) scale(4.6) rotate(.22deg)',offset:.975,filter:'blur(.35px)'},
{left:'50%',top:'50%',opacity:0,transform:'translate(-50%,-50%) scale(6.2) rotate(.25deg)',filter:'blur(1.1px)'}
];
resetNode(node);
node.style.visibility='visible';
node.style.opacity='0';
node.style.width = window.innerWidth<820 ? '42%' : '28%';
const anim=node.animate(kf,{duration,easing:'cubic-bezier(.18,.5,.16,1)',fill:'forwards'});
node._anim=anim;
const flashTimer=setTimeout(()=>{ if(rushFlash){ rushFlash.classList.remove('flash'); void rushFlash.offsetWidth; rushFlash.classList.add('flash'); } }, duration-260);
const clean=()=>{ clearTimeout(flashTimer); if(rushFlash) rushFlash.classList.remove('flash'); if(node._anim===anim) resetNode(node); };
anim.onfinish=clean; anim.oncancel=clean;
}
function farPlane(){
const node=actors.airplaneFar;
node.style.width = window.innerWidth<820 ? '28%' : '17%';
const lr=Math.random()>.5;
const mirror=lr?'':' scaleX(-1)';
const kf=[
{left: lr?'-20%':'105%', top:`${rand(8,22)}%`, opacity:0, transform:`scale(${rand(.72,.84)}) rotate(${lr?rand(-4,-1):rand(1,4)}deg)${mirror}`},
{opacity:.9, offset:.12},
{left: lr?'105%':'-20%', top:`${rand(4,18)}%`, opacity:.88, transform:`scale(${rand(.82,.94)}) rotate(${lr?rand(-1,2):rand(-2,1)}deg)${mirror}`, offset:.88},
{opacity:0}
];
play(node,kf,{duration:rand(9000,12500),easing:'linear'});
}
function birdFlock(){
const node=actors.birdFlock;
node.style.width = window.innerWidth<820 ? '24%' : '16%';
const patterns=[
[{left:'-18%',top:'18%',opacity:0,transform:'scale(.8) rotate(-6deg)'},{opacity:1,offset:.12},{left:'104%',top:'12%',opacity:1,transform:'scale(.92) rotate(4deg)',offset:.88},{opacity:0}],
[{left:'102%',top:'22%',opacity:0,transform:'scale(.85) rotate(5deg) scaleX(-1)'},{opacity:1,offset:.12},{left:'-18%',top:'11%',opacity:1,transform:'scale(.95) rotate(-3deg) scaleX(-1)',offset:.88},{opacity:0}],
[{left:'-18%',top:'36%',opacity:0,transform:'scale(.78) rotate(-11deg)'},{opacity:1,offset:.12},{left:'102%',top:'8%',opacity:1,transform:'scale(.98) rotate(8deg)',offset:.88},{opacity:0}]
];
play(node,pick(patterns),{duration:rand(6200,8600),easing:'linear'});
}
function swallow(){
const node=actors.swallow;
node.style.width = window.innerWidth<820 ? '14%' : '9%';
const lr=Math.random()>.5, mirror=lr?'':' scaleX(-1)';
const kf=[
{left:lr?'-14%':'103%', top:`${rand(12,26)}%`, opacity:0, transform:`scale(.74) rotate(${lr?rand(-15,-5):rand(5,15)}deg)${mirror}`},
{opacity:1, offset:.12},
{left:lr?'30%':'58%', top:`${rand(4,15)}%`, opacity:1, transform:`scale(.96) rotate(${lr?rand(2,9):rand(-9,-2)}deg)${mirror}`, offset:.5},
{left:lr?'106%':'-16%', top:`${rand(14,24)}%`, opacity:1, transform:`scale(.8) rotate(${lr?rand(10,18):rand(-18,-10)}deg)${mirror}`, offset:.86},
{opacity:0}
];
play(node,kf,{duration:rand(3000,4200),easing:'ease-in-out'});
}
function seagull(){
const node=actors.seagull;
node.style.width = window.innerWidth<820 ? '22%' : '14%';
const fromLeft=Math.random()>.5;
const mirror=fromLeft?'':' scaleX(-1)';
const kf=[
{left:fromLeft?'-20%':'104%', top:`${rand(8,30)}%`, opacity:0, transform:`scale(.86) rotate(${fromLeft?rand(-8,-2):rand(2,8)}deg)${mirror}`},
{opacity:1, offset:.12},
{left:fromLeft?'40%':'46%', top:`${rand(2,12)}%`, opacity:1, transform:`scale(1.02) rotate(${fromLeft?rand(-2,2):rand(-2,2)}deg)${mirror}`, offset:.52},
{left:fromLeft?'108%':'-24%', top:`${rand(14,24)}%`, opacity:1, transform:`scale(.9) rotate(${fromLeft?rand(4,10):rand(-10,-4)}deg)${mirror}`, offset:.88},
{opacity:0}
];
play(node,kf,{duration:rand(3800,5200),easing:'ease-in-out'});
}
function paperPlane(){
const node=actors.paperPlane;
node.style.width = window.innerWidth<820 ? '14%' : '8%';
const kf=[
{left:'-10%',top:'44%',opacity:0,transform:'scale(.8) rotate(-24deg)'},
{opacity:1,offset:.12},
{left:'34%',top:'23%',opacity:1,transform:'scale(1) rotate(-8deg)',offset:.46},
{left:'70%',top:'31%',opacity:1,transform:'scale(.92) rotate(14deg)',offset:.74},
{left:'104%',top:'18%',opacity:0,transform:'scale(.82) rotate(-6deg)'}
];
play(node,kf,{duration:rand(3200,4300),easing:'cubic-bezier(.2,.72,.31,1)'});
}
function meteor(){
const node=actors.meteor;
node.style.width = window.innerWidth<820 ? '25%' : '18%';
play(node,[
{left:`${rand(-10,8)}%`,top:'-6%',opacity:0,transform:'rotate(-19deg) scale(.76)'},
{opacity:1,offset:.14},
{left:`${rand(66,84)}%`,top:`${rand(24,40)}%`,opacity:0,transform:'rotate(-19deg) scale(1)'}
],{duration:rand(1200,1700),easing:'ease-out'});
}
function ufo(){
const node=actors.ufo;
node.style.width = window.innerWidth<820 ? '34%' : '22%';
const fromLeft=Math.random()>.5;
const kf=[
{left:fromLeft?'-26%':'102%',top:'10%',opacity:0,transform:`scale(.74)${fromLeft?'':' scaleX(-1)'}`},
{opacity:1,offset:.12},
{left:'40%',top:'6%',opacity:1,transform:`translateY(-10px) scale(1)${fromLeft?'':' scaleX(-1)'}`,offset:.48},
{left:'58%',top:'14%',opacity:1,transform:`translateY(0) scale(.95)${fromLeft?'':' scaleX(-1)'}`,offset:.74},
{left:fromLeft?'108%':'-28%',top:'9%',opacity:0,transform:`scale(.8)${fromLeft?'':' scaleX(-1)'}`}
];
play(node,kf,{duration:rand(4800,6200),easing:'ease-in-out'});
}
function popGorilla(){
const node=actors.gorilla;
node.style.width = window.innerWidth<820 ? '132%' : '92%';
play(node,[
{left:'50%',bottom:'-88%',opacity:0,transform:'translateX(-50%) scale(.88)'},
{left:'50%',bottom:'-38%',opacity:1,transform:'translateX(-50%) scale(1.03)',offset:.16},
{left:'50%',bottom:'-30%',opacity:1,transform:'translateX(-50%) scale(1.08)',offset:.72},
{left:'50%',bottom:'-35%',opacity:1,transform:'translateX(-50%) scale(1.02)',offset:.86},
{left:'50%',bottom:'-88%',opacity:0,transform:'translateX(-50%) scale(.9)'}
],{duration:6200,easing:'cubic-bezier(.18,.72,.18,1)'});
}
function popVisitor(node,setup={}){
Object.entries(setup.style||{}).forEach(([k,v])=>node.style[k]=v);
play(node, setup.keyframes || [
{opacity:0, transform:'scale(.92)'},
{opacity:1, transform:'scale(1.02)', offset:.12},
{opacity:1, transform:'scale(1)', offset:.82},
{opacity:0, transform:'scale(.98)'}
], {duration:setup.duration||3400, easing:setup.easing||'ease-out'});
}
function popCat(){
const node=actors.cat;
popVisitor(node,{style:{width:window.innerWidth<820?'34%':'22%',left:'6%',bottom:'-1%'},duration:3200,keyframes:[
{opacity:0,transform:'translateY(0) scale(.96)'},
{opacity:1,transform:'translateY(-4px) scale(1.02)',offset:.12},
{opacity:1,transform:'translateY(-2px) scale(1)',offset:.78},
{opacity:0,transform:'translateY(0) scale(.98)'}
]});
}
function popFrog(){
const node=actors.frog;
popVisitor(node,{style:{width:window.innerWidth<820?'68%':'46%',right:'2%',top:'10%'},duration:3000,keyframes:[
{opacity:0,transform:'scale(.82) rotate(3deg)'},
{opacity:1,transform:'scale(1.02) rotate(-2deg)',offset:.12},
{opacity:1,transform:'scale(1) rotate(-1deg)',offset:.78},
{opacity:0,transform:'scale(.96) rotate(2deg)'}
]});
}
function popOwl(){
const node=actors.owl;
popVisitor(node,{style:{width:window.innerWidth<820?'42%':'27%',left:'36%',bottom:'-2%'},duration:3500,keyframes:[
{opacity:0,transform:'scale(.86)'},
{opacity:1,transform:'scale(1.03)',offset:.15},
{opacity:1,transform:'scale(1)',offset:.8},
{opacity:0,transform:'scale(.94)'}
]});
}
function popGiraffe(){
const node=actors.giraffe;
popVisitor(node,{style:{width:window.innerWidth<820?'52%':'34%',right:'-10%',bottom:'10%'},duration:3200,keyframes:[
{opacity:0,transform:'translateX(0) scale(.96) rotate(-2deg)'},
{opacity:1,transform:'translateX(-2%) scale(1.02) rotate(1deg)',offset:.14},
{opacity:1,transform:'translateX(-1.5%) scale(1)',offset:.78},
{opacity:0,transform:'translateX(0) scale(.98) rotate(-2deg)'}
]});
}
function popDeer(){
const node=actors.deer;
const side=Math.random()>.5?'left':'right';
const style={width:window.innerWidth<820?'38%':'24%',bottom:'-1%'}; style[side]=side==='left'?'4%':'2%';
popVisitor(node,{style,duration:3000,keyframes:[
{opacity:0,transform:`scale(.94) ${side==='right'?'scaleX(-1)':''}`},
{opacity:1,transform:`scale(1.02) ${side==='right'?'scaleX(-1)':''}`,offset:.14},
{opacity:1,transform:`scale(1) ${side==='right'?'scaleX(-1)':''}`,offset:.8},
{opacity:0,transform:`scale(.98) ${side==='right'?'scaleX(-1)':''}`}
]});
}
function popBear(){
const node=actors.bear;
const side=Math.random()>.5?'left':'right';
const style={width:window.innerWidth<820?'46%':'30%',bottom:'-2%'}; style[side]=side==='left'?'1%':'0%';
popVisitor(node,{style,duration:3200,keyframes:[
{opacity:0,transform:`scale(.92) ${side==='right'?'scaleX(-1)':''}`},
{opacity:1,transform:`scale(1.02) ${side==='right'?'scaleX(-1)':''}`,offset:.14},
{opacity:1,transform:`scale(1) ${side==='right'?'scaleX(-1)':''}`,offset:.8},
{opacity:0,transform:`scale(.98) ${side==='right'?'scaleX(-1)':''}`}
]});
}
function ambient(){
if(scene==='day'){
const v=Math.random();
if(v<.24) frontPlaneRush();
else if(v<.46) giantPlane();
else if(v<.63) birdFlock();
else if(v<.77) swallow();
else if(v<.89) seagull();
else paperPlane();
} else if(scene==='sunset'){
const v=Math.random();
if(v<.28) frontPlaneRush();
else if(v<.5) giantPlane();
else if(v<.72) birdFlock();
else if(v<.86) seagull();
else farPlane();
} else if(scene==='night'){
const v=Math.random();
if(v<.26) meteor();
else if(v<.5) ufo();
else if(v<.72) giantPlane();
else if(v<.84) frontPlaneRush();
else farPlane();
} else if(scene==='rain') {
const v=Math.random();
if(v<.42) giantPlane();
else if(v<.62) frontPlaneRush();
else if(v<.82) seagull();
else paperPlane();
}
}
function rare(){
let options;
if(scene==='day') options=['gorilla','giraffe','cat','deer','bear'];
else if(scene==='sunset') options=['gorilla','giraffe','deer','cat'];
else if(scene==='night') options=['gorilla','owl','ufo'];
else options=['frog','gorilla','cat'];
const k=pick(options);
if(k==='gorilla') popGorilla();
else if(k==='giraffe') popGiraffe();
else if(k==='cat') popCat();
else if(k==='deer') popDeer();
else if(k==='bear') popBear();
else if(k==='frog') popFrog();
else if(k==='owl') popOwl();
else if(k==='ufo') ufo();
}
function scheduleAmbient(delay=rand(4400,7200)){
clearTimeout(ambientTimer); const token=generation;
ambientTimer=setTimeout(()=>{ if(token!==generation) return; ambient(); scheduleAmbient(); }, delay);
}
function scheduleRare(delay=rand(10500,16500)){
clearTimeout(rareTimer); const token=generation;
rareTimer=setTimeout(()=>{ if(token!==generation) return; rare(); scheduleRare(); }, delay);
}
win.addEventListener('pointermove',e=>{
const r=win.getBoundingClientRect();
const x=((e.clientX-r.left)/r.width-.5)*2;
const y=((e.clientY-r.top)/r.height-.5)*2;
skyDeco.style.transform=`translate3d(${-x*12}px,${-y*7}px,0)`;
layers.forEach(l=>l.style.transform=l.classList.contains('active')?`translate3d(${-x*5}px,${-y*3}px,0) scale(1.015)`:l.style.transform);
});
win.addEventListener('pointerleave',()=>{ skyDeco.style.transform=''; layers.forEach(l=>l.style.transform=''); });
document.addEventListener('keydown',e=>{
if(['1','2','3','4'].includes(e.key)) setScene(['day','sunset','night','rain'][+e.key-1]);
if(e.key.toLowerCase()==='r') rare();
if(e.key.toLowerCase()==='a') ambient();
});
scheduleAmbient(2400); scheduleRare(7200);
})();
USE CASE
窓の向こうに小さな世界を作るWeb演出。
ホテルや旅行サイトのHero、住宅・不動産サイトの窓景色、ブランドやキャンペーンの世界観演出、ポートフォリオ、404ページ、季節コンテンツなど、静かな画面に時間経過と発見を持たせたい場面へ応用できます。
Hotel / Resort Hero
ホテルやリゾートの窓から見える景色を昼・夕方・夜・雨へ切り替え、滞在中の時間の変化や非日常感を体験させるHeroとして利用できます。
Housing / Real Estate
住宅や不動産サイトで、室内から見える眺望や時間帯の違いを印象的に見せる演出へ応用できます。実際の景観写真へ置き換えても構造を活用できます。
Brand / Creative Campaign
ブランドサイトやキャンペーンで、通常は静かなキービジュアルに予想外の来訪者や巨大な通過物を差し込み、記憶に残るインタラクションとして利用できます。
Portfolio / Experimental Page
制作会社やクリエイターのポートフォリオ、404ページ、実験的な特設ページで、複数のアニメーション表現を一つの窓世界としてまとめる構成に向いています。
TECHNICAL NOTES
画像レイヤーとイベント頻度を、実装先に合わせて調整する。
複数の高解像度背景・透過PNGを使うため、実サイトではWebP/AVIF化、表示サイズに合わせたリサイズ、遅延読込を推奨します。Rare Eventは頻度を上げすぎると驚きが弱くなるため、通常イベントとの間隔を分けて調整してください。
Browser
Web Animations API、Pointer Events、CSS transform、opacity、filter、mix-blend-mode、CSS Custom Propertiesを使用します。現行の主要ブラウザを対象とし、公開前にPC・タブレット・スマートフォンでシーン切替とイベント表示を確認してください。
Performance
CanvasやWebGLは使用せず、DOM/CSSと画像レイヤーで構成しています。負荷の中心は背景画像、透過PNG、transform・filterです。実サイトでは画像圧縮、同時表示レイヤー数、drop-shadowやblurの使用量を調整してください。
Mobile
モバイルでは窓を画面の主役として大きく表示し、パララックス量を抑えます。巨大飛行機や動物はPCより相対サイズを調整し、画面外へのはみ出しを利用しながらインパクトを維持します。
Reduced Motion
prefers-reduced-motionではCSSアニメーションとtransitionを極短時間へ抑える設定を含みます。実サイトへ導入する場合は、ランダムイベントや大きなズーム演出も停止または弱化する分岐を追加すると、よりアクセシブルにできます。