diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs
index 31f6e79c01c8..620fe86fd624 100644
--- a/crates/eframe/src/web/app_runner.rs
+++ b/crates/eframe/src/web/app_runner.rs
@@ -60,7 +60,9 @@ impl AppRunner {
         super::storage::load_memory(&egui_ctx);
 
         egui_ctx.options_mut(|o| {
-            // On web, the browser controls the zoom factor:
+            // On web by default egui follows the zoom factor of the browser,
+            // and lets the browser handle the zoom shortscuts.
+            // A user can still zoom egui separately by calling [`egui::Context::set_zoom_factor`].
             o.zoom_with_keyboard = false;
             o.zoom_factor = 1.0;
         });
@@ -183,7 +185,7 @@ impl AppRunner {
     /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`].
     pub fn logic(&mut self) {
         super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points);
-        let canvas_size = super::canvas_size_in_points(self.canvas());
+        let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx());
         let raw_input = self.input.new_frame(canvas_size);
 
         let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs
index 30775733e583..c8678c81f57d 100644
--- a/crates/eframe/src/web/events.rs
+++ b/crates/eframe/src/web/events.rs
@@ -296,7 +296,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
             let modifiers = modifiers_from_mouse_event(&event);
             runner.input.raw.modifiers = modifiers;
             if let Some(button) = button_from_mouse_event(&event) {
-                let pos = pos_from_mouse_event(runner.canvas(), &event);
+                let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
                 let modifiers = runner.input.raw.modifiers;
                 runner.input.raw.events.push(egui::Event::PointerButton {
                     pos,
@@ -323,7 +323,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
         |event: web_sys::MouseEvent, runner| {
             let modifiers = modifiers_from_mouse_event(&event);
             runner.input.raw.modifiers = modifiers;
-            let pos = pos_from_mouse_event(runner.canvas(), &event);
+            let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
             runner.input.raw.events.push(egui::Event::PointerMoved(pos));
             runner.needs_repaint.repaint_asap();
             event.stop_propagation();
@@ -335,7 +335,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
         let modifiers = modifiers_from_mouse_event(&event);
         runner.input.raw.modifiers = modifiers;
         if let Some(button) = button_from_mouse_event(&event) {
-            let pos = pos_from_mouse_event(runner.canvas(), &event);
+            let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
             let modifiers = runner.input.raw.modifiers;
             runner.input.raw.events.push(egui::Event::PointerButton {
                 pos,
@@ -373,7 +373,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
         "touchstart",
         |event: web_sys::TouchEvent, runner| {
             let mut latest_touch_pos_id = runner.input.latest_touch_pos_id;
-            let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id);
+            let pos = pos_from_touch_event(
+                runner.canvas(),
+                &event,
+                &mut latest_touch_pos_id,
+                runner.egui_ctx(),
+            );
             runner.input.latest_touch_pos_id = latest_touch_pos_id;
             runner.input.latest_touch_pos = Some(pos);
             let modifiers = runner.input.raw.modifiers;
@@ -396,7 +401,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
         "touchmove",
         |event: web_sys::TouchEvent, runner| {
             let mut latest_touch_pos_id = runner.input.latest_touch_pos_id;
-            let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id);
+            let pos = pos_from_touch_event(
+                runner.canvas(),
+                &event,
+                &mut latest_touch_pos_id,
+                runner.egui_ctx(),
+            );
             runner.input.latest_touch_pos_id = latest_touch_pos_id;
             runner.input.latest_touch_pos = Some(pos);
             runner.input.raw.events.push(egui::Event::PointerMoved(pos));
@@ -459,7 +469,9 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
         });
 
         let scroll_multiplier = match unit {
-            egui::MouseWheelUnit::Page => canvas_size_in_points(runner.canvas()).y,
+            egui::MouseWheelUnit::Page => {
+                canvas_size_in_points(runner.canvas(), runner.egui_ctx()).y
+            }
             egui::MouseWheelUnit::Line => {
                 #[allow(clippy::let_and_return)]
                 let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit.
diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs
index 4ed0227739a1..361e8031d407 100644
--- a/crates/eframe/src/web/input.rs
+++ b/crates/eframe/src/web/input.rs
@@ -3,11 +3,13 @@ use super::{canvas_origin, AppRunner};
 pub fn pos_from_mouse_event(
     canvas: &web_sys::HtmlCanvasElement,
     event: &web_sys::MouseEvent,
+    ctx: &egui::Context,
 ) -> egui::Pos2 {
     let rect = canvas.get_bounding_client_rect();
+    let zoom_factor = ctx.zoom_factor();
     egui::Pos2 {
-        x: event.client_x() as f32 - rect.left() as f32,
-        y: event.client_y() as f32 - rect.top() as f32,
+        x: (event.client_x() as f32 - rect.left() as f32) / zoom_factor,
+        y: (event.client_y() as f32 - rect.top() as f32) / zoom_factor,
     }
 }
 
@@ -32,6 +34,7 @@ pub fn pos_from_touch_event(
     canvas: &web_sys::HtmlCanvasElement,
     event: &web_sys::TouchEvent,
     touch_id_for_pos: &mut Option<egui::TouchId>,
+    egui_ctx: &egui::Context,
 ) -> egui::Pos2 {
     let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos {
         // search for the touch we previously used for the position
@@ -49,14 +52,19 @@ pub fn pos_from_touch_event(
         .or_else(|| event.touches().get(0))
         .map_or(Default::default(), |touch| {
             *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier()));
-            pos_from_touch(canvas_origin(canvas), &touch)
+            pos_from_touch(canvas_origin(canvas), &touch, egui_ctx)
         })
 }
 
-fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 {
+fn pos_from_touch(
+    canvas_origin: egui::Pos2,
+    touch: &web_sys::Touch,
+    egui_ctx: &egui::Context,
+) -> egui::Pos2 {
+    let zoom_factor = egui_ctx.zoom_factor();
     egui::Pos2 {
-        x: touch.page_x() as f32 - canvas_origin.x,
-        y: touch.page_y() as f32 - canvas_origin.y,
+        x: (touch.page_x() as f32 - canvas_origin.x) / zoom_factor,
+        y: (touch.page_y() as f32 - canvas_origin.y) / zoom_factor,
     }
 }
 
@@ -68,7 +76,7 @@ pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web
                 device_id: egui::TouchDeviceId(0),
                 id: egui::TouchId::from(touch.identifier()),
                 phase,
-                pos: pos_from_touch(canvas_origin, &touch),
+                pos: pos_from_touch(canvas_origin, &touch, runner.egui_ctx()),
                 force: Some(touch.force()),
             });
         }
diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs
index d88e94229fd0..a322a530aa67 100644
--- a/crates/eframe/src/web/mod.rs
+++ b/crates/eframe/src/web/mod.rs
@@ -116,8 +116,8 @@ fn canvas_origin(canvas: &web_sys::HtmlCanvasElement) -> egui::Pos2 {
     egui::pos2(rect.left() as f32, rect.top() as f32)
 }
 
-fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement) -> egui::Vec2 {
-    let pixels_per_point = native_pixels_per_point();
+fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Context) -> egui::Vec2 {
+    let pixels_per_point = ctx.pixels_per_point();
     egui::vec2(
         canvas.width() as f32 / pixels_per_point,
         canvas.height() as f32 / pixels_per_point,
diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs
index b84308283724..7d61acd965f4 100644
--- a/crates/egui/src/memory.rs
+++ b/crates/egui/src/memory.rs
@@ -185,6 +185,12 @@ pub struct Options {
     /// presses Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
     ///
     /// This is `true` by default.
+    ///
+    /// On the web-backend of `eframe` this is set to false by default,
+    /// so that the zoom shortcuts are handled exclusively by the browser,
+    /// which will change the `native_pixels_per_point` (`devicePixelRatio`).
+    /// You can still zoom egui independently by calling [`crate::Context::set_zoom_factor`],
+    /// which will be applied on top of the browsers global zoom.
     #[cfg_attr(feature = "serde", serde(skip))]
     pub zoom_with_keyboard: bool,