Skip to content

Commit

Permalink
Introduce useScrollWhenDragging hook
Browse files Browse the repository at this point in the history
  • Loading branch information
fluiddot committed Mar 23, 2022
1 parent ff1b9dd commit a362614
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import useScrollWhenDragging from './use-scroll-when-dragging';
import DraggableChip from './draggable-chip';
import { store as blockEditorStore } from '../../store';
import { useBlockListContext } from '../block-list/block-list-context';
Expand Down Expand Up @@ -64,10 +65,19 @@ const BlockDraggableWrapper = ( { children } ) => {
const isDragging = useSharedValue( false );
const scrollAnimation = useSharedValue( 0 );

const [
startScrolling,
scrollOnDragOver,
stopScrolling,
draggingScrollHandler,
] = useScrollWhenDragging();

const scrollHandler = ( event ) => {
'worklet';
const { contentOffset } = event;
scroll.offsetY.value = contentOffset.y;

draggingScrollHandler( event );
};

// Stop dragging blocks if the block draggable is unmounted.
Expand Down Expand Up @@ -100,9 +110,13 @@ const BlockDraggableWrapper = ( { children } ) => {
( scroll.offsetY.value - blockLayout.y ) -
EXTRA_OFFSET_WHEN_CLOSE_TO_TOP_EDGE
);
scrollAnimation.value = withTiming( scrollOffsetTarget, {
duration: SCROLL_ANIMATION_DURATION,
} );
scrollAnimation.value = withTiming(
scrollOffsetTarget,
{ duration: SCROLL_ANIMATION_DURATION },
() => startScrolling( position.y )
);
} else {
runOnUI( startScrolling )( position.y );
}
}
};
Expand Down Expand Up @@ -150,6 +164,9 @@ const BlockDraggableWrapper = ( { children } ) => {

chip.x.value = dragPosition.x;
chip.y.value = dragPosition.y;

// Update scrolling velocity
scrollOnDragOver( dragPosition.y );
};

const stopDragging = () => {
Expand All @@ -158,6 +175,7 @@ const BlockDraggableWrapper = ( { children } ) => {

chip.scale.value = withTiming( 0 );
runOnJS( stopDraggingBlocks )();
stopScrolling();
};

const chipStyles = useAnimatedStyle( () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* External dependencies
*/
import { Dimensions } from 'react-native';
import {
useSharedValue,
useAnimatedRef,
scrollTo,
useAnimatedReaction,
withTiming,
withRepeat,
cancelAnimation,
Easing,
} from 'react-native-reanimated';

/**
* Internal dependencies
*/
import { useBlockListContext } from '../block-list/block-list-context';

const SCROLL_INACTIVE_DISTANCE_PX = 50;
const SCROLL_INTERVAL_MS = 1000;
const VELOCITY_MULTIPLIER = 5000;

export default function useScrollWhenDragging() {
const { scrollRef } = useBlockListContext();
const animatedScrollRef = useAnimatedRef();
animatedScrollRef( scrollRef );

const windowHeight = Dimensions.get( 'window' ).height;

const velocityY = useSharedValue( 0 );
const offsetY = useSharedValue( 0 );
const dragStartY = useSharedValue( 0 );
const animationTimer = useSharedValue( 0 );
const isAnimationTimerActive = useSharedValue( false );
const isScrollActive = useSharedValue( false );

const scroll = {
offsetY: useSharedValue( 0 ),
maxOffsetY: useSharedValue( 0 ),
};
const scrollHandler = ( event ) => {
'worklet';
const { contentSize, contentOffset, layoutMeasurement } = event;
scroll.offsetY.value = contentOffset.y;
scroll.maxOffsetY.value = contentSize.height - layoutMeasurement.height;
};

const stopScrolling = () => {
'worklet';
isAnimationTimerActive.value = false;
isScrollActive.value = false;
velocityY.value = 0;
};

const startScrolling = ( y ) => {
'worklet';
stopScrolling();
offsetY.value = scroll.offsetY.value;
dragStartY.value = y;

animationTimer.value = 0;
animationTimer.value = withRepeat(
withTiming( 1, {
duration: SCROLL_INTERVAL_MS,
easing: Easing.linear,
} ),
-1,
true
);
isAnimationTimerActive.value = true;
};

const scrollOnDragOver = ( y ) => {
'worklet';
const dragDistance = Math.max(
Math.abs( y - dragStartY.value ) - SCROLL_INACTIVE_DISTANCE_PX,
0
);
const distancePercentage = dragDistance / windowHeight;

if ( ! isScrollActive.value ) {
isScrollActive.value = dragDistance > 0;
} else if ( y > dragStartY.value ) {
// User is dragging downwards.
velocityY.value = VELOCITY_MULTIPLIER * distancePercentage;
} else if ( y < dragStartY.value ) {
// User is dragging upwards.
velocityY.value = -VELOCITY_MULTIPLIER * distancePercentage;
} else {
velocityY.value = 0;
}
};

useAnimatedReaction(
() => animationTimer.value,
( value, previous ) => {
const delta = Math.abs( value - previous );
let newOffset = offsetY.value + delta * velocityY.value;

if ( scroll.maxOffsetY.value !== 0 ) {
newOffset = Math.max(
0,
Math.min( scroll.maxOffsetY.value, newOffset )
);
} else {
// Scroll values are empty until receiving the first scroll event.
// In that case, the max offset is unknown and we can't clamp the
// new offset value.
newOffset = Math.max( 0, newOffset );
}
offsetY.value = newOffset;

if ( velocityY.value !== 0 ) {
scrollTo( animatedScrollRef, 0, offsetY.value, false );
} else if ( ! isAnimationTimerActive.value ) {
cancelAnimation( animationTimer );
}
}
);

return [ startScrolling, scrollOnDragOver, stopScrolling, scrollHandler ];
}

0 comments on commit a362614

Please sign in to comment.