@@ -6,8 +6,9 @@ import { PickerItemOption } from './type';
66const { prefix } = config ;
77const name = `${ prefix } -picker-item` ;
88
9- // 动画持续时间
10- const ANIMATION_DURATION = 1000 ;
9+ // 动画持续时间(优化:根据滑动距离动态计算,基础时长降低)
10+ const ANIMATION_DURATION_BASE = 300 ; // 基础动画时长
11+ const ANIMATION_DURATION_MAX = 600 ; // 最大动画时长
1112// 和上一次move事件间隔小于INERTIA_TIME
1213const INERTIA_TIME = 300 ;
1314// 且距离大于`MOMENTUM_DISTANCE`时,执行惯性滚动
@@ -93,18 +94,10 @@ export default class PickerItem extends SuperComponent {
9394 this . StartY = 0 ;
9495 this . StartOffset = 0 ;
9596 this . startTime = 0 ;
96- this . _moveTimer = null ;
9797 this . _animationTimer = null ; // 动画期间更新虚拟滚动的定时器
98- this . _lastOffset = 0 ; // 上一次的偏移量(用于计算滑动速度)
99- this . _lastMoveTime = 0 ; // 上一次移动的时间
100- this . _scrollDirection = 0 ; // 滑动方向:1向下,-1向上,0静止
10198 } ,
10299 detached ( ) {
103100 // 清理定时器,防止内存泄漏
104- if ( this . _moveTimer ) {
105- clearTimeout ( this . _moveTimer ) ;
106- this . _moveTimer = null ;
107- }
108101 if ( this . _animationTimer ) {
109102 clearInterval ( this . _animationTimer ) ;
110103 this . _animationTimer = null ;
@@ -136,61 +129,19 @@ export default class PickerItem extends SuperComponent {
136129
137130 onTouchMove ( event ) {
138131 const { StartY, StartOffset } = this ;
139- const { itemHeight, enableVirtualScroll } = this . data ;
140- const currentTime = Date . now ( ) ;
132+ const { itemHeight } = this . data ;
141133
142134 // 偏移增量
143135 const deltaY = event . touches [ 0 ] . clientY - StartY ;
144- const newOffset = range ( StartOffset + deltaY , - ( this . getCount ( ) * itemHeight ) , 0 ) ;
145-
146- // 计算滑动速度和方向
147- const offsetDelta = newOffset - this . _lastOffset ;
148- const timeDelta = currentTime - this . _lastMoveTime || 16 ;
149- const scrollSpeed = Math . abs ( offsetDelta / timeDelta ) * 16 ; // 转换为 px/frame
150-
151- // 计算滑动方向(避免嵌套三元表达式)
152- if ( offsetDelta > 0 ) {
153- this . _scrollDirection = 1 ; // 向下滑动
154- } else if ( offsetDelta < 0 ) {
155- this . _scrollDirection = - 1 ; // 向上滑动
156- } else {
157- this . _scrollDirection = 0 ; // 静止
158- }
159-
160- // 判断是否为快速滑动
161- const isFastScroll = scrollSpeed > VIRTUAL_SCROLL_CONFIG . FAST_SCROLL_THRESHOLD ;
162-
163- // 优化节流策略:快速滑动时立即更新,慢速滑动时节流
164- if ( ! this . _moveTimer || isFastScroll ) {
165- if ( this . _moveTimer ) {
166- clearTimeout ( this . _moveTimer ) ;
167- this . _moveTimer = null ;
168- }
136+ const newOffset = range ( StartOffset + deltaY , - ( this . getCount ( ) - 1 ) * itemHeight , 0 ) ;
169137
170- this . setData ( { offset : newOffset } ) ;
171-
172- // 虚拟滚动:更新可见范围(快速滑动时使用更大的缓冲区)
173- if ( enableVirtualScroll ) {
174- this . updateVisibleOptions ( newOffset , isFastScroll ) ;
175- }
176-
177- this . _moveTimer = setTimeout ( ( ) => {
178- this . _moveTimer = null ;
179- } , VIRTUAL_SCROLL_CONFIG . THROTTLE_TIME ) ;
180- }
181-
182- // 记录当前状态
183- this . _lastOffset = newOffset ;
184- this . _lastMoveTime = currentTime ;
138+ // touchMove 期间只更新 offset,不更新虚拟滚动数据
139+ // 虚拟滚动数据在 touchEnd 时统一更新,避免频繁 setData 导致掉帧
140+ this . setData ( { offset : newOffset } ) ;
185141 } ,
186142
187143 onTouchEnd ( event ) {
188- if ( this . _moveTimer ) {
189- clearTimeout ( this . _moveTimer ) ;
190- this . _moveTimer = null ;
191- }
192-
193- const { offset, itemHeight } = this . data ;
144+ const { offset, itemHeight, enableVirtualScroll, formatOptions } = this . data ;
194145 const { startTime } = this ;
195146 if ( offset === this . StartOffset ) {
196147 return ;
@@ -207,63 +158,64 @@ export default class PickerItem extends SuperComponent {
207158 // 调整偏移量
208159 const newOffset = range ( offset + distance , - this . getCount ( ) * itemHeight , 0 ) ;
209160 const index = range ( Math . round ( - newOffset / itemHeight ) , 0 , this . getCount ( ) - 1 ) ;
161+ const finalOffset = - index * itemHeight ;
162+
163+ // 动态计算动画时长:根据滑动距离调整,距离越大时长越长,但有上限
164+ const scrollDistance = Math . abs ( finalOffset - offset ) ;
165+ const scrollItems = scrollDistance / itemHeight ;
166+ const animationDuration = Math . min (
167+ ANIMATION_DURATION_MAX ,
168+ ANIMATION_DURATION_BASE + scrollItems * 30 , // 每滑动一个选项增加30ms
169+ ) ;
210170
211- // 判断是否为快速惯性滚动
171+ // 判断是否为快速惯性滚动(用于决定缓冲区大小)
212172 const isFastInertia = Math . abs ( distance ) > itemHeight * 3 ;
213-
214- // 立即更新虚拟滚动视图(修复惯性滚动后空白问题,快速滚动时使用更大缓冲区)
215- if ( this . data . enableVirtualScroll ) {
216- this . updateVisibleOptions ( - index * itemHeight , isFastInertia ) ;
217- }
173+ // 根据是否快速惯性滚动选择缓冲区大小
174+ const bufferCount = isFastInertia ? VIRTUAL_SCROLL_CONFIG . FAST_SCROLL_BUFFER : VIRTUAL_SCROLL_CONFIG . BUFFER_COUNT ;
218175
219176 // 清除之前的动画更新定时器
220177 if ( this . _animationTimer ) {
221178 clearInterval ( this . _animationTimer ) ;
222179 this . _animationTimer = null ;
223180 }
224181
225- // 在动画执行期间定期更新虚拟滚动视图(确保动画过程流畅)
226- if ( this . data . enableVirtualScroll && Math . abs ( distance ) > 0 ) {
227- const startOffset = offset ;
228- const endOffset = - index * itemHeight ;
229- const startTime = Date . now ( ) ;
182+ // 性能优化:合并 setData 调用,一次性更新所有状态
183+ const updateData : any = {
184+ offset : finalOffset ,
185+ duration : animationDuration ,
186+ curIndex : index ,
187+ } ;
230188
231- this . _animationTimer = setInterval ( ( ) => {
232- const elapsed = Date . now ( ) - startTime ;
233- const progress = Math . min ( elapsed / ANIMATION_DURATION , 1 ) ;
189+ // 虚拟滚动:预先计算覆盖动画全程的可见范围,避免动画期间频繁更新
190+ if ( enableVirtualScroll ) {
191+ // 计算当前位置和目标位置的索引范围
192+ const currentIndex = Math . floor ( Math . abs ( offset ) / itemHeight ) ;
193+ const targetIndex = index ;
234194
235- // 使用缓动函数计算当前偏移量
236- const easeOutCubic = 1 - ( 1 - progress ) ** 3 ;
237- const currentOffset = startOffset + ( endOffset - startOffset ) * easeOutCubic ;
195+ // 计算覆盖动画全程的可见范围(从当前位置到目标位置)
196+ const minIndex = Math . min ( currentIndex , targetIndex ) ;
197+ const maxIndex = Math . max ( currentIndex , targetIndex ) ;
238198
239- // 快速惯性滚动时使用更大的缓冲区
240- this . updateVisibleOptions ( currentOffset , isFastInertia ) ;
199+ // 使用缓冲区扩展范围,确保动画过程中不会出现空白
200+ const animationStartIndex = Math . max ( 0 , minIndex - bufferCount ) ;
201+ const animationEndIndex = Math . min ( formatOptions . length , maxIndex + this . data . visibleItemCount + bufferCount ) ;
241202
242- if ( progress >= 1 ) {
243- clearInterval ( this . _animationTimer ) ;
244- this . _animationTimer = null ;
245- }
246- } , 16 ) ; // 约60fps
203+ updateData . visibleOptions = formatOptions . slice ( animationStartIndex , animationEndIndex ) ;
204+ updateData . virtualStartIndex = animationStartIndex ;
205+ updateData . virtualOffsetY = animationStartIndex * itemHeight ;
247206 }
248207
249- this . setData (
250- {
251- offset : - index * itemHeight ,
252- duration : ANIMATION_DURATION ,
253- curIndex : index ,
254- } ,
255- ( ) => {
256- // 动画结束后清除定时器并最终更新虚拟滚动视图
257- if ( this . _animationTimer ) {
258- clearInterval ( this . _animationTimer ) ;
259- this . _animationTimer = null ;
260- }
261- if ( this . data . enableVirtualScroll ) {
262- // 动画结束后使用正常缓冲区(不再是快速滚动状态)
263- this . updateVisibleOptions ( - index * itemHeight , false ) ;
264- }
265- } ,
266- ) ;
208+ this . setData ( updateData , ( ) => {
209+ // 动画结束后,精确更新虚拟滚动视图到最终位置
210+ if ( enableVirtualScroll ) {
211+ const visibleRange = this . computeVirtualRange ( finalOffset , formatOptions . length , itemHeight , false ) ;
212+ this . setData ( {
213+ visibleOptions : formatOptions . slice ( visibleRange . startIndex , visibleRange . endIndex ) ,
214+ virtualStartIndex : visibleRange . startIndex ,
215+ virtualOffsetY : visibleRange . startIndex * itemHeight ,
216+ } ) ;
217+ }
218+ } ) ;
267219
268220 if ( index === this . _selectedIndex ) return ;
269221 this . updateSelected ( index , true ) ;
@@ -355,21 +307,15 @@ export default class PickerItem extends SuperComponent {
355307 const { visibleItemCount } = this . data ;
356308
357309 // 根据滑动速度动态调整缓冲区大小
358- const dynamicBuffer = isFastScroll ? FAST_SCROLL_BUFFER : BUFFER_COUNT ;
359-
360- // 根据滑动方向调整缓冲区分配
361- // 向上滑动(_scrollDirection = -1):增加顶部缓冲区
362- // 向下滑动(_scrollDirection = 1):增加底部缓冲区
363- const topBuffer = this . _scrollDirection === - 1 ? dynamicBuffer + 2 : dynamicBuffer ;
364- const bottomBuffer = this . _scrollDirection === 1 ? dynamicBuffer + 2 : dynamicBuffer ;
310+ const bufferCount = isFastScroll ? FAST_SCROLL_BUFFER : BUFFER_COUNT ;
365311
366312 // 计算当前可见区域的中心索引
367313 const centerIndex = Math . floor ( scrollTop / itemHeight ) ;
368314
369- // 计算起始索引(减去顶部缓冲区 )
370- const startIndex = Math . max ( 0 , centerIndex - topBuffer ) ;
371- // 计算结束索引(加上可见数量和底部缓冲区 )
372- const endIndex = Math . min ( totalCount , centerIndex + visibleItemCount + bottomBuffer ) ;
315+ // 计算起始索引(减去缓冲区 )
316+ const startIndex = Math . max ( 0 , centerIndex - bufferCount ) ;
317+ // 计算结束索引(加上可见数量和缓冲区 )
318+ const endIndex = Math . min ( totalCount , centerIndex + visibleItemCount + bufferCount ) ;
373319
374320 return { startIndex, endIndex } ;
375321 } ,
0 commit comments