@@ -178,6 +178,14 @@ pub enum DocumentEvent {
178178 ResourceLoad ( ResourceLoadResponse ) ,
179179}
180180
181+ #[ derive( Clone , Copy , Debug , Default , PartialEq , Eq ) ]
182+ pub enum ScrollBehavior {
183+ #[ default]
184+ Auto ,
185+ Instant ,
186+ Smooth ,
187+ }
188+
181189pub struct BaseDocument {
182190 /// ID of the document
183191 id : usize ,
@@ -1879,7 +1887,7 @@ impl BaseDocument {
18791887 self . viewport_scroll != initial
18801888 }
18811889
1882- pub fn scroll_by (
1890+ pub ( crate ) fn scroll_chain_by (
18831891 & mut self ,
18841892 anchor_node_id : Option < usize > ,
18851893 scroll_x : f64 ,
@@ -1918,24 +1926,7 @@ impl BaseDocument {
19181926 } )
19191927 }
19201928
1921- /// Scroll the viewport so that the given node is aligned with the top of the viewport.
1922- pub fn scroll_to_node ( & mut self , node_id : usize ) {
1923- let Some ( node) = self . nodes . get ( node_id) else {
1924- return ;
1925- } ;
1926-
1927- // `absolute_position` gives the node's position in document space (it does not
1928- // account for the viewport scroll), so it is the scroll offset we want to land on.
1929- let target = node. absolute_position ( 0.0 , 0.0 ) ;
1930- let current = self . viewport_scroll ;
1931-
1932- // `scroll_viewport_by` subtracts the delta from the current scroll offset, so pass
1933- // `current - target` in order to land on `target`.
1934- self . scroll_viewport_by ( current. x - target. x as f64 , current. y - target. y as f64 ) ;
1935- }
1936-
1937- /// Duration (in milliseconds) of an animated scroll started via
1938- /// [`BaseDocument::scroll_to_node_smooth`] or [`BaseDocument::scroll_to_fragment_smooth`].
1929+ /// Duration (in milliseconds) of an animated scroll.
19391930 const SMOOTH_SCROLL_DURATION_MS : f64 = 300.0 ;
19401931
19411932 /// Returns the current scroll offset and the maximum scroll offset (the minimum is
@@ -1999,52 +1990,64 @@ impl BaseDocument {
19991990 self . shell_provider . request_redraw ( ) ;
20001991 }
20011992
2002- /// Like [`BaseDocument::scroll_to_node`], but animates the viewport towards the node
2003- /// instead of jumping instantly.
2004- pub fn scroll_to_node_smooth ( & mut self , node_id : usize ) {
2005- let Some ( node) = self . nodes . get ( node_id) else {
1993+ fn should_scroll_smoothly ( & self , node_id : usize , behavior : ScrollBehavior ) -> bool {
1994+ match behavior {
1995+ ScrollBehavior :: Auto => self . nodes . get ( node_id) . is_some_and ( |node| {
1996+ node. primary_styles ( ) . is_some_and ( |style| {
1997+ style. clone_scroll_behavior ( )
1998+ == style:: computed_values:: scroll_behavior:: T :: Smooth
1999+ } )
2000+ } ) ,
2001+ ScrollBehavior :: Instant => false ,
2002+ ScrollBehavior :: Smooth => true ,
2003+ }
2004+ }
2005+
2006+ /// Scroll an element to the given absolute scroll offset in CSS pixels.
2007+ pub fn scroll_to ( & mut self , node_id : usize , x : f64 , y : f64 , behavior : ScrollBehavior ) {
2008+ if self . nodes . get ( node_id) . is_none ( ) {
20062009 return ;
2010+ }
2011+
2012+ let ( current, max) = self . node_scroll_state ( node_id) ;
2013+ let target = crate :: Point {
2014+ x : x. clamp ( 0.0 , max. x ) ,
2015+ y : y. clamp ( 0.0 , max. y ) ,
20072016 } ;
20082017
2009- let target = node. absolute_position ( 0.0 , 0.0 ) ;
2010- self . start_scroll_animation (
2011- None ,
2012- crate :: Point {
2013- x : target. x as f64 ,
2014- y : target. y as f64 ,
2015- } ,
2016- ) ;
2018+ if self . should_scroll_smoothly ( node_id, behavior) {
2019+ self . start_scroll_animation ( Some ( node_id) , target) ;
2020+ } else {
2021+ self . scroll_animation = ScrollAnimationState :: None ;
2022+ self . scroll_node_by (
2023+ node_id,
2024+ current. x - target. x ,
2025+ current. y - target. y ,
2026+ & mut |_| { } ,
2027+ ) ;
2028+ self . shell_provider . request_redraw ( ) ;
2029+ }
20172030 }
20182031
2019- /// Scroll a node to the given absolute scroll offset (in CSS pixels), clamped to the
2020- /// node's scrollable range. Scrolls on the root element are applied to the viewport.
2021- pub fn scroll_node_to ( & mut self , node_id : usize , x : f64 , y : f64 ) {
2022- let ( current, max) = self . node_scroll_state ( node_id) ;
2023- let target_x = x. clamp ( 0.0 , max. x ) ;
2024- let target_y = y. clamp ( 0.0 , max. y ) ;
2025-
2026- // `scroll_node_by` works in deltas (subtracted from the current offset), so pass
2027- // `current - target` to land on `target`. Because the target is pre-clamped to the
2028- // scrollable range, no scroll is bubbled up to ancestors.
2029- self . scroll_node_by (
2030- node_id,
2031- current. x - target_x,
2032- current. y - target_y,
2033- & mut |_| { } ,
2034- ) ;
2032+ /// Scroll an element by the given relative offset in CSS pixels.
2033+ pub fn scroll_by ( & mut self , node_id : usize , x : f64 , y : f64 , behavior : ScrollBehavior ) {
2034+ let current = self . node_scroll_state ( node_id) . 0 ;
2035+ self . scroll_to ( node_id, current. x + x, current. y + y, behavior) ;
20352036 }
20362037
2037- /// Like [`BaseDocument::scroll_node_to`], but animates towards the offset instead of
2038- /// jumping instantly.
2039- pub fn scroll_node_to_smooth ( & mut self , node_id : usize , x : f64 , y : f64 ) {
2040- let ( _, max) = self . node_scroll_state ( node_id) ;
2041- self . start_scroll_animation (
2042- Some ( node_id) ,
2043- crate :: Point {
2044- x : x. clamp ( 0.0 , max. x ) ,
2045- y : y. clamp ( 0.0 , max. y ) ,
2046- } ,
2047- ) ;
2038+ /// Scroll the viewport so that the given element is aligned with its top-left edge.
2039+ pub fn scroll_into_view ( & mut self , node_id : usize , behavior : ScrollBehavior ) {
2040+ let Some ( target) = self
2041+ . nodes
2042+ . get ( node_id)
2043+ . map ( |node| node. absolute_position ( 0.0 , 0.0 ) )
2044+ else {
2045+ return ;
2046+ } ;
2047+ let Some ( root_id) = self . try_root_element ( ) . map ( |root| root. id ) else {
2048+ return ;
2049+ } ;
2050+ self . scroll_to ( root_id, target. x as f64 , target. y as f64 , behavior) ;
20482051 }
20492052
20502053 /// Resolve a URL fragment (the `#...` part of a URL) to a scroll target.
@@ -2074,40 +2077,38 @@ impl BaseDocument {
20742077 None
20752078 }
20762079
2077- /// Scroll to the element targeted by the given URL fragment (the `#...` part of a URL).
2078- ///
2079- /// An empty fragment (or a `top` fragment that matches no element) scrolls to the top
2080- /// of the document, matching browser behaviour. Returns `true` if a scroll target was
2081- /// found.
2082- pub fn scroll_to_fragment ( & mut self , fragment : & str ) -> bool {
2080+ fn scroll_to_fragment_with_behavior (
2081+ & mut self ,
2082+ fragment : & str ,
2083+ behavior : ScrollBehavior ,
2084+ ) -> bool {
20832085 match self . resolve_fragment_scroll_target ( fragment) {
20842086 Some ( Some ( node_id) ) => {
2085- self . scroll_to_node ( node_id) ;
2087+ self . scroll_into_view ( node_id, behavior ) ;
20862088 true
20872089 }
20882090 Some ( None ) => {
2089- let current = self . viewport_scroll ;
2090- self . scroll_viewport_by ( current . x , current . y ) ;
2091+ let root_id = self . root_element ( ) . id ;
2092+ self . scroll_to ( root_id , 0.0 , 0.0 , behavior ) ;
20912093 true
20922094 }
20932095 None => false ,
20942096 }
20952097 }
20962098
2099+ /// Scroll to the element targeted by the given URL fragment (the `#...` part of a URL).
2100+ ///
2101+ /// An empty fragment (or a `top` fragment that matches no element) scrolls to the top
2102+ /// of the document, matching browser behaviour. Returns `true` if a scroll target was
2103+ /// found.
2104+ pub fn scroll_to_fragment ( & mut self , fragment : & str ) -> bool {
2105+ self . scroll_to_fragment_with_behavior ( fragment, ScrollBehavior :: Auto )
2106+ }
2107+
20972108 /// Like [`BaseDocument::scroll_to_fragment`], but animates the viewport towards the
20982109 /// target instead of jumping instantly. Returns `true` if a scroll target was found.
20992110 pub fn scroll_to_fragment_smooth ( & mut self , fragment : & str ) -> bool {
2100- match self . resolve_fragment_scroll_target ( fragment) {
2101- Some ( Some ( node_id) ) => {
2102- self . scroll_to_node_smooth ( node_id) ;
2103- true
2104- }
2105- Some ( None ) => {
2106- self . start_scroll_animation ( None , crate :: Point :: ZERO ) ;
2107- true
2108- }
2109- None => false ,
2110- }
2111+ self . scroll_to_fragment_with_behavior ( fragment, ScrollBehavior :: Smooth )
21112112 }
21122113
21132114 /// Computes the size and position of the `Node` relative to the viewport
0 commit comments