この記事は はてなエンジニア Advent Calendar 2025 の
id:rmatsuoka さんが書いた「ぼーっと眺めるためのRSSリーダーを自作したらインターネットが楽しい」でした。
本日は はてなで Android エンジニアをやってる
id:mangano-ito がお送りします。
さて、もう 2026 年です。
2025 年末、自宅の掃除などをしていました。デジタル大掃除というかんじで、NAS のデータの整理をしていましたところ、思いがけず懐かしのデータに出会ったのでこの機会に紹介したいと思います。
ソリティア
みなさんはソリティアって知ってますか。
多分知らない方のほうが少ないんじゃないかと思います。 というのも、Windows に入ってるソリティアがとても有名だからです (小学生みたいな文章)。

ところで、上の Wikipedia にあるように、ソリティアは「一人だけで遊ぶことのできるゲームの総称」なので、 Windows の「ソリティア」は本当は「クロンダイク」というゲームであることも有名です。
いずれにしても、Windows にプリインストールされていた、カードゲームを覚えている人は多いでしょう。
演出
で、Windows のソリティアってなんかクリア (?) したら、異常にテンションの高い演出がはいって、すごく印象深いんですよね。

これは実際に Windows 98 を仮想マシンで動かしたものをキャプチャしています。
仮想環境だからかアニメーションが異常に高速になってたので速度を半分にしたんですが、もっとゆっくりとポーンポーンって小気味良いよく落ちてくる感じの思い出だったので、Windows 11 に入ってたフリーセルをやってみたら同じ演出で、こっちのイメージのほうが近いですね:
「フリーセル」でググったらアプリ画像で同じ演出が使われているくらいなので、そのセンセーショナルさといったら推して知るべしかと思われます:

ふたたび昔の話
話を年末に戻しますと、昔このアニメーションをブラウザで再現する JavaScript のコードを作成しまして、それが NAS に残っていました。どういうものかというと、
(GIF 版)

ブラウザの任意のページでそのユーザースクリプトを実行すると、ページが「ソリティア」できるというものです。

Wikipedia の「トランプ」のページ を使うと爽快感があります。
カーソルで選択した DOM 要素をソリティアのカードのごとく地面に落とすことができるので、Web ページを破壊できます。
利用価値はゼロですが、自由な感じが若さを感じてこんなもの作ってたな…と思いにさせた一品です。コピーとかされてて実際作った日時は定かではないのですが……
あと地面との衝突の計算が甘くて、たまに地面を突き抜けていくのがめちゃくちゃダサいなと思いました。
ミニ体験コーナー
↓ ミニソリティア体験コーナーを用意したので遊んでいってください ↓
ちょっとだけコードを紹介
流石に Advent Calendar として、これだけで終わるのはマズい気がしたので、コード見て思い出しながら実装の一部を省略しつつ紹介します。コーディングスタイル含め恥ずかしいですが当時のままを持ってきています。
まず、このアニメーションの軌跡は canvas とかではなく、本当に選択した DOM 要素をクローンしてきて、Shadow DOM なぞ考えもせずそれをドキュメント上のオーバーレイした要素に appendChild しています:
function clone( element ) { var dup = element.cloneNode( false ); dup.id = ""; dup.className = ""; var children = element.childNodes; for ( var i = 0; i < children.length; ++i ) { var child = clone( children[ i ] ); dup.appendChild( child ); } // deep-copy computed styles // because the original style depends on the context where it is spawned var style = null; try { style = element.currentStyle || document.defaultView.getComputedStyle( element, "" ); } catch ( exception ) {} if ( style !== null ) { for ( var i = 0; i < style.length; ++i ) { var prop = style[ i ]; dup.style[ prop ] = style[ prop ]; } dup.style[ "position" ] = "absolute"; dup.style[ "z-index" ] = "250"; } return dup; }
ポイントとしては、getComputedStyle で計算済スタイルをそのまま適用したり、子要素をディープコピーしている点です。だから、要素のスタイルはそのまま生きていて、アニメーションはアニメーションするんですよね:

こんな雑な実装でよく動くなって思うんですが、意外とこれくらい愚直でもなめらかにレンダーされるので、マシンスペックもそうですがブラウザの最適化ってすごいなって感じです。
ほか、放物線を描くように位置を計算したり、地面 (ことビューポートの下端) にぶつかったらバウンドするような物理計算まわりです:
function cloneBound( object, dt ) { var element = object.clone; if ( BOUNCE_CLONED ) { var dup = shallowClone( element, object.z++ ); element = dup; } var x = object.x; var y = object.y; var vx = object.vX; var vy = object.vY; var width = object.width; var height = object.height; var dimension = getViewportDimension(); var offset = getViewportOffset(); var bottom = y + height; var groundY = offset.y + dimension.height; var diffGroundY = ( groundY - bottom ); var dvy = GRAVITY_ACCELERATION * dt; var dy = ( vy + dvy ) * dt; while ( dy > diffGroundY && dt > 0 ) { // this means that the element is going to collide the ground at this update // so make a bounce and recalculate the position bounded var t = ( groundY - bottom ) / ( vy ); dt -= t; vy += GRAVITY_ACCELERATION * t; vy *= -BOUND_COEFFICIENT; dvy = GRAVITY_ACCELERATION * dt; dy = ( vy + dvy ) * dt; vy += dvy; y = groundY - height + dy; dvy = GRAVITY_ACCELERATION * dt; dy = ( vy + dvy ) * dt; bottom = y + height; diffGroundY = ( groundY - bottom ); } vy += dvy; x += vx; y += dy; object.x = x; object.y = y; object.vY = vy; setPosition( element, x, y ); if ( ( x < offset.x - width ) || ( dimension.width + offset.x < x ) ) { // stop solitarization // when object is out of the viewport range object.active = false; --_activeCount; } if ( BOUNCE_CLONED ) { _layer.appendChild( dup ); } } function callback( object, t ) { var now = Date.now(); var dt = now - t; cloneBound( object, dt ) if ( !_running || !object.active ) { console.log( "solitairization finished." ); if ( _activeCount <= 0 ) { _running = false; } return; } delayed( callback.bind( this, object, now ) ); };
アニメーションフレームごとに重力加速度を加えた位置に要素をクローンしていってる感じですね。地面に当たったらバウンドするように衝突したかもチェックしています。
このあたり、フレームごとに差分を計算しているがゆえに、加速度がものすごい場合は地面との衝突判定ができなくてバウンドせず突き抜けるような要因があると思います。
全体的に極めて荒々しく愚直なコードという感じがしますが、これでまあまあ動いているのが不思議でもあります。var を使ってるあたりも年代を察することもできるのではないでしょうか。
おわり
昔つくったコードをながめていると、なんか野暮ったくてダサいなという部分もあれば、そういえばこういう書き方にハマってたなとかが思い出されて、若干黒歴史を感じますが、アイデアとか勢いみたいなのが感じられ、掘り出すとしみじみと趣深くて、新年に向けた気力が戻ってきた感じもします。
以上です! 2026 年も Happy Coding!
次の日の はてなエンジニア Advent Calendar 2025 の記事もぜひお楽しみに!






