Windows 上の .NET MAUI WebView でページを表示すると、右端に縦スクロールバーが出てUIが崩れることがあります。本記事では WebView2 の仕組みを踏まえ、MAUI 側ではなく HTML/CSS を JavaScript で注入してスクロールバーだけを非表示にする実装例と注意点をまとめます。
.NET MAUI の WebView(Windows)で縦スクロールバーが消えない理由
.NET MAUI の WebView は、Windows では内部的に WebView2(Microsoft Edge / Chromium ベース) を使って Web コンテンツを描画します。ここで見えている縦スクロールバーは、MAUI のコントロールが「外側に付けているUI部品」ではなく、多くのケースで 表示している Web ページ(HTML/CSS)が描画しているスクロールバー です。
つまり、「MAUI のプロパティを1つ設定したらスクロールバーだけ消える」といった単純な制御が用意されていないことが多く、実務的には Web 側(CSS)を変更する のが近道になります。
スクロールバーの“表示”と“スクロール可否”は別物
まず押さえておきたいのが、スクロールバーを消したい目的が次のどちらかで実装が変わる点です。
| 目的 | やりたいこと | 代表的なCSS | 注意点 |
|---|---|---|---|
| スクロールはできるがバーは見せたくない | マウスホイール/タッチパッドでスクロールできる状態を維持しつつ、縦スクロールバーを非表示 | ::-webkit-scrollbar を隠す/幅を0にする | ページ内のスクロール要素が body 以外の場合、追加の指定が必要 |
| スクロール自体を無効にしたい | 固定表示にしたい(スクロール操作を受け付けない) | overflow: hidden | コンテンツがはみ出すと読めなくなる。UI設計の見直しが必要 |
「縦スクロールバーだけ隠す」が目的なら、基本は前者です。overflow: hidden は強力ですが、スクロールそのものが止まるため、意図しない表示欠けが起きやすい点に注意してください。
解決方針:JavaScript 注入で CSS を追加し、縦スクロールバーを非表示にする
Windows の MAUI WebView では、ページ読み込み後に WebView2 に対して ExecuteScriptAsync() を呼び出し、<style> タグを動的に追加する のが現実的な落としどころです。Webページを自分で管理しているなら HTML/CSS 側に直接書くのが最もシンプルですが、外部ページ・共通コンポーネント・同一HTMLを複数のアプリで流用している場合は、アプリ側で注入できると運用が楽になります。
まずは“スクロールは残してバーだけ隠す”CSSを用意する
WebView2 は Chromium 系なので、基本は ::-webkit-scrollbar が効きます。縦スクロールバーだけを消したい場合は、縦方向の幅(width)を 0 に寄せるのが安定です。
/* スクロールは可能なまま、スクロールバーだけを隠す(WebView2/Chromium向け) */
html, body {
/* ここで overflow を hidden にしないのがポイント(スクロール自体は残す) */
-ms-overflow-style: none; /* 古いEdge/IE系の名残(保険) */
scrollbar-width: none; /* Firefox向け(保険) */
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
width: 0 !important; /* 縦スクロールバーを潰す */
height: 0 !important; /* 横も消したい場合。横を残すなら height は外す */
display: none; /* 実装依存だが併記しておくと効く環境がある */
}
「縦だけ消して横は残したい」なら、height: 0 を削除し、縦方向のみ 0 にする構成にします。逆に、スクロール自体も止めたいなら次のようにします。
/* スクロール自体を止めたい場合(バーも消えるが、操作もできなくなる) */
html, body {
overflow: hidden;
}
実装例:Windows の WebView2 に対して CSS 注入を行う(.NET MAUI)
ここからは「Windows のときだけ」WebView2 に CSS を注入する流れを、.NET MAUI のコードとして具体化します。ポイントは次の3つです。
- Windows では
WebViewの実体がMicrosoft.UI.Xaml.Controls.WebView2になる CoreWebView2が初期化される前に触ると落ちる/Null になるので、初期化完了を待つ- ページ遷移のたびに反映させたいなら、
AddScriptToExecuteOnDocumentCreatedAsync(もしくは DOMContentLoaded など)で再注入する
Behavior を使って「特定の WebView だけ」スクロールバー非表示を適用する
プロジェクト全体の WebView に影響を出したくない場合は、Behavior にまとめるのが扱いやすいです。XAML で対象だけ付け替えられ、デバッグもしやすくなります。
Behavior(Windowsのみ実装)
using Microsoft.Maui.Controls;
#if WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
#endif
namespace YourApp.Behaviors;
public class HideVerticalScrollBarBehavior : Behavior
{
#if WINDOWS
private WebView2? _webView2;
private bool _hooked;
#endif
protected override void OnAttachedTo(WebView bindable)
{
base.OnAttachedTo(bindable);
bindable.HandlerChanged += OnHandlerChanged;
}
protected override void OnDetachingFrom(WebView bindable)
{
bindable.HandlerChanged -= OnHandlerChanged;
#if WINDOWS
if (_webView2?.CoreWebView2 != null && _hooked)
{
_webView2.CoreWebView2.DOMContentLoaded -= CoreWebView2OnDOMContentLoaded;
_hooked = false;
}
_webView2 = null;
#endif
base.OnDetachingFrom(bindable);
}
private async void OnHandlerChanged(object? sender, EventArgs e)
{
#if WINDOWS
if (sender is not WebView mauiWebView)
return;
// MAUI WebView の PlatformView を WebView2 として取得
if (mauiWebView.Handler?.PlatformView is not WebView2 platformWebView2)
return;
_webView2 = platformWebView2;
// CoreWebView2 を確実に初期化
await _webView2.EnsureCoreWebView2Async();
// 1回のナビゲーションだけでなく「毎回」適用したいなら、DocumentCreated に登録しておくのが堅牢
await _webView2.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(BuildInjectionScript());
// DOMContentLoaded でも念押し(ページによっては head が遅延生成される対策)
if (!_hooked)
{
_webView2.CoreWebView2.DOMContentLoaded += CoreWebView2OnDOMContentLoaded;
_hooked = true;
}
#endif
}
#if WINDOWS
private void CoreWebView2OnDOMContentLoaded(object? sender, CoreWebView2DOMContentLoadedEventArgs e)
{
// 既に DocumentCreated で実行されるが、念のため再実行(重複注入はスクリプト側で防止)
_ = _webView2?.CoreWebView2.ExecuteScriptAsync(BuildInjectionScript());
}
private static string BuildInjectionScript()
{
// style タグを一度だけ追加する(重複防止用に id を固定)
return @"
(function () {
const STYLE_ID = '**maui_hide_vscrollbar**';
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = ` html, body {
-ms-overflow-style: none;
scrollbar-width: none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
width: 0 !important;
display: none !important;
}
`;
document.head && document.head.appendChild(style);
})();";
}
#endif
}
XAML 側で適用
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:YourApp.Behaviors">
<WebView Source="https://example.com">
<WebView.Behaviors>
<behaviors:HideVerticalScrollBarBehavior />
</WebView.Behaviors>
</WebView>
この形なら、スクロールバーを消したい画面だけに Behavior を付けられます。アプリ全体の WebView に強制適用しないので、画面ごとの要件差(スクロールバーが必要な管理画面など)にも対応できます。
DOM のタイミング問題:どこで注入するのが一番安全か
「注入したのに効いたり効かなかったりする」場合、ほとんどが 実行タイミング の問題です。WebView2 では “CoreWebView2 がまだ無い” “head がまだ無い” “SPA で画面だけ書き換わった” といったケースが起きやすいので、複数のタイミングを組み合わせると安定します。
| タイミング | 狙い | メリット | 注意点 |
|---|---|---|---|
| EnsureCoreWebView2Async 後 | CoreWebView2 を確実に触れるようにする | Null 例外を避けられる | ここではまだ DOM が無い場合がある |
| AddScriptToExecuteOnDocumentCreatedAsync | 各ナビゲーションで自動実行 | 「遷移すると元に戻る」を防ぎやすい | ページ側の CSP やフレーム構成に影響されることがある |
| DOMContentLoaded | DOM が組み上がった後に確実に追加 | head への追加が通りやすい | SPA の内部遷移では発火しないことがある |
| NavigationCompleted | 描画完了後の最後の一押し | 見た目の揺れを確認しやすい | 遅い実行になるので、一瞬バーが見える場合がある |
実務では、DocumentCreated に登録しつつ、DOMContentLoaded でも念押し が一番バランスが良いことが多いです(重複注入は style の id で防ぐ)。
「ページ内の特定要素がスクロールしている」ケースに注意
Web ページによっては、スクロール対象が body ではなく、次のような要素になっている場合があります。
- モーダル内のスクロール領域(例:
.modal-body) - 一覧コンポーネントのスクロール(例:
.list) - CSS で
height: 100vhを使い、内部のdivにoverflow: autoを当てている
この場合、body だけに CSS を当てても縦スクロールバーが残ります。自分が管理できるページなら、スクロール要素にクラスを付けて “そこだけ” を狙い撃ちするのが保守的です。
/* 例:スクロール領域が .scroll-container の場合 */
.scroll-container {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scroll-container::-webkit-scrollbar {
width: 0 !important;
display: none !important;
}
外部ページで構造が読めない場合は *::-webkit-scrollbar のような全要素対象もできますが、意図しない副作用(エディタや地図など、スクロールバーが必要なUIまで消える)が出やすいので、最終手段にしてください。
よくあるつまずきと対処
| 症状 | 原因になりやすいポイント | 対処の方向性 |
|---|---|---|
| NullReferenceException が出る | CoreWebView2 初期化前に触っている | EnsureCoreWebView2Async() を await してからイベント登録・スクリプト実行する |
| 最初は消えるが、別ページへ遷移すると戻る | 1回だけ ExecuteScriptAsync している | AddScriptToExecuteOnDocumentCreatedAsync で毎回注入する |
| たまに効かない/再現性が低い | DOM のタイミング(head が無い、SPAの内部遷移) | DocumentCreated + DOMContentLoaded の併用、必要なら NavigationCompleted でも再注入 |
| 縦は消えたが、スクロールできなくなった | overflow: hidden を入れてしまった | バー非表示目的なら overflow は触らず、scrollbar の見た目だけ潰す |
| 特定の部分だけバーが残る | スクロール領域が body ではなく div | スクロール要素にクラスを付けて CSS を追加する/構造に合わせてセレクタを調整 |
| 外部サイトだけ効かない | 厳しい CSP(Content-Security-Policy)やフレーム制御 | まずは対象サイトで動作確認。CSP が厳しい場合は「自前ページで包む」「表示方法を変える」など設計側の回避が必要 |
“スクロールバーを隠す”ことのUX・アクセシビリティ面の注意
縦スクロールバーは「今どれくらいスクロールできるか」「どこまで読んだか」を示す重要な手がかりです。完全に消すと、次のような影響が出ることがあります。
- スクロール可能であることに気づけない(特にマウスホイールを使わないユーザー)
- 長文ページで現在位置が分からなくなる
- ドラッグでの高速スクロールができなくなる
そのため、見た目の都合でバーを消す場合でも、次のような “代替の手がかり” を用意すると運用トラブルが減ります。
- ページ末尾に到達しやすいように「次へ」「トップへ」などのボタンを配置する
- コンテンツが長い場合はページ内ナビ(固定ヘッダー、セクションジャンプ)を用意する
- アプリ側の UI と被るなら、Web 側に余白(padding)を確保してバーを目立たなくするという折衷案も検討する
補足:iOS の InputTransparent と混同しない
iOS では「スクロールを無効化したい」目的で InputTransparent="True" のようなアプローチが語られることがありますが、これは タッチ入力を透過させることで操作を受け付けなくする 方向の手段です。Windows の WebView2 で “スクロールバーだけを消す” という話とは目的も副作用も異なります。
まとめ:Windows の .NET MAUI WebView は「CSS注入」が最短ルート
- Windows の .NET MAUI WebView は WebView2 で描画され、スクロールバーは Web(HTML/CSS)側の要素として扱われることが多い
- スクロールバーを消したいなら、JavaScript で
<style>を注入して::-webkit-scrollbarを制御する方法が現実的 - 安定させるコツは、
EnsureCoreWebView2Asyncで初期化を待ち、AddScriptToExecuteOnDocumentCreatedAsyncとDOMContentLoadedで適用漏れを防ぐこと - ページ内のスクロール領域が body 以外のケースや、外部サイトの CSP には要注意
「縦スクロールバーだけ隠して、操作性は落としたくない」という要件はよくあります。まずは本記事の Behavior 例を最小構成で動かし、対象ページのスクロール構造に合わせて CSS セレクタを調整していくのが成功パターンです。

コメント