ウィンドウの最小・最大サイズを制限する

tauri.conf.json の minWidth などと setMinSize()・setSizeConstraints() でリサイズできる範囲を決める。必要な権限、幅だけの制限、表示内容に応じた切り替えも示す。

ウィンドウ 対象: Tauri 2.x 更新日: 読了目安: 約9分 win-015
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 表示内容に合わせて最小サイズを変える
  4. 幅だけを制限する
  5. 2. バックエンドから実装する (Rust)
  6. 動作確認
  7. よくあるエラーと対処法
  8. OS ごとの違いと注意点
  9. 関連レシピ

サイドバーと本文が並ぶ画面を狭めすぎて崩れる、横に広げすぎて 1 行が長くなり読みにくい、といった問題は、ユーザーがリサイズできる範囲を決めておけば防げます。固定の範囲は tauri.conf.json の minWidth / minHeight / maxWidth / maxHeight、表示内容に応じた変更は JS の setMinSize() / setMaxSize() / setSizeConstraints() か Rust の同名メソッドで行います。制限の対象はタイトルバーと枠を除いた内側のサイズで、単位は論理ピクセルを使うのが基本です。

前提条件

プラグインは不要で、設定ファイルで決めるだけなら権限も要りません。実行時に変える core:window:allow-set-min-size / allow-set-max-size / allow-set-size-constraints はどれも core:window:default に含まれないので、使うものを追加します。後述の例では、狭いウィンドウを広げるために core:window:allow-set-size も使います。innerSize() と scaleFactor() は core:window:default に含まれます。

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

設定ファイルの 4 つのキーは独立していて、書いたものだけが効きます。次の例では高さの上限を付けていません。初期サイズ(width / height)は範囲の内側にしておきます。

{
  "app": {
    "windows": [
      {
        "label": "main",
        "width": 1024,
        "height": 720,
        "minWidth": 480,
        "minHeight": 360,
        "maxWidth": 1600
      }
    ]
  }
}

値を決めるときの目安が 2 つあります。1 つは、内側の論理サイズがページの CSS ピクセルの幅(window.innerWidth)と一致することです。minWidth: 480 にすれば、CSS で 480px 未満のレイアウトを用意する必要はありません。もう 1 つは、対応したい一番小さな画面に収まることです。1920x1080 の画面を拡大率 150% で使うと論理解像度は 1280x720 で、タスクバーを除いた作業領域はさらに狭くなります。minHeight: 720 ではこの環境でウィンドウが画面からはみ出します。

やりたいこと使うもの
いつも同じ範囲に制限する設定ファイルの minWidth など
表示内容に応じて最小サイズを変えるsetMinSize() / setMaxSize()
幅だけ・高さだけを実行中に制限するsetSizeConstraints()
大きさを完全に固定するresizable: false(ユーザーによるリサイズを禁止する)
大きさそのものを変えるsetSize()(ウィンドウのサイズを変更する)

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

表示内容に合わせて最小サイズを変える

サイドバーを開いたときだけ最小幅を広げる例です。setMinSize() / setMaxSize() は幅と高さを組で受け取り、null を渡すと制限を外します。今のウィンドウが新しい最小幅より狭い場合に備えて、setSize() で広げておくと確実です。

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

const win = getCurrentWindow();
const CONTENT_MIN = 480; // 本文だけのときの最小幅
const SIDEBAR = 240;

export async function setSidebar(open: boolean) {
  document.body.classList.toggle('sidebar-open', open);
  const minWidth = CONTENT_MIN + (open ? SIDEBAR : 0);
  await win.setMinSize(new LogicalSize(minWidth, 360)); // core:window:allow-set-min-size が必要

  const now = (await win.innerSize()).toLogical(await win.scaleFactor()); // 物理 → 論理
  if (now.width < minWidth) {
    await win.setSize(new LogicalSize(minWidth, now.height)); // core:window:allow-set-size が必要
  }
}

PhysicalSize でも指定できますが、拡大率 200% のモニターでは見た目が半分の大きさになります。モニターをまたいでも同じ見た目の範囲にするなら LogicalSize を使います。

幅だけを制限する

幅だけ、高さだけを制限したいときは setSizeConstraints() を使います。数値は論理ピクセルとして扱われます。注意点は、呼ぶたびに 4 つの制限をまとめて置き換えることです。書かなかった項目は「制限なし」になるので、設定ファイルの minHeight も消えます。残したい制限は毎回すべて書きます。また 0 は「指定なし」と同じ扱いです。

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

const win = getCurrentWindow();

// 読みやすい行の長さを保つため、幅を 480〜1200 にする。高さの最小 360 も残す
export async function limitWidth() {
  await win.setSizeConstraints({ minWidth: 480, maxWidth: 1200, minHeight: 360 }); // core:window:allow-set-size-constraints が必要
}

// 制限をすべて外す
export async function clearConstraints() {
  await win.setSizeConstraints(null);
}

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

実行中の変更は set_min_size() / set_max_size()(引数は Option)と set_size_constraints() です。Rust で作るウィンドウには、ビルダーの min_inner_size() / max_inner_size()(論理ピクセル)か、項目ごとに決められる inner_size_constraints() を付けます。WindowSizeConstraints の各項目は PixelUnit で、None が制限なしです。ウィンドウを作るコマンドは、Windows で固まらないよう async にします。

use tauri::{LogicalSize, LogicalUnit, PixelUnit, WebviewUrl, WebviewWindowBuilder, WindowSizeConstraints};

fn logical(v: f64) -> Option<PixelUnit> {
    Some(LogicalUnit::new(v).into())
}

/// 呼び出し元ウィンドウの最小サイズを変える。幅か高さが無ければ制限を外す
#[tauri::command]
fn set_min_window_size(window: tauri::WebviewWindow, width: Option<f64>, height: Option<f64>) -> Result<(), String> {
    let result = match (width, height) {
        (Some(w), Some(h)) => window.set_min_size(Some(LogicalSize::new(w, h))),
        // None だけでは型が決まらないので型を付ける
        _ => window.set_min_size(None::<LogicalSize<f64>>),
    };
    result.map_err(|e| e.to_string())
}

/// 幅 400〜720・高さ 300 以上に制限した設定ウィンドウを開く
#[tauri::command]
async fn open_settings(app: tauri::AppHandle) -> Result<(), String> {
    WebviewWindowBuilder::new(&app, "settings", WebviewUrl::App("settings.html".into()))
        .title("設定")
        .inner_size(520.0, 400.0)
        .inner_size_constraints(WindowSizeConstraints {
            min_width: logical(400.0),
            min_height: logical(300.0),
            max_width: logical(720.0),
            max_height: None, // 高さの上限なし
        })
        .build()
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![set_min_window_size, open_settings])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';

await invoke('set_min_window_size', { width: 720, height: 480 });
await invoke('set_min_window_size', {}); // 引数を省くと None になり、制限が外れる
await invoke('open_settings');

動作確認

npm run tauri dev で起動し、次のコードを読み込んでから枠をドラッグして縮めます。

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

const win = getCurrentWindow();
await win.onResized(async ({ payload }) => {
  const logical = payload.toLogical(await win.scaleFactor());
  console.log(`inner ${payload.width}x${payload.height} / logical ${Math.round(logical.width)}x${Math.round(logical.height)} / css ${window.innerWidth}x${window.innerHeight}`);
});

前述の設定ファイルのまま、拡大率 150% の Windows で最小まで縮めると、次のところで止まります。物理ピクセルは 1.5 倍ですが、論理ピクセルと CSS ピクセルは minWidth / minHeight と同じです。

inner 720x540 / logical 480x360 / css 480x360

setSidebar(true) を呼ぶと、狭めていたウィンドウが幅 720 まで広がり、それ以下には縮められなくなります。

よくあるエラーと対処法

  • 「window.set_min_size not allowed. Permissions associated with this command: core:window:allow-set-min-size」: 権限の追加漏れです。この文面はデバッグビルドのもので、リリースビルドでは「Command plugin:window|set_min_size not allowed by ACL」だけになります。setSizeConstraints() なら core:window:allow-set-size-constraints を足します。
  • setSizeConstraints() を呼んだら設定ファイルの最小高さが効かなくなった: 4 つの制限がまとめて置き換わるためです。残したい項目も一緒に渡します。
  • Rust で set_min_size(None) がコンパイルできない: 引数の型が決まらないという趣旨のエラーになります。None::<LogicalSize<f64>> のように型を付けます。
  • 小さい画面でウィンドウがはみ出す: 最小サイズが作業領域より大きいと、ウィンドウは画面に収まりません。最小サイズを小さめにし、狭い幅のレイアウトは CSS で用意します。

OS ごとの違いと注意点

  • 最大化との関係: 最大サイズを付けたウィンドウでも、最大化ボタンを押したときの見え方が意図と違うことがあります。最大化させたくないなら maximizable: false を併用します(resizable: false のときは無視されます)。macOS ではタイトルバーのズームボタンが無効になり、これはフルスクリーンへの入口も兼ねています。Linux・iOS・Android では効きません。
  • ページのズーム: zoomHotkeysEnabled などでページを拡大すると、CSS ピクセルは論理ピクセルより大きくなります。125% に拡大すると minWidth: 480 のウィンドウでも CSS の幅は 384px になるので、ズームを許すなら狭い幅のレイアウトも用意します。
  • 大きさの固定: 最小と最大を同じ値にするより resizable: false を使います。こちらは最大化ボタンも自動で無効になります。

関連レシピ

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

Web Ninja

この記事を書いた人

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

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

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

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