diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10ae73c6d..3aac7286c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he
 ### Changed
 
 - **core**: Optimize QueryId::is_same by not creating a String using format for every comparison (#678 @tashcan)
+- **core**: Skip paint for out of bounds widgets (#677 @tashcan)
 
 ## [0.4.0-alpha.19] - 2024-12-18
 
diff --git a/core/src/widget_tree/widget_id.rs b/core/src/widget_tree/widget_id.rs
index 4a4eb55de..1257dd6ea 100644
--- a/core/src/widget_tree/widget_id.rs
+++ b/core/src/widget_tree/widget_id.rs
@@ -1,5 +1,6 @@
 use std::{convert::Infallible, rc::Rc};
 
+use ahash::HashSetExt;
 use indextree::{Node, NodeId};
 use rxrust::ops::box_it::CloneableBoxOp;
 use smallvec::smallvec;
@@ -289,21 +290,38 @@ impl WidgetId {
 
   pub(crate) fn paint_subtree(self, ctx: &mut PaintingCtx) {
     let mut w = Some(self);
+
+    let wnd = ctx.window();
+    let tree = wnd.tree();
+
+    let mut did_paint = HashSet::<WidgetId, ahash::RandomState>::new();
     while let Some(id) = w {
       ctx.id = id;
-      ctx.painter.save();
-      let wnd = ctx.window();
-      let tree = wnd.tree();
 
       let mut need_paint = false;
       if ctx.painter.alpha() != 0. {
         if let Some(layout_box) = ctx.box_rect() {
-          let render = id.assert_get(tree);
+          let transform = *ctx.painter.transform();
           ctx
             .painter
             .translate(layout_box.min_x(), layout_box.min_y());
-          render.paint(ctx);
-          need_paint = true;
+          if ctx
+            .painter()
+            .intersection_paint_bounds(&Rect::from_size(layout_box.size))
+            .is_some()
+          {
+            let render = id.assert_get(tree);
+            ctx.painter.set_transform(transform);
+            ctx.painter.save();
+            ctx
+              .painter
+              .translate(layout_box.min_x(), layout_box.min_y());
+            render.paint(ctx);
+            did_paint.insert(id);
+            need_paint = true;
+          } else {
+            ctx.painter.set_transform(transform);
+          }
         }
       }
 
@@ -314,7 +332,10 @@ impl WidgetId {
           let mut node = w;
           while let Some(p) = node {
             // self node sub-tree paint finished, goto sibling
-            ctx.painter.restore();
+            if did_paint.contains(&p) {
+              did_paint.remove(&p);
+              ctx.painter.restore();
+            }
             node = p.next_sibling(tree);
             if node.is_some() {
               break;