mirror of
https://github.com/immich-app/immich.git
synced 2025-07-07 10:14:08 -04:00
Fix jitter
This commit is contained in:
parent
344c695b52
commit
6b0dcfffb8
@ -165,7 +165,13 @@
|
|||||||
const updateIsScrolling = () => (assetStore.scrolling = true);
|
const updateIsScrolling = () => (assetStore.scrolling = true);
|
||||||
// note: don't throttle, debounch, or otherwise do this function async - it causes flicker
|
// note: don't throttle, debounch, or otherwise do this function async - it causes flicker
|
||||||
const updateSlidingWindow = () => assetStore.updateSlidingWindow(element?.scrollTop || 0);
|
const updateSlidingWindow = () => assetStore.updateSlidingWindow(element?.scrollTop || 0);
|
||||||
const compensateScrollCallback = (delta: number) => element?.scrollBy(0, delta);
|
const compensateScrollCallback = ({ delta, top }: { delta?: number; top?: number }) => {
|
||||||
|
if (delta) {
|
||||||
|
element?.scrollBy(0, delta);
|
||||||
|
} else if (top) {
|
||||||
|
element?.scrollTo({ top });
|
||||||
|
}
|
||||||
|
};
|
||||||
const topSectionResizeObserver: OnResizeCallback = ({ height }) => (assetStore.topSectionHeight = height);
|
const topSectionResizeObserver: OnResizeCallback = ({ height }) => (assetStore.topSectionHeight = height);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { formatDateGroupTitle, fromLocalDateTime } from '$lib/utils/timeline-util';
|
import { formatDateGroupTitle, fromLocalDateTime } from '$lib/utils/timeline-util';
|
||||||
import { TUNABLES } from '$lib/utils/tunables';
|
import { TUNABLES } from '$lib/utils/tunables';
|
||||||
import { getAssetInfo, getTimeBucket, getTimeBuckets, TimeBucketSize, type AssetResponseDto } from '@immich/sdk';
|
import { getAssetInfo, getTimeBucket, getTimeBuckets, TimeBucketSize, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { debounce, isEqual, throttle } from 'lodash-es';
|
import { clamp, debounce, isEqual, throttle } from 'lodash-es';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
@ -218,6 +218,7 @@ export type ViewportXY = Viewport & {
|
|||||||
export class AssetBucket {
|
export class AssetBucket {
|
||||||
// --- public ---
|
// --- public ---
|
||||||
#intersecting: boolean = $state(false);
|
#intersecting: boolean = $state(false);
|
||||||
|
actuallyIntersecting: boolean = $state(false);
|
||||||
isLoaded: boolean = $state(false);
|
isLoaded: boolean = $state(false);
|
||||||
dateGroups: AssetDateGroup[] = $state([]);
|
dateGroups: AssetDateGroup[] = $state([]);
|
||||||
readonly store: AssetStore;
|
readonly store: AssetStore;
|
||||||
@ -233,6 +234,7 @@ export class AssetBucket {
|
|||||||
#top: number = $state(0);
|
#top: number = $state(0);
|
||||||
#initialCount: number = 0;
|
#initialCount: number = 0;
|
||||||
|
|
||||||
|
percent: number = $state(0);
|
||||||
// --- should be private, but is used by AssetStore ---
|
// --- should be private, but is used by AssetStore ---
|
||||||
|
|
||||||
bucketCount: number = $derived(
|
bucketCount: number = $derived(
|
||||||
@ -423,7 +425,7 @@ export class AssetBucket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set bucketHeight(height: number) {
|
set bucketHeight(height: number) {
|
||||||
const { store } = this;
|
const { store, percent } = this;
|
||||||
const index = store.buckets.indexOf(this);
|
const index = store.buckets.indexOf(this);
|
||||||
const bucketHeightDelta = height - this.#bucketHeight;
|
const bucketHeightDelta = height - this.#bucketHeight;
|
||||||
const prevBucket = store.buckets[index - 1];
|
const prevBucket = store.buckets[index - 1];
|
||||||
@ -444,8 +446,14 @@ export class AssetBucket {
|
|||||||
// if the bucket is 'before' the last intersecting bucket in the sliding window
|
// if the bucket is 'before' the last intersecting bucket in the sliding window
|
||||||
// then adjust the scroll position by the delta, to compensate for the bucket
|
// then adjust the scroll position by the delta, to compensate for the bucket
|
||||||
// size adjustment
|
// size adjustment
|
||||||
if (currentIndex > 0 && index <= currentIndex) {
|
if (currentIndex > 0) {
|
||||||
store.compensateScrollCallback?.(bucketHeightDelta);
|
if (index < currentIndex) {
|
||||||
|
store.compensateScrollCallback?.({ delta: bucketHeightDelta });
|
||||||
|
} else if (currentIndex == currentIndex) {
|
||||||
|
this.store.updateIntersections();
|
||||||
|
const top = this.#top + height * percent;
|
||||||
|
store.compensateScrollCallback?.({ top });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -539,7 +547,7 @@ export class AssetStore {
|
|||||||
scrubberTimelineHeight: number = $state(0);
|
scrubberTimelineHeight: number = $state(0);
|
||||||
|
|
||||||
// -- should be private, but used by AssetBucket
|
// -- should be private, but used by AssetBucket
|
||||||
compensateScrollCallback: ((delta: number) => void) | undefined;
|
compensateScrollCallback: (({ delta, top }: { delta?: number; top?: number }) => void) | undefined;
|
||||||
topIntersectingBucket: AssetBucket | undefined = $state();
|
topIntersectingBucket: AssetBucket | undefined = $state();
|
||||||
|
|
||||||
visibleWindow = $derived.by(() => ({
|
visibleWindow = $derived.by(() => ({
|
||||||
@ -704,29 +712,51 @@ export class AssetStore {
|
|||||||
let topIntersectingBucket = undefined;
|
let topIntersectingBucket = undefined;
|
||||||
for (const bucket of this.buckets) {
|
for (const bucket of this.buckets) {
|
||||||
this.#updateIntersection(bucket);
|
this.#updateIntersection(bucket);
|
||||||
if (!topIntersectingBucket && bucket.intersecting) {
|
if (!topIntersectingBucket && bucket.actuallyIntersecting) {
|
||||||
topIntersectingBucket = bucket;
|
topIntersectingBucket = bucket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.topIntersectingBucket !== topIntersectingBucket) {
|
if (this.topIntersectingBucket !== topIntersectingBucket) {
|
||||||
this.topIntersectingBucket = topIntersectingBucket;
|
this.topIntersectingBucket = topIntersectingBucket;
|
||||||
}
|
}
|
||||||
|
for (const bucket of this.buckets) {
|
||||||
|
if (bucket === this.topIntersectingBucket) {
|
||||||
|
this.topIntersectingBucket.percent = clamp(
|
||||||
|
(this.visibleWindow.top - this.topIntersectingBucket.top) / this.topIntersectingBucket.bucketHeight,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
bucket.percent = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateIntersection(bucket: AssetBucket) {
|
#calculateIntersecting(bucket: AssetBucket, expandTop: number, expandBottom: number) {
|
||||||
const bucketTop = bucket.top;
|
const bucketTop = bucket.top;
|
||||||
const bucketBottom = bucketTop + bucket.bucketHeight;
|
const bucketBottom = bucketTop + bucket.bucketHeight;
|
||||||
const topWindow = this.visibleWindow.top - INTERSECTION_EXPAND_TOP;
|
const topWindow = this.visibleWindow.top - expandTop;
|
||||||
const bottomWindow = this.visibleWindow.bottom + INTERSECTION_EXPAND_BOTTOM;
|
const bottomWindow = this.visibleWindow.bottom + expandBottom;
|
||||||
|
|
||||||
// a bucket intersections if
|
// a bucket intersections if
|
||||||
// 1) bucket's bottom is in the visible range -or-
|
// 1) bucket's bottom is in the visible range -or-
|
||||||
// 2) bucket's bottom is in the visible range -or-
|
// 2) bucket's bottom is in the visible range -or-
|
||||||
// 3) bucket's top is above visible range and bottom is below visible range
|
// 3) bucket's top is above visible range and bottom is below visible range
|
||||||
bucket.intersecting =
|
return (
|
||||||
(bucketTop >= topWindow && bucketTop < bottomWindow) ||
|
(bucketTop >= topWindow && bucketTop < bottomWindow) ||
|
||||||
(bucketBottom >= topWindow && bucketBottom < bottomWindow) ||
|
(bucketBottom >= topWindow && bucketBottom < bottomWindow) ||
|
||||||
(bucketTop < topWindow && bucketBottom >= bottomWindow);
|
(bucketTop < topWindow && bucketBottom >= bottomWindow)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateIntersection(bucket: AssetBucket) {
|
||||||
|
const actuallyIntersecting = this.#calculateIntersecting(bucket, 0, 0);
|
||||||
|
let preIntersecting = false;
|
||||||
|
if (!actuallyIntersecting) {
|
||||||
|
preIntersecting = this.#calculateIntersecting(bucket, INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM);
|
||||||
|
}
|
||||||
|
bucket.intersecting = actuallyIntersecting || preIntersecting;
|
||||||
|
bucket.actuallyIntersecting = actuallyIntersecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
#processPendingChanges = throttle(() => {
|
#processPendingChanges = throttle(() => {
|
||||||
@ -743,7 +773,7 @@ export class AssetStore {
|
|||||||
this.#pendingChanges = [];
|
this.#pendingChanges = [];
|
||||||
}, 2500);
|
}, 2500);
|
||||||
|
|
||||||
setCompensateScrollCallback(compensateScrollCallback?: (delta: number) => void) {
|
setCompensateScrollCallback(compensateScrollCallback?: ({ delta, top }: { delta?: number; top?: number }) => void) {
|
||||||
this.compensateScrollCallback = compensateScrollCallback;
|
this.compensateScrollCallback = compensateScrollCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user