Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set scroll velocity from drag distance #23082

Merged

Conversation

gravityrail
Copy link
Contributor

@gravityrail gravityrail commented Jun 10, 2020

Description

Current scrolling behaviour is somewhat unintuitive. The user needs to drag the item to the top of the viewport, and only then does scrolling begin. Also, the scroll placeholder is fully opaque. These two effects combine to make it hard to know where you're scrolling to, and to control scroll velocity.

This PR sets the vertical scroll velocity of the nearest suitable parent according to a function F of the vertical distance between the current mouse position and the Y position where the drag started.

It also sets the opacity of the drag placeholder to 0.7.

How has this been tested?

I just messed with it til it worked, sorry.

Screenshots

Video of previous behaviour: https://cloudup.com/cMcikA6icrw

Video of new behaviour: https://cloudup.com/c7UJlVgp5ry

Types of changes

Changes how scroll velocity reacts to cursor position while dragging.

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@gziolo gziolo requested a review from oandregal June 11, 2020 04:32
@gziolo gziolo added [Feature] Drag and Drop Drag and drop functionality when working with blocks [Type] Enhancement A suggestion for improvement. labels Jun 11, 2020
@gravityrail
Copy link
Contributor Author

btw I have done very little testing and zero cross browser testing, this PR should serve as inspiration rather than a complete solution. I would love for someone to put it through its paces. Happy to chip away at it if nobody else volunteers.

@ellatrix
Copy link
Member

ellatrix commented Jun 11, 2020

That's not a bad idea. I was initially hoping to just allow the user to scroll at least, because currently, you cannot scroll at all while you're dragging a block. What you're doing is essentially replacing mouse behaviour with scroll instead of move around the page. One problem I could see with this: if you already have the trackpad button pressed, and you wish to cover a distance greater than the distance between where you pressed and the edge of the trackpad, you cannot simply let go to restart scrolling from the opposite side, because you'd drop the block.

Maybe it's better to start small and at least allow the user to scroll the page while dragging.

@folletto
Copy link
Contributor

I'm not sure I get the trackpad issue. The new behaviour seems start moving when the mouse cursor moves from the initial position, and slows down when the cursor goes back to that initial position. So in theory the trackpad movement never reaches the edge. If anything, this scrolling behaviour seems requiring less trackpad space, as a higher velocity is reached in far less space then getting to the edge of the screen, no?

@ellatrix
Copy link
Member

ellatrix commented Jun 11, 2020

Ah, you are right, sorry about that! I had it playing out wrong in my head when thinking about possible problems. :)

I do like the interaction. What would happen if the block position is "live" updated in the block list?

@folletto
Copy link
Contributor

That's ok! That's why we discuss and cross check ❤️

I do like the interaction. What would happen if the block position is "live" updated in the block list?

I would fear it would jump around too much, and I would worry what happens if one moves a very large block. Maybe still worth a try to see how it feels?

@ellatrix
Copy link
Member

Yeah, I think I agree. I see this is what the Github project boards do, but these are much smaller items.

@mtias
Copy link
Member

mtias commented Jun 11, 2020

This looks good! It seems it could benefit from a larger area around the place where you drag where there is no scroll. From the video it seems like going back to the same position is tricky because it immediately starts scrolling the other way.

@gravityrail
Copy link
Contributor Author

@mtias good suggestion. I can add it to this PR if you like

@chrisvanpatten
Copy link
Member

This looks EXCELLENT. So so excited to see this — this has been a persistent micro-annoyance for some time :)

@gravityrail
Copy link
Contributor Author

@mtias I just added a 50px non-scrolling region around the drag start point. Let me know how this feels for you. I don't have experience with Gutenberg so I don't know the best way to:

  • get the first scrollable parent
  • adapt this for horizontal scrolling situations
  • do testing

Any pointers to people or examples that I could get this info from?

@gravityrail gravityrail added Good First Review A PR that's suitable for someone looking to contribute for the first time by reviewing code and removed Good First Review A PR that's suitable for someone looking to contribute for the first time by reviewing code labels Jun 15, 2020
@ellatrix
Copy link
Member

Let's try to do this for 5.5 :)

@talldan talldan mentioned this pull request Jun 18, 2020
@talldan talldan linked an issue Jun 18, 2020 that may be closed by this pull request
Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this 🎉

@@ -166,7 +178,7 @@ function BlockPopover( {
shouldAnchorIncludePadding
// Popover calculates the width once. Trigger a reset by remounting
// the component.
key={ shouldShowContextualToolbar }
key={ shouldShowContextualToolbar || isToolbarForced }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to determine what effect this change has, updating the comment would be great.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, if isToolbarForced isn't part of the key, then the component will still be unmounted unexpectedly when shouldShowContextualToolbar changes.

I will push a change that explains this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ellatrix Maybe we don't need this key anymore since now we compute recompute when the width/height changes in Popover?

Comment on lines +128 to +139
const distanceY = event.clientY - dragStartY.current;
if ( distanceY > SCROLL_INACTIVE_DISTANCE_PX ) {
velocityY.current =
VELOCITY_MULTIPLIER *
( distanceY - SCROLL_INACTIVE_DISTANCE_PX );
} else if ( distanceY < -SCROLL_INACTIVE_DISTANCE_PX ) {
velocityY.current =
VELOCITY_MULTIPLIER *
( distanceY + SCROLL_INACTIVE_DISTANCE_PX );
} else {
velocityY.current = 0;
}
Copy link
Contributor

@talldan talldan Jun 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A usability issue I had is that the feature is less effective if you're dragging from near the top or the bottom of the screen.

When dragging from the top downwards, if I wanted to aim for a block that I didn't need to scroll to but was nearer the lower part of the screen, I found I would scroll past my target very quickly as I moved my cursor down.

The opposite was true when dragging upwards from near the top of the screen, the page wouldn't scroll very quickly because there wasn't much distance I could move the cursor up.

An answer might be to make the velocity calculation based on the percentage the cursor has moved towards the edge of the screen (or probably scroll container), in pseudocode for moving down (I haven't factored the inactive scroll distance for this):

const movableDistance = screenHeight - dragStartY.current;
const dragDistance = event.clientY - dragStartY.current;
const distancePercentage = dragDistance / movableDistance;
// distancePercentage would be a much smaller value, so VELOCITY_MULTIPLIER would have to be adjusted.
velocityY.current = VELOCITY_MULTIPLIER * distancePercentage

An interesting iteration might also be to try easing on the calculation (e.g. https://easings.net/#easeInQuart), so that the speed isn't too dramatic at first, but worth trying one thing at a time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are great suggestions. Given that this patch is already considered to be an improvement, I would suggest we raise those as tickets for future work and get this merged. That way we can compare multiple strategies while not missing the 5.5 window.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gravityrail I do still have concerns about usability, I feel like it's pretty easy to make the page start scrolling, but harder to make it stop in the right place.

Given others have said they're happy with the change, lets push on and address this as a follow-up 👍

You mentioned that you don't have a lot of time to work on this, would it be better if I try out the suggestion in a follow-up PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love it if you could do a follow up PR with your suggestions. Happy to pitch in and test.

@@ -150,7 +154,9 @@ class Draggable extends Component {
document.body.classList.add( 'is-dragging-components-draggable' );
document.addEventListener( 'dragover', this.onDragOver );

this.props.setTimeout( onDragStart );
this.props.setTimeout(
onDragStart.bind( this, event.clientX, event.clientY )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to pass the event object rather than the clientX and Y.

Maybe also the same for onDragEnd so that the interface is consistent for each of the callbacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I couldn't get that to work - something happens to the event object when I pass it, and it ends up with null clientX and clientY props. I don't know enough to about React, javascript or how browsers work to precisely know why. I would love for someone else to fix it because I agree - that API shape ain't the cleanest.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gravityrail It's possibly related to React's event pooling behavior given the event is being passed to a timeout:
https://reactjs.org/docs/events.html#event-pooling

I'll do some debugging.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it was related to the event pooling.

I pushed a commit that makes the change. If you want to git fetch from this repo and then git cherry-pick db82900 will bring it onto your branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Thanks for the patch 👍

@jasmussen
Copy link
Contributor

I'm seeing an issue in the master branch where the draggable clone disappears when you're dragging around the edge of the screen:

dragondrop

Does this PR address or improve that situation?

@ellatrix
Copy link
Member

I'm fine with merging this sooner rather than later, iterate on it, and get some exposure in the master branch.

@gravityrail
Copy link
Contributor Author

@jasmussen yes, this PR does address the disappearing clone issue.

@jasmussen
Copy link
Contributor

Let's get this in 👌

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm testing on the demo post and when I drop an element being dragged, the editor scrolls to a completely different location.

const velocityY = useRef( null );
const scrollParentY = useRef( null );

const scrollEditorInterval = useRef( null );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think there's away to extract the logic into a dedicated hook as it's kind of obscures the code of the component. Would something like that (or close) work?

const [ onDragStart, onDragOver, onDragEnd ] = useScrollOnDrag()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. I don't have a ton of bandwidth to refine this so you should feel free to mess around with it and propose a better-factored alternative.

@gravityrail
Copy link
Contributor Author

I'm testing on the demo post and when I drop an element being dragged, the editor scrolls to a completely different location.

@youknowriad do you have a sense of what the cause/solution of this bug is?

@mtias
Copy link
Member

mtias commented Jun 24, 2020

I'm testing on the demo post and when I drop an element being dragged, the editor scrolls to a completely different location.

This happens in master as well, I've run into it trying to demo some drag and drop features.

Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see anything blocking merge here, and the consensus from those testing seems to be good.

Thanks for all your work @gravityrail, it's great to get some much needed drag and drop improvements into WordPress 5.5.

I'm happy to work on a couple of follow-up PRs as @gravityrail mentioned not having much time:

  • Encapsulate the scrolling behavior in a React Hook
  • Try some minor adjustments to the scroll velocity

@talldan
Copy link
Contributor

talldan commented Jun 25, 2020

I've made a few small follow-up PRs:

More experimentally I tried a couple of things with how the scroll speed is calculated:

@ellatrix
Copy link
Member

Thanks @gravityrail and @talldan! 🎉

@lelas
Copy link

lelas commented Apr 22, 2021

Came here because drag and drop is a complete hit and miss now (Chrome + Firefox). As soon as I start dragging, scrolling begins and it is super difficult to drop a block in the right position. It's pure "Drag and random drop". Headlines landing 4 blocks down, in the wrong columns, etc. etc. Incredible difficult to work with as-is.

@gravityrail
Copy link
Contributor Author

gravityrail commented Apr 22, 2021 via email

@lelas
Copy link

lelas commented Apr 23, 2021

Thanks for the report. Sounds like a regression. Scrolling is only meant to begin once you have dragged towards the top or bottom of the screen, though maybe there's something weird about how your browser computes screen sizes that is throwing off the algorithm What is your browser, operating system, and screen resolution? (With versions)

Thanks for the swift response, Daniel. And apologies for cross posting. I commented on issue 24770 afterwards and was going to delete my comment on this issue.

OS: Windows 10 Pro, version 10.0.19042, Build 19042
Browser: Chrome, version 89.0.4389.128 (Official Build) (64-bit)
Screen resolution: 3440 x 1440 Pixels
Browser window size: 1734 x 1417 (while experiencing these issues earlier)

I only briefly tried Firefox. Experienced issues there too, so continued in Chrome. I'll see if I experience the same problems on my laptop tomorrow -- to rule out issues with my Chrome installation and mouse settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Drag and Drop Drag and drop functionality when working with blocks [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cannot drag and scroll
10 participants