diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs
index 4e3ebad1dfe7..bae0a3fb4dd9 100644
--- a/crates/eframe/src/lib.rs
+++ b/crates/eframe/src/lib.rs
@@ -85,10 +85,10 @@
 //!
 //!     /// Call this once from JavaScript to start your app.
 //!     #[wasm_bindgen]
-//!     pub async fn start(&self, canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
+//!     pub async fn start(&self, canvas: web_sys::HtmlCanvasElement) -> Result<(), wasm_bindgen::JsValue> {
 //!         self.runner
 //!             .start(
-//!                 canvas_id,
+//!                 canvas,
 //!                 eframe::WebOptions::default(),
 //!                 Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc))),)
 //!             )
diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs
index f683b991affe..1a0c3f44c530 100644
--- a/crates/eframe/src/web/app_runner.rs
+++ b/crates/eframe/src/web/app_runner.rs
@@ -31,12 +31,12 @@ impl AppRunner {
     /// # Errors
     /// Failure to initialize WebGL renderer, or failure to create app.
     pub async fn new(
-        canvas_id: &str,
+        canvas: web_sys::HtmlCanvasElement,
         web_options: crate::WebOptions,
         app_creator: epi::AppCreator,
         text_agent: TextAgent,
     ) -> Result<Self, String> {
-        let painter = super::ActiveWebPainter::new(canvas_id, &web_options).await?;
+        let painter = super::ActiveWebPainter::new(canvas, &web_options).await?;
 
         let system_theme = if web_options.follow_system_theme {
             super::system_theme()
diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs
index 07339d7e081a..71aeb7915abf 100644
--- a/crates/eframe/src/web/mod.rs
+++ b/crates/eframe/src/web/mod.rs
@@ -122,17 +122,6 @@ fn theme_from_dark_mode(dark_mode: bool) -> Theme {
     }
 }
 
-fn get_canvas_element_by_id(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
-    let document = web_sys::window()?.document()?;
-    let canvas = document.get_element_by_id(canvas_id)?;
-    canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
-}
-
-fn get_canvas_element_by_id_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement {
-    get_canvas_element_by_id(canvas_id)
-        .unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}"))
-}
-
 /// Returns the canvas in client coordinates.
 fn canvas_content_rect(canvas: &web_sys::HtmlCanvasElement) -> egui::Rect {
     let bounding_rect = canvas.get_bounding_client_rect();
diff --git a/crates/eframe/src/web/web_painter.rs b/crates/eframe/src/web/web_painter.rs
index e4db8eac3161..b5164b915c0e 100644
--- a/crates/eframe/src/web/web_painter.rs
+++ b/crates/eframe/src/web/web_painter.rs
@@ -5,7 +5,7 @@ use wasm_bindgen::JsValue;
 /// therefore this trait is merely there for specifying and documenting the interface.
 pub(crate) trait WebPainter {
     // Create a new web painter targeting a given canvas.
-    // fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String>
+    // fn new(canvas: HtmlCanvasElement, options: &WebOptions) -> Result<Self, String>
     // where
     //     Self: Sized;
 
diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs
index 29e23fa2fa84..e13cb0018cdd 100644
--- a/crates/eframe/src/web/web_painter_glow.rs
+++ b/crates/eframe/src/web/web_painter_glow.rs
@@ -18,9 +18,7 @@ impl WebPainterGlow {
         self.painter.gl()
     }
 
-    pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
-        let canvas = super::get_canvas_element_by_id_or_die(canvas_id);
-
+    pub async fn new(canvas: HtmlCanvasElement, options: &WebOptions) -> Result<Self, String> {
         let (gl, shader_prefix) =
             init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
         #[allow(clippy::arc_with_non_send_sync)]
diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs
index 1da12b0a0831..7d845a969e58 100644
--- a/crates/eframe/src/web/web_painter_wgpu.rs
+++ b/crates/eframe/src/web/web_painter_wgpu.rs
@@ -83,7 +83,10 @@ impl WebPainterWgpu {
     }
 
     #[allow(unused)] // only used if `wgpu` is the only active feature.
-    pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
+    pub async fn new(
+        canvas: web_sys::HtmlCanvasElement,
+        options: &WebOptions,
+    ) -> Result<Self, String> {
         log::debug!("Creating wgpu painter");
 
         let mut backends = options.wgpu_options.supported_backends;
@@ -162,7 +165,6 @@ impl WebPainterWgpu {
             }
         }
 
-        let canvas = super::get_canvas_element_by_id_or_die(canvas_id);
         let surface = instance
             .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
             .map_err(|err| format!("failed to create wgpu surface: {err}"))?;
diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs
index 6f6cc9308db8..120340893051 100644
--- a/crates/eframe/src/web/web_runner.rs
+++ b/crates/eframe/src/web/web_runner.rs
@@ -57,7 +57,7 @@ impl WebRunner {
     /// Failing to initialize graphics, or failure to create app.
     pub async fn start(
         &self,
-        canvas_id: &str,
+        canvas: web_sys::HtmlCanvasElement,
         web_options: crate::WebOptions,
         app_creator: epi::AppCreator,
     ) -> Result<(), JsValue> {
@@ -67,7 +67,7 @@ impl WebRunner {
 
         let text_agent = TextAgent::attach(self)?;
 
-        let runner = AppRunner::new(canvas_id, web_options, app_creator, text_agent).await?;
+        let runner = AppRunner::new(canvas, web_options, app_creator, text_agent).await?;
 
         {
             // Make sure the canvas can be given focus.
diff --git a/crates/egui_demo_app/src/web.rs b/crates/egui_demo_app/src/web.rs
index 8c64f30142eb..8c9cdd1d493e 100644
--- a/crates/egui_demo_app/src/web.rs
+++ b/crates/egui_demo_app/src/web.rs
@@ -32,10 +32,13 @@ impl WebHandle {
 
     /// Call this once from JavaScript to start your app.
     #[wasm_bindgen]
-    pub async fn start(&self, canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
+    pub async fn start(
+        &self,
+        canvas: web_sys::HtmlCanvasElement,
+    ) -> Result<(), wasm_bindgen::JsValue> {
         self.runner
             .start(
-                canvas_id,
+                canvas,
                 eframe::WebOptions::default(),
                 Box::new(|cc| Ok(Box::new(WrapApp::new(cc)))),
             )
diff --git a/web_demo/index.html b/web_demo/index.html
index 76866d363e7f..901dcff42534 100644
--- a/web_demo/index.html
+++ b/web_demo/index.html
@@ -166,7 +166,7 @@
 
             check_for_panic();
 
-            handle.start("the_canvas_id").then(on_app_started).catch(on_error);
+            handle.start(document.getElementById("the_canvas_id")).then(on_app_started).catch(on_error);
         }
 
         function on_app_started(handle) {
diff --git a/web_demo/multiple_apps.html b/web_demo/multiple_apps.html
index 6b1903cf211d..515c1497c551 100644
--- a/web_demo/multiple_apps.html
+++ b/web_demo/multiple_apps.html
@@ -138,7 +138,10 @@
             const handle_one = new wasm_bindgen.WebHandle();
             const handle_two = new wasm_bindgen.WebHandle();
 
-            Promise.all([handle_one.start("canvas_id_one"), handle_two.start("canvas_id_two")]).then((handles) => {
+            Promise.all([
+                handle_one.start(document.getElementById("canvas_id_one")),
+                handle_two.start(document.getElementById("canvas_id_two")),
+            ]).then((handles) => {
                 on_apps_started(handle_one, handle_two)
             }).catch(on_error);
         }