ウィンドウのサイズを変更する

setSize()(権限 core:window:allow-set-size)と Rust の set_size() でサイズを変える。innerSize() が物理ピクセルで返る落とし穴と、表示前に決めてチラつきを防ぐ手順も示す。

ウィンドウ 対象: Tauri 2.x 更新日: 読了目安: 約10分 win-001
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 実行時にサイズを変える
  4. 論理ピクセルと物理ピクセルを取り違えない
  5. 2. バックエンドから実装する (Rust)
  6. 動作確認
  7. よくあるエラーと対処法
  8. OS ごとの違いと注意点
  9. 関連レシピ

「コンパクト表示に切り替える」「前回の大きさで開く」「画面の 8 割で起動する」など、ウィンドウの大きさをアプリ側で決める場面はよくあります。起動時の大きさは tauri.conf.json の width / height、実行中の変更は JS の setSize() か Rust の set_size() で行います。つまずきやすいのは単位で、設定ファイルと LogicalSize は論理ピクセル、innerSize() などで読む値は物理ピクセルです。単位の取り違えと、起動直後にサイズが一瞬変わって見える問題の避け方も説明します。

前提条件

プラグインは不要です。サイズを変える core:window:allow-set-size は core:window:default に含まれないので追加します。読み取り側の innerSize() / outerSize() / scaleFactor() / isMaximized() は core:window:default に、onResized() のイベント購読は core:event:default に含まれるので、core:default があれば追加は不要です。後述の「復元してから表示する」例で JS から show() を呼ぶなら core:window:allow-show も足します。

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "core:window:allow-set-size",
    "core:window:allow-show"
  ]
}

設定ファイルの値はすべて論理ピクセルで、省略時は 800 x 600 です。preventOverflow: true は、生成時にウィンドウが作業領域(タスクバーなどを除いた範囲)からはみ出さないよう内側のサイズを縮めます。大きめの初期サイズを小さなノート PC で開いたときの「下端が画面外」を防げますが、判定は生成時の 1 回だけで、x / y を指定していなければプライマリモニターが基準です。

{
  "app": {
    "windows": [
      {
        "label": "main",
        "title": "My App",
        "width": 1280,
        "height": 800,
        "minWidth": 480,
        "minHeight": 360,
        "preventOverflow": true
      }
    ]
  }
}

設定ファイルと実行時 API は次のように使い分けます。

やりたいこと使うもの
毎回同じ大きさで起動するtauri.conf.json の width / height
操作に応じて切り替えるJS の setSize() / Rust の set_size()
画面の大きさから起動時に決めるRust の setup で set_size() → show()
前回終了時の大きさで開くWindow State プラグイン
ユーザーが変えられる範囲を制限するminWidth など(win-015)

1. フロントエンドから実装する (TypeScript)

実行時にサイズを変える

setSize() には LogicalSize か PhysicalSize を渡します。普段は LogicalSize を使えば、拡大率 100% と 150% のモニターで見た目の大きさが揃います。指定するのは内側のサイズ(タイトルバーと枠を除いた、ページの描画領域)です。

import { getCurrentWindow, LogicalSize } from '@tauri-apps/api/window';

const win = getCurrentWindow();

// コンパクト表示と通常表示を切り替える
export async function setCompact(compact: boolean) {
  const size = compact ? new LogicalSize(360, 640) : new LogicalSize(1024, 768);
  await win.setSize(size); // core:window:allow-set-size が必要
}

最大化中に setSize() を呼ぶと、最大化は自動で解除されます。ユーザーの最大化を尊重したいなら、isMaximized() を見て呼ばない判断をアプリ側で入れます。「タイトルバー込みで作業領域の 8 割」のようにウィンドウ全体で考えたいときは、outerSize() と innerSize() の差(枠の分)を引いてから渡します。

論理ピクセルと物理ピクセルを取り違えない

拡大率 150% の Windows では、LogicalSize(1024, 768) にしたウィンドウの innerSize() は 1536 x 1152 を返します。単位は API ごとに決まっています。

値単位
設定ファイルの width / minWidth など論理
setSize() の引数渡したクラス次第
innerSize() / outerSize() / onResized() の値物理
モニターの size / workArea物理
preventOverflow のマージン指定物理

典型的な失敗が「innerSize() を保存して次回 LogicalSize で復元したら、起動するたびに 1.5 倍になる」です。保存時に scaleFactor() で割って論理ピクセルに直せば、倍率の違うモニターで開いても同じ見た目で復元できます。また JS はページの読み込み後に動くため、そのままだと設定ファイルの大きさで一度表示されてから切り替わって見えます。ウィンドウに "visible": false を付けておき、復元してから show() します。

import { getCurrentWindow, LogicalSize } from '@tauri-apps/api/window';

const KEY = 'window-size';
const win = getCurrentWindow();

// リサイズされるたびに「論理ピクセル」で保存する
export async function rememberSize() {
  let timer: number | undefined;
  return win.onResized(({ payload }) => {
    window.clearTimeout(timer); // ドラッグ中は連続で届くので 300ms まとめる
    timer = window.setTimeout(async () => {
      // 最小化・最大化中の大きさは保存しない
      if (payload.width === 0 || (await win.isMinimized()) || (await win.isMaximized())) return;
      const logical = payload.toLogical(await win.scaleFactor());
      localStorage.setItem(KEY, JSON.stringify({
        width: Math.round(logical.width),
        height: Math.round(logical.height),
      }));
    }, 300);
  });
}

// 起動時に復元してから表示する(tauri.conf.json で "visible": false)
export async function restoreSize() {
  try {
    const saved = localStorage.getItem(KEY);
    if (saved) {
      const { width, height } = JSON.parse(saved) as { width: number; height: number };
      await win.setSize(new LogicalSize(width, height));
    }
  } finally {
    await win.show(); // 例外が起きても必ず表示する。core:window:allow-show が必要
  }
}

2. バックエンドから実装する (Rust)

set_size() の引数は Into<Size> なので LogicalSize と PhysicalSize のどちらも渡せます。JS からどちらの単位でも受けたいコマンドは、引数を tauri::Size にします。

setup フックは、設定ファイルのウィンドウが作られた直後に呼ばれます。"visible": false のウィンドウをここで作業領域に合わせ、中央に寄せて から show() すれば、JS の権限を増やさずにチラつきのない起動になります。current_monitor() はモニターを特定できないと None を返すので、プライマリモニターへフォールバックします。

use tauri::{LogicalSize, Manager, PhysicalSize};

#[tauri::command]
fn resize_window(app: tauri::AppHandle, label: String, size: tauri::Size) -> Result<(), String> {
    let window = app
        .get_webview_window(&label)
        .ok_or_else(|| format!("window '{}' not found", label))?;
    window.set_size(size).map_err(|e| e.to_string())
}

// 作業領域の 8 割(内側のサイズ)にして中央に置き、表示する
fn fit_and_show(window: &tauri::WebviewWindow) -> tauri::Result<()> {
    let monitor = match window.current_monitor()? {
        Some(m) => Some(m),
        None => window.primary_monitor()?,
    };
    match monitor {
        Some(m) => {
            let area = m.work_area().size; // 物理ピクセル
            window.set_size(PhysicalSize::new(area.width * 8 / 10, area.height * 8 / 10))?;
            window.center()?;
        }
        None => window.set_size(LogicalSize::new(1024.0, 768.0))?,
    }
    window.show()
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            // tauri.conf.json で main に "visible": false を付けておく
            if let Some(main) = app.get_webview_window("main") {
                fit_and_show(&main)?;
            }
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![resize_window])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

JS からは Size クラスで包んで渡します。LogicalSize のままだと { width, height } だけが送られ単位が分からないため、tauri::Size として受け取れません。Size は { Logical: { width, height } } の形にしてくれます。

import { invoke } from '@tauri-apps/api/core';
import { LogicalSize, Size } from '@tauri-apps/api/dpi';

await invoke('resize_window', { label: 'main', size: new Size(new LogicalSize(1024, 768)) });

動作確認

npm run tauri dev で起動し、次の関数をボタンなどから呼んで値を確かめます。

import { getCurrentWindow } from '@tauri-apps/api/window';

export async function logSize() {
  const win = getCurrentWindow();
  const factor = await win.scaleFactor();
  const inner = await win.innerSize(); // 物理ピクセル
  const logical = inner.toLogical(factor);
  console.log(`inner ${inner.width}x${inner.height} / scale ${factor} / logical ${Math.round(logical.width)}x${Math.round(logical.height)}`);
}

拡大率 150% の Windows で setCompact(false) の後に呼ぶと次のように出ます。物理ピクセルを倍率で割ると setSize() に渡した値に戻ります。

inner 1536x1152 / scale 1.5 / logical 1024x768

rememberSize() と restoreSize() を組み込んで枠を広げてから再起動すると、同じ見た目の大きさで開きます。

よくあるエラーと対処法

  • 「window.set_size not allowed. Permissions associated with this command: core:window:allow-set-size」: 権限の追加漏れです。この詳しい文面はデバッグビルド(tauri dev)のもので、リリースビルドでは「Command plugin:window|set_size not allowed by ACL」だけになります。追加して tauri dev を再起動します。
  • 起動するたびにウィンドウが大きくなる: 物理ピクセルの値を論理ピクセルとして復元しています。toLogical(scaleFactor) で直してから保存します。
  • 指定した大きさにならない: minWidth などの制約が優先されます。少なくとも Windows では、制約の範囲外の値を渡すと範囲内に収められます。
  • PhysicalSize で引数の変換に失敗した趣旨のエラーになる: Rust 側は u32 で受けるので、小数や負の値は通りません。Math.floor() などで 1 以上の整数にします。

OS ごとの違いと注意点

  • Windows: LogicalSize は、呼び出した時点でウィンドウがいるモニターの倍率で物理ピクセルに換算されます。
  • macOS: サイズ変更は非同期に適用されます。setSize() の直後の innerSize() が新しい値を返す保証はないので、確定値は onResized() で受け取ります。
  • Linux: サイズは論理ピクセルの整数に丸めて要求されます。こちらも直後の読み取りには頼らない方が安全です。
  • iOS / Android: setSize() は非対応で、設定ファイルの width / height も無視されます。
  • 共通: 倍率の違うモニターへ移すと OS が提案する大きさにリサイズされ、onScaleChanged() に新しい倍率と物理サイズが届きます。枠を触っていなくても物理サイズが変わるのは正常です。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する