Open patchthecode opened 1 year ago
Here's my workaround based on a bit of testing and tweaking - you might have to adjust it to your needs. It works great for my purposes, even feels a bit "bouncy".
<template>
<recycle-scroller :items="items" key-field="i" size-field="scroll_height" :buffer="0" :emit-update="true" @update="scroller_updated" ref="commits_scroller_ref" tabindex="-1"/>
</template>
<script>
// ...
let scroll_item_offset = 0
let debouncer = 0
let ignore_next_scroll_event = false
const commits_scroller_updated = (start_index, end_index) => {
if(ignore_next_scroll_event) {
ignore_next_scroll_event = false
return
}
window.clearTimeout(debouncer)
debouncer = window.setTimeout(() => {
// +1 because vue-virtual-scroller's start index is always one too far up it seems, but
// check for !=0 because when we're at the very top, we want to stay there.
// And once we have scrolled, we need to go to at least index 3 because
// `scroller.scrollToItem(2)` actually results in the scroller to jump to 0 again.
// Not sure if these tweaks (+1, !=0 and 3) are row height or container dependent, so
// if it doesn't work for you, you'll have to tweak this line.
const new_scroll_item_offset = start_index > 0 ? Math.max(3, start_index + 1) : 0
// To avoid jumping back and forth while keeping the scroll bar pressed down with mouse:
if(new_scroll_item_offset === scroll_item_offset)
return
scroll_item_offset = new_scroll_item_offset
commits_scroller_ref.value?.scrollToItem(scroll_item_offset)
// To avoid endless loops:
ignore_next_scroll_event = true
}, 70)
}
Here's my solution, only tested in horizontal mode
export default function scrollSnap () {
let element: HTMLElement
const start = {
scrollY: 0,
scrollX: 0,
touchX: 0,
touchY: 0,
time: 0,
}
let scrollingDirection: null | 'vertical' | 'horizontal' = null
function onTouchStart (event: TouchEvent) {
start.touchX = event.touches[0].clientX
start.touchY = event.touches[0].clientY
start.scrollX = element.scrollLeft
start.scrollY = element.scrollTop
start.time = Date.now()
scrollingDirection = null
}
function onTouchMove (event: TouchEvent) {
const touchY = event.touches[0].pageY
const touchX = event.touches[0].pageX
const distanceY = start.touchY - touchY
const distanceX = start.touchX - touchX
if (scrollingDirection === null) {
scrollingDirection = Math.abs(distanceX) > Math.abs(distanceY) ? 'horizontal' : 'vertical'
}
if (Math.abs(distanceX) > Math.abs(distanceY)) {
event.preventDefault()
}
if (scrollingDirection === 'horizontal') {
event.preventDefault()
element.scrollLeft = start.scrollX + distanceX
} else if (scrollingDirection === 'vertical') {
// event.preventDefault()
element.scrollTop = start.scrollY + distanceY
}
}
function onTouchEnd (event: TouchEvent) {
const elapsedTime = Date.now() - start.time
// Vertical velocity calculation
const distanceY = element.scrollTop - start.scrollY
const velocityY = distanceY / elapsedTime
// Multiplier to control the effect of the swipe velocity on scrolling
const multiplier = 150
// Predicted end position for vertical scroll
const predictedEndY = element.scrollTop + (velocityY * multiplier)
// Horizontal snap logic
const distanceX = element.scrollLeft - start.scrollX
const velocityX = distanceX / elapsedTime
const thresholdVelocity = 0.5
let targetPositionX
if (Math.abs(velocityX) > thresholdVelocity) {
targetPositionX = velocityX > 0 ? Math.ceil(element.scrollLeft / element.clientWidth) : Math.floor(element.scrollLeft / element.clientWidth)
} else {
targetPositionX = Math.round(element.scrollLeft / element.clientWidth)
}
element.scrollTo({
top: predictedEndY,
left: targetPositionX * element.clientWidth,
behavior: 'smooth',
})
scrollingDirection = null
}
function bind (el: HTMLElement) {
element = el
element.addEventListener('touchstart', onTouchStart)
element.addEventListener('touchmove', onTouchMove)
element.addEventListener('touchend', onTouchEnd)
}
function unbind (element: HTMLElement) {
element.removeEventListener('touchstart', onTouchStart)
element.removeEventListener('touchmove', onTouchMove)
element.removeEventListener('touchend', onTouchEnd)
}
return {
bind,
unbind,
}
}
Excuse me, have you found a good solution so far?
I may need this feature at the moment, but the virtual window has caused a benchmark change in CSS, making it difficult to implement.
I tried the methods of the two above, but they did not achieve the expected results.
Clear and concise description of the problem
When we scroll there is no snapping with this library. This lets the list scroll until decelerated, but many times for visual purposes, it would be nice that it decelerates with the object in the center of the screen.
Suggested solution
Im not sure how to implement this with this library, but with regular html and css, its as simple as giving the parent div the
and giving the child div
Alternative
No response
Additional context
related issue. but dead.. no replies or comments for years... https://github.com/Akryum/vue-virtual-scroller/issues/543
Validations