[Tauri #02] Astroで作ったアナログ時計を完全透過デスクトップガジェット化
はじめに
前回の【#01 環境構築編】に引き続き、今回はTauriの実装編です。
[Tauri #01] Tauriによるデスクトップアプリ開発:環境構築から透過型デジタル時計のビルドまで // PROTOCOL.LAIN
JavaScriptとTauriを組み合わせ、OS標準の枠を排した透過型デジタル時計を作成する手順をまとめました。Rust環境のセットアップから実用的なガジェットのビルドまで網羅しています。
humanxai.info目標は、以前Astroで作成した「サイバーパンク風アナログ時計」を、背景が完全に透けた、デスクトップを自由に動かせるガジェットアプリとして移植すること。
結論から言うと、開発開始からわずか1時間ちょっと(お昼ご飯を食べる前)で完成してしまいました。Tauri、恐るべし。とはいえ、いくつかの「デスクトップアプリ特有の壁」にぶつかったので、その解決策を備忘録として残しておきます。
Astroで製作したアナログ時計に関する記事:
[Astro #14] Copland OS風UIの極北:カスタムアナログ時計とコンテキストメニューの実装 // PROTOCOL.LAIN
Astroプロジェクトに、Copland OSライクなSVGカスタムアナログ時計と右クリック(長押し)メニューを追加。filterプロパティの罠や、localStorageを使った状態保存、シーン切り替え時の表示制御について解説します。
humanxai.info
[Astro #15] Copland OS: SVG clock 無段階サイズ変更と、SPA特有のバグを攻略 // PROTOCOL.LAIN
AstroとNext.jsの混在環境で発生するSPA特有のイベントリスナー重複や、requestAnimationFrameの停止処理など、実戦的なデバッグ手法をまとめました。
humanxai.infoスクリーンショット(完成品):
1. コードの移植と「ドラッグ移動」の壁
まずはAstroで書いていたHTML/CSS/JSを、TauriのVanilla JSテンプレートにそのまま移植しました。この時点では「白い背景のウィンドウの中に時計がある」という、ただのWebブラウザと同じ状態です。
これをガジェットらしく「時計を掴んで自由に移動できる」ようにするため、TauriのAPI startDragging() を呼び出したのですが……いきなりエラーが発生。
Dragging failed window.start_dragging not allowed.
Permissions associated with this command: core:window:allow-start-dragging
解決策:Tauri v2の厳格な権限管理
Tauri(特にv2)はセキュリティが非常に厳しく、デフォルトではフロントエンド(JS)からOSのウィンドウを操作することが許されていません。
これを許可するために、src-tauri/capabilities/default.json の permissions に権限を追記する必要があります。
{
"permissions": [
"core:default",
"core:window:allow-start-dragging", // ドラッグ移動を許可
"core:window:allow-close" // アプリの終了を許可
]
}
これで、JSから appWindow.startDragging() を呼ぶだけで、OSレベルのヌルヌルなドラッグ移動ができるようになりました。
2. 完全透過への道:OSの「影」を消し去る
ドラッグができるようになったら、次は「時計以外の背景を完全に消し去る」作業です。
まず、CSSで body の背景を透明にし、tauri.conf.json で transparent: true を設定しました。しかし、結果は「うっすらとした四角い影」が残るという謎の現象に。
実はこれ、Windows OSが「ウィンドウにはドロップシャドウをつけるものだ」と気を利かせて、透明なウィンドウに影をつけてしまっていたのが原因でした。
解決策:shadow設定をオフにする
src-tauri/tauri.conf.json の windows セクションに "shadow": false を追加します。
"windows": [
{
"title": "lain-clock",
"width": 300,
"height": 300,
"transparent": true,
"decorations": false,
"shadow": false, // ← これを追加!四角い影を消し去る
"alwaysOnTop": true
}
]
これで、不自然な枠や影が一切ない、デスクトップに時計だけが浮いている状態が完成しました!
3. スライダーでウィンドウサイズを動的に変える
時計には「サイズ変更用のスライダー」を実装していましたが、Webの感覚でCSSの width と height を変えるだけではダメでした。時計の絵は大きくなっても、Tauriの透明なウィンドウサイズ(枠)自体はそのままなので、はみ出した部分が切れてしまいます。
解決策:Tauri APIでLogicalSizeを変更する
JSからTauriのAPIを叩き、時計のサイズ変更に合わせてOS側のウィンドウサイズもリアルタイムに変更するように書き換えました。
// ウィンドウサイズ変更の権限 "core:window:allow-set-size" を追加した上で実行
async function applySize(size) {
const numSize = parseInt(size);
// 1. CSS側の時計サイズを更新
clock.style.width = `${numSize}px`;
clock.style.height = `${numSize}px`;
// 2. Tauri側のウィンドウサイズを同期する
try {
const { LogicalSize } = window.__TAURI__.window;
const win = window.__TAURI__.window;
const current = win.getCurrentWindow ? win.getCurrentWindow() : win.appWindow;
// メニュー表示用に少しだけ余白(+20px)を持たせてリサイズ
await current.setSize(new LogicalSize(numSize + 20, numSize + 20));
} catch (err) {
console.error("Failed to resize window:", err);
}
}
ハマりポイント:スライダーの最大値
最初はウィンドウサイズが巨大化するのを防ぐため、スライダーの最大値を window.innerWidth ベースで計算していました。
しかし、ウィンドウ自体を小さくすると innerWidth も小さくなり、「一度小さくすると二度と大きくできない呪いの縮小ループ」に陥りました。
ここはWebの文脈ではなく、OSの文脈でディスプレイの解像度(screen.width)を基準にするのが正解です。
function updateSliderRange() {
// ウィンドウ内ではなく、モニター全体のサイズを基準にする
const maxSafeSize = Math.floor(Math.min(screen.width, screen.height) * 0.8);
sizeSlider.max = maxSafeSize.toString();
}
まとめ:Webフロントエンドの知識がそのままデスクトップへ
開発開始から1時間少々。
- TypeScript/JSでのロジック移植
- ウィンドウの完全透過化
- OSレベルでのドラッグ移動
- リアルタイムなウィンドウリサイズ
これらをWebフロントエンドの知識+ちょっとしたTauriの設定だけで実現できてしまいました。デスクトップに馴染む「俺専用ガジェット」を作る体験は、想像以上にテンションが上がります。
次は「クリック透過(Click Through)」の設定や、PC起動時の自動起動(スタートアップ)など、さらにアプリとしての完成度を高めていこうと思います。
COMM_LOG: tauri-02-desktop-clock-widget