tauri icon indicating copy to clipboard operation
tauri copied to clipboard

Allow changing the webview background color

Open mallendeo opened this issue 3 years ago • 18 comments

Describe the bug Window flashes a white background before rendering app content on macOS Big Sur.

The css for the index.html is:

html, body {
  width: 100%; height: 100%;
  background: rgb(231, 232, 232);
}

@media (prefers-color-scheme: dark) {
  html, body {
    background: rgb(37, 38, 40);
  }
}

To Reproduce Steps to reproduce the behavior:

  1. Create an index.html with <style>html, body { background: black; }</style>
  2. Run the app (dev, or dist bundle)
  3. Screen will flash a white background before rendering the content.

Expected behavior The main window should wait for the content to load, or it should apply the page's background color on open.

Screenshots

https://user-images.githubusercontent.com/1578018/115579993-b0ccbf80-a294-11eb-8c68-07898105ab25.mp4

Platform and Versions (please complete the following information):

OS: Mac OS, version 11.2.3 X64 Node: 15.13.0 NPM: 7.7.6 Yarn: 1.22.10 Rustc: 1.51.0

Additional context This is running the app with the default values (no transparent window mode). When using transparent window mode, it doesn't flash, but the window loses all shadows and borders until it is resized.

https://user-images.githubusercontent.com/1578018/115580068-bfb37200-a294-11eb-832d-9639839ae126.mp4

mallendeo avatar Apr 21 '21 15:04 mallendeo

also experiencing this on linux:

https://user-images.githubusercontent.com/16073360/115911342-94b35480-a433-11eb-90b9-23e64960c7a3.mp4

kinghat avatar Apr 23 '21 17:04 kinghat

I suspected this is the browser's default background color, so I made a small edit to wry to set the background manually to red (linux only) to see this a bit easier. The initial color (dark/light) is the OS window's default color, then the webview is loaded with whatever background color is set (red here), and then the application loads.

https://user-images.githubusercontent.com/3301043/115920054-844cab00-a42e-11eb-99e6-1ff5ddbd674f.mp4

This is a change needed in wry to support setting the webview background color. I've opened up a ticket at https://github.com/tauri-apps/wry/issues/197.

If/when that is implemented, then we can look at expanding the tauri::Attributes trait to include setting the background color, and maybe add a hex string backgroundColor field to the window config inside tauri.config.json.

chippers avatar Apr 23 '21 19:04 chippers

Since I also ran into this issue today, I am sharing my hacky workaround in case someone is also bothered by the white flashing. The idea is to start the window as hidden and then show it after the html is hopefully rendered.

It works somehow like this:

.setup(|app| {

      let main_window = app.get_window("main").unwrap();
      // we perform the initialization code on a new task so the app doesn't freeze
      tauri::async_runtime::spawn(async move {
        // adapt sleeping time to be long enough
        sleep(Duration::from_millis(500));
        main_window.show().unwrap();
      });

      Ok(())
    })

and win the tauri.conf.json

"windows": [
  {
    ...
    "visible": false
  }
]

happyshovels avatar May 14 '21 22:05 happyshovels

use std::thread::sleep;
use std::time::Duration;

Works fine if the delay is 175 ms instead of 500.

elibroftw avatar Mar 27 '22 22:03 elibroftw

I faced the same issue building Museeks with Electron. I am not sure there is much Tauri can do about it.

How I actually solved it is by hiding the window by default, and showing it only after my frontend app has loaded (via an event) (instead of using an arbitrary sleep that may need different values on different systems)

martpie avatar May 17 '22 11:05 martpie

@martpie: would you mind sharing your code?

isopterix avatar Jun 05 '22 10:06 isopterix

Of course:

Tauri conf:

"windows": [
  {
    "title": "Your app",
+   "visible": false,
    "fullscreen": false,
    "resizable": true
  }
 ]

Rust side:

#[tauri::command]
pub async fn show_main_window(window: tauri::Window) {
    window.get_window("main").unwrap().show().unwrap(); // replace "main" by the name of your window
}

From your front-end, once you know your app is instantialized (for example with SolidJS):

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

// Solid JS speicifc, `componentDidMount` for React, etc...
onMounted(() => {
  invoke('show_main_window');
})

you just have to find the equivalent of Vue, React etc if you use any other

martpie avatar Jun 05 '22 11:06 martpie

Of course:

Tauri conf:


"windows": [

  {

    "title": "Your app",

+   "visible": false,

    "fullscreen": false,

    "resizable": true

  }

 ]

Rust side:


#[tauri::command]

pub async fn show_main_window(window: tauri::Window) {

    window.get_window("main").unwrap().show().unwrap(); // replace "main" by the name of your window

}

From your front-end, once you know your app is instantialized (for example with SolidJS):


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



onMounted(() => {

  invoke('show_main_window');

})

you just have to find the equivalent of Vue, React etc if you use any other

This is in fact the recommendations solution to this problem. And optimizing your Frontend rendering (through ssg or similar) so the delay to first-content is very small. Something like the app-shell architecture helps here as well.

JonasKruckenberg avatar Jun 05 '22 15:06 JonasKruckenberg

MacOS solution:

We can remove the webView background:

main_window.with_webview(|webview|  unsafe {
   let id = webview.inner();
   let no: cocoa::base::id = msg_send![class!(NSNumber), numberWithBool:0];
   let _: cocoa::base::id =
   msg_send![id, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")];
});

ref: https://github.com/tauri-apps/wry/blob/765fe5ae413a1c13ad99802b49f3af859a2445d5/src/webview/macos/mod.rs#L180

then set the window background to the color you want:

let id = main_window.ns_window().unwrap() as cocoa::base::id;
let color = NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0.0, 0.0, 0.0, 1.0);
let _: cocoa::base::id = msg_send![id, setBackgroundColor: color];

mantou132 avatar Jun 29 '22 15:06 mantou132

Unfortunately, this doesn't seem to work for dynamically created windows. At least with in Sveltekit. If you create a window using WebveiwWindow and set display: false in the options, then the onMount function never gets called for the widget with the supplied route.

jlgerber avatar Aug 01 '22 04:08 jlgerber

Has anyone solved this?

Raduc4 avatar May 08 '23 17:05 Raduc4

Hey 👋 What if we extend the tauri config to support setting a background color from one single place to both the shell's background color and the webview's background color? So whenever the initial load happens, there would be no "flash granade" thrown into your face.

Then, if needed, it can be overriden by the webview's CSS styles.

So it would be:

  1. Tauri's Config -> appBackgroundColor
  • Sets the background color for the app shell
  • Sets the background color for the webview's browser default background color
  1. Application specific css overrides
  • This would override the webview's browser defaults with whatever color you've picked

reanimatedmanx avatar Sep 03 '23 08:09 reanimatedmanx

Just wanted to check in on what the status of this is in 2024. I am still experiencing the issue outlined by the author on Windows 11. The maintainers of wry have added set_background_color to wry::Webview in version 0.21.0. I just checked my Cargo.lock file and the latest version of Tauri seems to be using wry 0.24.7.

Unfortunately, from my research, there seems to be no way for us to accesss this wry API in our Rust code. Is there any plan on exposing this functionality for Tauri users? Alternatively, has anyone found a solution to this issue?

KirillTregubov avatar Jan 17 '24 06:01 KirillTregubov

It's an interesting problem, that is hard to solve. Even some major products still have the white flash on startup problem, like Google Chrome for example.

If we just define a color through a static config file, then it would still flash in the wrong color for users that prefer a different theme. This is less than ideal, but I guess that in general it's at least less annoying with black than white.

A somewhat better solution would be if Tauri could detect light/dark system theme, and set a configurable light/dark background color when creating the initial window. But if an app supports multiple themes, besides just light and dark, then it would still flash in the wrong color for users that prefer a different theme.

There is always going to be a delay between the initial window being created, and frontend code rendering colors. As long as the app is applying any customizable colors through frontend code, there will be a flash of a different color. So ideally there would be some more sophisticated solution here.

One option would be to add some kind of argument or hook to the window creation where a color can be provided as input. So that the app has a chance to read user and system settings and make a decision based on some custom logic, and pass that into the window creation.

It would be really cool if there was a whole theme system in Tauri, where we could define custom themes in config, and it would be integrated, so it applies automatically to window background and decorations, and is made available to the app code as variables in Rust, JS, and CSS.


For Tauri v2, I am using the following workaround. However, I belive this only works for desktop, and there is no equivalent thing for mobile, so the white flash still remains an issue there.

Tauri tauri.conf.json

{
	"app": {
		"windows": [
			{
				"title": "MyTitle",
				"visible": false
			}
		],
	},

SvelteKit +layout.svelte

<script>
	import { onMount } from "svelte";

	import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
	import { PhysicalSize } from "@tauri-apps/api/dpi";

	import globals from "$lib/globals.svelte";

	function setPreferedTheme() {
		let prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
		let value = prefersDark ? "dark" : "light";
		document.documentElement.setAttribute("data-theme", value);
	}

	async function initWindow() {
		if (globals.windowInited) {
			return;
		}

		setPreferedTheme();

		// Sleep because it takes a split second for colors to update after setting theme,
		// which leads to a white flash if using dark theme.
		await new Promise(r => setTimeout(r, 42));

		let window = WebviewWindow.getCurrent();
		await window.setTitle("MyTitle");
		await window.setSize(new PhysicalSize(1400, 800));
		await window.setMinSize(new PhysicalSize(300, 400));
		await window.center();
		await window.show();

		globals.windowInited = true;
	}

	onMount(() => {
		initWindow();
	});
</script>

thnee avatar Mar 09 '24 17:03 thnee

It would be great to expose the background color setting in the Webview API. Spent about an hour trying to set background color here and there and even used some sample from with_webview which returns a platform webview but nothing really worked and the app still flashes white background at open.

pronebird avatar Apr 12 '24 18:04 pronebird

but nothing really worked and the app still flashes white background at open.

Likely because it's not (only) the webview flashing but also the native window itself which makes this quite a bit harder.

FabianLars avatar Apr 12 '24 18:04 FabianLars

but nothing really worked and the app still flashes white background at open.

Likely because it's not (only) the webview flashing but also the native window itself which makes this quite a bit harder.

Good point. I will set the background colour on both and see if it helps.

pronebird avatar Apr 12 '24 19:04 pronebird

I can create a window without webview and change the color. That works fine.

But webview doesn't seem to react to the change of color:

pub trait WebviewExt {
    #[cfg(target_os = "macos")]
    fn set_background_color(&self, color: &tauri::window::Color) -> tauri::Result<()>;
}

impl WebviewExt for tauri::Webview {
    #[cfg(target_os = "macos")]
    fn set_background_color(&self, color: &tauri::window::Color) -> tauri::Result<()> {
        use crate::color_ext::ColorExt;
        let (r, g, b, a) = color.as_nscolor_tuple();

        self.with_webview(move |platform_webview| {
            unsafe {
                let nscolor: cocoa::base::id =
                    msg_send![class!(NSColor), colorWithDeviceRed:r green:g blue:b alpha:a];
                let id = platform_webview.ns_window();
                let () = msg_send![id, setBackgroundColor: nscolor];
            }
        })
    }
}

pronebird avatar Apr 16 '24 10:04 pronebird