@@ -4,13 +4,17 @@ final class FLIPScheduler {
44 // NOTE: extend this to support css properties as well - for now it is always the bounding rect stuff
55 private var scheduledAnimations : [ DOM . Node : ScheduledNode ] = [ : ]
66 private var runningAnimations : [ DOM . Node : GeometryAnimation ] = [ : ]
7+ private var firstWindowScrollOffset : ( x: Double , y: Double ) ? = nil
78
89 init ( dom: any DOM . Interactor ) {
910 self . dom = dom
1011 }
1112
1213 func scheduleAnimationOf( _ nodes: [ DOM . Node ] , inParent parentNode: DOM . Node , context: inout _RenderContext ) {
13- logTrace ( " scheduling FLIP animations for \( nodes. count) nodes, animation: \( context. transaction. animation != nil ) " )
14+ if firstWindowScrollOffset == nil {
15+ storeWindowScrollOffset ( )
16+ }
17+
1418 let parentRect = dom. getBoundingClientRect ( parentNode)
1519 for node in nodes {
1620 guard !scheduledAnimations. keys. contains ( node) else {
@@ -27,6 +31,10 @@ final class FLIPScheduler {
2731 }
2832
2933 func scheduleAnimationOf( _ node: DOM . Node , context: inout _RenderContext ) {
34+ if firstWindowScrollOffset == nil {
35+ storeWindowScrollOffset ( )
36+ }
37+
3038 scheduledAnimations [ node] = ScheduledNode (
3139 transaction: context. transaction,
3240 geometry: getNodeGeometry ( node) ,
@@ -50,14 +58,26 @@ final class FLIPScheduler {
5058 }
5159
5260 func commitScheduledAnimations( context: inout _CommitContext ) {
61+ let scroll = dom. getScrollOffset ( )
62+ let firstWindowScroll = firstWindowScrollOffset ?? ( x: Double ( scroll. x) , y: Double ( scroll. y) )
63+ self . firstWindowScrollOffset = nil
5364
5465 commitPreMeasurementChanges ( context: & context)
5566
56- measureLastAndCreateAnimations ( context: & context)
67+ let lastScroll = dom. getScrollOffset ( )
68+ let lastWindowScroll = ( x: Double ( lastScroll. x) , y: Double ( lastScroll. y) )
69+ let windowScrollDelta = ( x: lastWindowScroll. x - firstWindowScroll. x, y: lastWindowScroll. y - firstWindowScroll. y)
70+
71+ measureLastAndCreateAnimations ( windowScrollDelta: windowScrollDelta, context: & context)
5772
5873 progressAllAnimations ( context: & context)
5974 }
6075
76+ private func storeWindowScrollOffset( ) {
77+ let scroll = dom. getScrollOffset ( )
78+ self . firstWindowScrollOffset = ( x: Double ( scroll. x) , y: Double ( scroll. y) )
79+ }
80+
6181 private func commitPreMeasurementChanges( context: inout _CommitContext ) {
6282
6383 for (node, animation) in scheduledAnimations {
@@ -70,38 +90,14 @@ final class FLIPScheduler {
7090 case . none:
7191 continue
7292 case . moveAbsolute( let rect) :
73- // Extract current style values before setting new ones
74- let stylePosition = context. dom. makeStyleAccessor ( node, cssName: " position " )
75- let styleLeft = context. dom. makeStyleAccessor ( node, cssName: " left " )
76- let styleTop = context. dom. makeStyleAccessor ( node, cssName: " top " )
77- let styleWidth = context. dom. makeStyleAccessor ( node, cssName: " width " )
78- let styleHeight = context. dom. makeStyleAccessor ( node, cssName: " height " )
79-
80- // Extract previous style values for later reversal
81- let previousValues = PreviousStyleValues (
82- position: stylePosition. get ( ) ,
83- left: styleLeft. get ( ) ,
84- top: styleTop. get ( ) ,
85- width: styleWidth. get ( ) ,
86- height: styleHeight. get ( )
87- )
93+ let previousValues = context. dom. fixAbsolutePosition ( node, toRect: rect)
8894 // TODO: store previousValues for reversal when animation completes
8995 _ = previousValues
90-
91- logTrace (
92- " setting position of node \( node) to absolute, left: \( rect. x) px, top: \( rect. y) px, width: \( rect. width) px, height: \( rect. height) px "
93- )
94-
95- stylePosition. set ( " absolute " )
96- styleLeft. set ( " \( rect. x) px " )
97- styleTop. set ( " \( rect. y) px " )
98- styleWidth. set ( " \( rect. width) px " )
99- styleHeight. set ( " \( rect. height) px " )
10096 }
10197 }
10298 }
10399
104- private func measureLastAndCreateAnimations( context: inout _CommitContext ) {
100+ private func measureLastAndCreateAnimations( windowScrollDelta : ( x : Double , y : Double ) , context: inout _CommitContext ) {
105101 // measures all last states and calculates all new animations
106102
107103 // parent rect cache
@@ -130,6 +126,7 @@ final class FLIPScheduler {
130126 node: node,
131127 first: animation. geometry,
132128 last: lastGeometry,
129+ windowScrollDelta: windowScrollDelta,
133130 transaction: animation. transaction,
134131 frameTime: context. currentFrameTime
135132 )
@@ -138,7 +135,7 @@ final class FLIPScheduler {
138135 scheduledAnimations. removeAll ( )
139136 }
140137
141- func progressAllAnimations( context: inout _CommitContext ) {
138+ private func progressAllAnimations( context: inout _CommitContext ) {
142139 // applies all changes of dirty animations and removes completed ones
143140 // TODO: optimize
144141 var removedNodes : [ DOM . Node ] = [ ]
@@ -186,11 +183,24 @@ private extension FLIPScheduler {
186183 var width : Double
187184 var height : Double
188185
189- var scopedCoordinates : ( x: Double , y: Double ) {
190- if let parentRect = parentRect {
191- return ( x: boundingClientRect. x - parentRect. x, y: boundingClientRect. y - parentRect. y)
186+ func difference(
187+ from other: NodeGeometry ,
188+ scrollDelta: ( x: Double , y: Double )
189+ ) -> ( x: Double , y: Double , width: Double , height: Double ) {
190+ if let parentRect = parentRect, let otherParentRect = other. parentRect {
191+ return (
192+ x: self . boundingClientRect. x - other. boundingClientRect. x - parentRect. x + otherParentRect. x,
193+ y: self . boundingClientRect. y - other. boundingClientRect. y - parentRect. y + otherParentRect. y,
194+ width: width - other. width,
195+ height: height - other. height
196+ )
192197 } else {
193- return ( x: boundingClientRect. x, y: boundingClientRect. y)
198+ return (
199+ x: self . boundingClientRect. x - other. boundingClientRect. x - scrollDelta. x,
200+ y: self . boundingClientRect. y - other. boundingClientRect. y - scrollDelta. y,
201+ width: width - other. width,
202+ height: height - other. height
203+ )
194204 }
195205 }
196206 // NOTE: extend with transform/rotate or other stuff
@@ -205,13 +215,15 @@ private extension FLIPScheduler {
205215 translation == nil && width == nil && height == nil
206216 }
207217
208- init ( node: DOM . Node , first: NodeGeometry , last: NodeGeometry , transaction: Transaction , frameTime: Double ) {
209- let firstCoordinates = first. scopedCoordinates
210- let lastCoordinates = last. scopedCoordinates
211- let dx = firstCoordinates. x - lastCoordinates. x
212- let dy = firstCoordinates. y - lastCoordinates. y
213- let dw = last. width - first. width
214- let dh = last. height - first. height
218+ init (
219+ node: DOM . Node ,
220+ first: NodeGeometry ,
221+ last: NodeGeometry ,
222+ windowScrollDelta: ( x: Double , y: Double ) ,
223+ transaction: Transaction ,
224+ frameTime: Double
225+ ) {
226+ let ( dx, dy, dw, dh) = first. difference ( from: last, scrollDelta: windowScrollDelta)
215227
216228 // TODO: implement scale animation as option
217229
@@ -280,18 +292,6 @@ fileprivate extension FLIPScheduler {
280292 func getNodeGeometry( _ node: DOM . Node , scopedTo parentRect: DOM . Rect ? = nil ) -> NodeGeometry {
281293 let rect = dom. getBoundingClientRect ( node)
282294
283- // TODO: figure out how to do this in embedded
284- // let computedStyle = dom.makeComputedStyleAccessor(node)
285-
286- // // Parse width and height from computed styles (e.g., "100px" -> 100.0)
287- // let widthString = computedStyle.get("width")
288- // let heightString = computedStyle.get("height")
289-
290- // logTrace("width: \(widthString), height: \(heightString)")
291-
292- // let width = parseCSSLength(widthString) ?? rect.width
293- // let height = parseCSSLength(heightString) ?? rect.height
294-
295295 let width = rect. width
296296 let height = rect. height
297297
@@ -302,18 +302,6 @@ fileprivate extension FLIPScheduler {
302302 height: height
303303 )
304304 }
305-
306- func parseCSSLength( _ value: String ) -> Double ? {
307- nil
308-
309- // guard !value.isEmpty, value != "auto" else { return nil }
310- // // Remove "px" suffix and parse as Double
311- // if value.hasSuffix("px") {
312- // let numberString = String(value.dropLast(2))
313- // return Double(numberString)
314- // }
315- // return nil
316- }
317305}
318306
319307private func shouldAnimateSizeDelta( _ ds: Double ) -> Bool {
@@ -344,3 +332,34 @@ extension DOM.Interactor {
344332 return nodeRect
345333 }
346334}
335+
336+ private extension DOM . Interactor {
337+ func fixAbsolutePosition( _ node: DOM . Node , toRect rect: DOM . Rect ) -> FLIPScheduler . PreviousStyleValues {
338+ let stylePosition = makeStyleAccessor ( node, cssName: " position " )
339+ let styleLeft = makeStyleAccessor ( node, cssName: " left " )
340+ let styleTop = makeStyleAccessor ( node, cssName: " top " )
341+ let styleWidth = makeStyleAccessor ( node, cssName: " width " )
342+ let styleHeight = makeStyleAccessor ( node, cssName: " height " )
343+
344+ // Extract previous style values for later reversal
345+ let previousValues = FLIPScheduler . PreviousStyleValues (
346+ position: stylePosition. get ( ) ,
347+ left: styleLeft. get ( ) ,
348+ top: styleTop. get ( ) ,
349+ width: styleWidth. get ( ) ,
350+ height: styleHeight. get ( )
351+ )
352+
353+ logTrace (
354+ " setting position of node \( node) to absolute, left: \( rect. x) px, top: \( rect. y) px, width: \( rect. width) px, height: \( rect. height) px "
355+ )
356+
357+ stylePosition. set ( " absolute " )
358+ styleLeft. set ( " \( rect. x) px " )
359+ styleTop. set ( " \( rect. y) px " )
360+ styleWidth. set ( " \( rect. width) px " )
361+ styleHeight. set ( " \( rect. height) px " )
362+
363+ return previousValues
364+ }
365+ }
0 commit comments