Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing the webview background color #1564

Closed
mallendeo opened this issue Apr 21, 2021 · 30 comments · Fixed by #11486
Closed

Allow changing the webview background color #1564

mallendeo opened this issue Apr 21, 2021 · 30 comments · Fixed by #11486
Labels
status: backlog Issue is ready and we can work on it type: feature request

Comments

@mallendeo
Copy link

mallendeo commented Apr 21, 2021

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

default.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.

transparent.mp4
@mallendeo mallendeo changed the title Screen flashes a white background before rendering app App window flashes a white background before rendering content Apr 21, 2021
@kinghat
Copy link

kinghat commented Apr 23, 2021

also experiencing this on linux:

Peek.2021-04-23.12-23.mp4

@chippers
Copy link
Member

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.

2021-04-23.11-55-56.mp4

This is a change needed in wry to support setting the webview background color. I've opened up a ticket at tauri-apps/wry#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.

@happyshovels
Copy link

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
  }
]

@amrbashir amrbashir added the status: upstream This issue is blocked by upstream dependencies and we need to wait or contribute upstream fixes label Apr 29, 2022
@martpie
Copy link

martpie commented May 17, 2022

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)

@isopterix
Copy link

@martpie: would you mind sharing your code?

@martpie
Copy link

martpie commented Jun 5, 2022

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

@JonasKruckenberg
Copy link
Member

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.

@mantou132

This comment was marked as outdated.

@jlgerber
Copy link

jlgerber commented Aug 1, 2022

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.

@amrbashir amrbashir added status: backlog Issue is ready and we can work on it type: feature request and removed status: upstream This issue is blocked by upstream dependencies and we need to wait or contribute upstream fixes type: bug labels Sep 30, 2022
@amrbashir amrbashir changed the title App window flashes a white background before rendering content Allow changing the webview background color Sep 30, 2022
@Raduc4
Copy link

Raduc4 commented May 8, 2023

Has anyone solved this?

cguedes added a commit to refstudio/refstudio that referenced this issue Jul 18, 2023
…enders

Note that the webkit white background is a pending tauri issue: tauri-apps/tauri#1564
cguedes added a commit to refstudio/refstudio that referenced this issue Jul 18, 2023
* Rename EmptyView to WelcomeView

* Add splashscreen and load settings and project structure before app renders

Note that the webkit white background is a pending tauri issue: tauri-apps/tauri#1564

* Fix imports

* Add splash.tsx to the entry list of knip

* Fix PR comments and move AppStartup to own file

* Add unit tests

* Make transparent splashscreen

* Revert "Make transparent splashscreen"

This reverts commit 0b267ce.
@thnee
Copy link

thnee commented Mar 9, 2024

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>

@pronebird
Copy link
Contributor

pronebird commented Apr 12, 2024

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.

@FabianLars
Copy link
Member

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.

@pronebird
Copy link
Contributor

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
Copy link
Contributor

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];
            }
        })
    }
}

@rogue-ninja-creative
Copy link

It seems apps using Electron have fixed this. I've been using MongoDB Compass for a long time and it had the same issue; a flash of white before the content loads and the background colour updates. But that seems to have been fixed with the latest update.

mitkury added a commit to sssupa/supa that referenced this issue Apr 22, 2024
…d is ready to show ([solution](tauri-apps/tauri#1564 (comment)))

Make my own persistent window logic (the plugin I used doesn't work with 'visibility': false)
@albingroen
Copy link

Any update on this? This is another one of those things that make me wanna go to Electron :/

I understand it's a hard problem, and if I knew where to look I'd love to help. Is anyone actively working on it?

The "white flash" effect in a dark mode app really makes it feel cheap...

@martpie
Copy link

martpie commented Aug 8, 2024

The best workaround is to hide the window by default, and only show it once your app has loaded the required resources (and eventually painted another frame via requestAnimationFrame)

This may introduce an additional delay to show the app though.

Pick your poison.

@timuric
Copy link

timuric commented Aug 13, 2024

There is a guide on how to do it at least on macos in tauri v2 https://v2.tauri.app/plugin/window-customization/#macos-transparent-titlebar-with-custom-window-background-color

@xuchaoqian
Copy link
Contributor

The "white flash" effect in a dark mode app really makes it feel cheap...

@albingroen I agree with your point, this is a very impactful issue for the user experience. Just imagine the feeling of opening VSCode every time and seeing a blank white screen.

@amrbashir has made efforts in the past: feat: allow setting webview bg color, and I feel we are quite close to solving this problem, we just need a hero to step up and conquer this challenge.

@martpie
Copy link

martpie commented Aug 15, 2024

Do you mean it's now supported by Wry-vanilla and the only thing left is to implement this into Tauri's APIs? (Surface option in window configuration, pass it to Wry, etc)?

@its-monotype
Copy link

Is there anything similar to Electron's event ready-to-show? ref

@deminearchiver
Copy link

Is there anything similar to Electron's event ready-to-show? ref

What I do is I make the window be initially invisible, attach either a DOMContentLoaded event listener on the JS side, or use a framework-specific "on mount" hook, like createEffect for React or onMount for SolidJS, in which show the current window.

@KirillTregubov
Copy link

With the release of tauri v2, is there any update to this?

I'll try digging through the changes and see if I can find something to accomplish this.

@dionysuzx
Copy link

dionysuzx commented Oct 17, 2024

EDIT: THIS DOESN'T WORK

I solved this issue in my Svelte 5 + Tauri 2.0 app like so:

  1. Set "visible": false in tauri.conf.json.

  2. Set the "core:window:allow-show" permission under capabilities/default.json like so:

    {
        ...
        "permissions": ["core:default", "core:window:allow-show", "shell:allow-open"]
    }
  3. Show window on mount (using state rune / store):

    Homepage:

    <script lang="ts">
      import { onMount } from "svelte";
      import { window } from "../stores/window.svelte.js";
    
      onMount(() => {
        window.show();
      });
    </script>

    Writable store:

    import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
    
    let shown = $state(false);
    
    export const window = {
      get shown() {
        return shown;
      },
      async show() {
        if (!shown) {
          console.log("showing window");
          await getCurrentWebviewWindow().show();
          shown = true;
        } else {
          console.log("window already shown");
        }
      },
    };

Hope it helps, cheers! 🍺

@xuchaoqian
Copy link
Contributor

You will see the white background again if you reload, refresh, or resize the window. It seems very abrupt, especially in dark theme mode.

I solved this issue in my Svelte 5 + Tauri 2.0 app like so:

  1. Set "visible": false in tauri.conf.json.

  2. Set the "core:window:allow-show" permission under capabilities/default.json like so:

    {
        ...
        "permissions": ["core:default", "core:window:allow-show", "shell:allow-open"]
    }
  3. Show window on mount (using state rune / store):
    Homepage:

    <script lang="ts">
      import { onMount } from "svelte";
      import { window } from "../stores/window.svelte.js";
    
      onMount(() => {
        window.show();
      });
    </script>

    Writable store:

    import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
    
    let shown = $state(false);
    
    export const window = {
      get shown() {
        return shown;
      },
      async show() {
        if (!shown) {
          console.log("showing window");
          await getCurrentWebviewWindow().show();
          shown = true;
        } else {
          console.log("window already shown");
        }
      },
    };

Hope it helps, cheers! 🍺

@dionysuzx
Copy link

dionysuzx commented Oct 18, 2024

you're right @xuchaoqian. this is not fixed. i'll update my comment until i figure out a fix... unless; do you know how to fix it? :)

@meow2149
Copy link

You just do it:

import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
window.addEventListener('DOMContentLoaded', async () => {
  await getCurrentWebviewWindow().show()
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: backlog Issue is ready and we can work on it type: feature request
Projects
Status: 📬Proposal
Development

Successfully merging a pull request may close this issue.